Dashboard: Variable controls via simple react component (#103442)

* Dashboard: Variable controls refactor

* Update tests

* Fix name

* fix lint
pull/103450/head
Torkel Ödegaard 9 months ago committed by GitHub
parent 48877f9187
commit 5aa358c481
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      public/app/features/dashboard-scene/scene/DashboardControls.test.tsx
  2. 27
      public/app/features/dashboard-scene/scene/DashboardControls.tsx
  3. 70
      public/app/features/dashboard-scene/scene/VariableControls.tsx
  4. 3
      public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts
  5. 4
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
  6. 3
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts

@ -1,7 +1,7 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes'; import { SceneVariableSet, TextBoxVariable } from '@grafana/scenes';
import { DashboardControls, DashboardControlsState } from './DashboardControls'; import { DashboardControls, DashboardControlsState } from './DashboardControls';
import { DashboardScene } from './DashboardScene'; import { DashboardScene } from './DashboardScene';
@ -10,7 +10,6 @@ describe('DashboardControls', () => {
describe('Given a standard scene', () => { describe('Given a standard scene', () => {
it('should initialize with default values', () => { it('should initialize with default values', () => {
const scene = buildTestScene(); const scene = buildTestScene();
expect(scene.state.variableControls).toEqual([]);
expect(scene.state.timePicker).toBeDefined(); expect(scene.state.timePicker).toBeDefined();
expect(scene.state.refreshPicker).toBeDefined(); expect(scene.state.refreshPicker).toBeDefined();
}); });
@ -38,9 +37,7 @@ describe('DashboardControls', () => {
}); });
it('should render visible controls', async () => { it('should render visible controls', async () => {
const scene = buildTestScene({ const scene = buildTestScene({});
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
});
const renderer = render(<scene.Component model={scene} />); const renderer = render(<scene.Component model={scene} />);
expect(await renderer.findByTestId(selectors.pages.Dashboard.Controls)).toBeInTheDocument(); expect(await renderer.findByTestId(selectors.pages.Dashboard.Controls)).toBeInTheDocument();
@ -55,7 +52,6 @@ describe('DashboardControls', () => {
hideTimeControls: true, hideTimeControls: true,
hideVariableControls: true, hideVariableControls: true,
hideLinksControls: true, hideLinksControls: true,
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
}); });
const renderer = render(<scene.Component model={scene} />); const renderer = render(<scene.Component model={scene} />);

@ -4,7 +4,6 @@ import { GrafanaTheme2, VariableHide } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { import {
SceneObjectState, SceneObjectState,
SceneObject,
SceneObjectBase, SceneObjectBase,
SceneComponentProps, SceneComponentProps,
SceneTimePicker, SceneTimePicker,
@ -22,9 +21,10 @@ import { PanelEditControls } from '../panel-edit/PanelEditControls';
import { getDashboardSceneFor } from '../utils/utils'; import { getDashboardSceneFor } from '../utils/utils';
import { DashboardLinksControls } from './DashboardLinksControls'; import { DashboardLinksControls } from './DashboardLinksControls';
import { DashboardScene } from './DashboardScene';
import { VariableControls } from './VariableControls';
export interface DashboardControlsState extends SceneObjectState { export interface DashboardControlsState extends SceneObjectState {
variableControls: SceneObject[];
timePicker: SceneTimePicker; timePicker: SceneTimePicker;
refreshPicker: SceneRefreshPicker; refreshPicker: SceneRefreshPicker;
hideTimeControls?: boolean; hideTimeControls?: boolean;
@ -73,7 +73,6 @@ export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
public constructor(state: Partial<DashboardControlsState>) { public constructor(state: Partial<DashboardControlsState>) {
super({ super({
variableControls: [],
timePicker: state.timePicker ?? new SceneTimePicker({}), timePicker: state.timePicker ?? new SceneTimePicker({}),
refreshPicker: state.refreshPicker ?? new SceneRefreshPicker({}), refreshPicker: state.refreshPicker ?? new SceneRefreshPicker({}),
...state, ...state,
@ -119,8 +118,7 @@ export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
} }
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) { function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
const { variableControls, refreshPicker, timePicker, hideTimeControls, hideVariableControls, hideLinksControls } = const { refreshPicker, timePicker, hideTimeControls, hideVariableControls, hideLinksControls } = model.useState();
model.useState();
const dashboard = getDashboardSceneFor(model); const dashboard = getDashboardSceneFor(model);
const { links, editPanel } = dashboard.useState(); const { links, editPanel } = dashboard.useState();
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
@ -137,7 +135,12 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
className={cx(styles.controls, editPanel && styles.controlsPanelEdit)} className={cx(styles.controls, editPanel && styles.controlsPanelEdit)}
> >
<Stack grow={1} wrap={'wrap'}> <Stack grow={1} wrap={'wrap'}>
{!hideVariableControls && variableControls.map((c) => <c.Component model={c} key={c.state.key} />)} {!hideVariableControls && (
<>
<VariableControls dashboard={dashboard} />
<DataLayerControls dashboard={dashboard} />
</>
)}
<Box grow={1} /> <Box grow={1} />
{!hideLinksControls && !editPanel && <DashboardLinksControls links={links} dashboard={dashboard} />} {!hideLinksControls && !editPanel && <DashboardLinksControls links={links} dashboard={dashboard} />}
{editPanel && <PanelEditControls panelEditor={editPanel} />} {editPanel && <PanelEditControls panelEditor={editPanel} />}
@ -153,6 +156,18 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
); );
} }
function DataLayerControls({ dashboard }: { dashboard: DashboardScene }) {
const layers = sceneGraph.getDataLayers(dashboard, true);
return (
<>
{layers.map((layer) => (
<layer.Component model={layer} key={layer.state.key} />
))}
</>
);
}
function getStyles(theme: GrafanaTheme2) { function getStyles(theme: GrafanaTheme2) {
return { return {
controls: css({ controls: css({

@ -0,0 +1,70 @@
import { css } from '@emotion/css';
import { VariableHide } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { sceneGraph, useSceneObjectState, SceneVariable, SceneVariableState, ControlsLabel } from '@grafana/scenes';
import { DashboardScene } from './DashboardScene';
export function VariableControls({ dashboard }: { dashboard: DashboardScene }) {
const variables = sceneGraph.getVariables(dashboard)!.useState();
return (
<>
{variables.variables.map((variable) => (
<VariableValueSelectWrapper key={variable.state.key} variable={variable} />
))}
</>
);
}
interface VariableSelectProps {
variable: SceneVariable;
}
export function VariableValueSelectWrapper({ variable }: VariableSelectProps) {
const state = useSceneObjectState<SceneVariableState>(variable, { shouldActivateOrKeepAlive: true });
if (state.hide === VariableHide.hideVariable) {
return null;
}
return (
<div className={containerStyle} data-testid={selectors.pages.Dashboard.SubMenu.submenuItem}>
<VariableLabel variable={variable} />
<variable.Component model={variable} />
</div>
);
}
function VariableLabel({ variable }: VariableSelectProps) {
const { state } = variable;
if (variable.state.hide === VariableHide.hideLabel) {
return null;
}
const labelOrName = state.label || state.name;
const elementId = `var-${state.key}`;
return (
<ControlsLabel
htmlFor={elementId}
isLoading={state.loading}
onCancel={() => variable.onCancel?.()}
label={labelOrName}
error={state.error}
layout={'horizontal'}
description={state.description ?? undefined}
/>
);
}
const containerStyle = css({
display: 'flex',
// No border for second element (inputs) as label and input border is shared
'> :nth-child(2)': css({
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
}),
});

@ -10,14 +10,12 @@ import {
GroupByVariable, GroupByVariable,
IntervalVariable, IntervalVariable,
QueryVariable, QueryVariable,
SceneDataLayerControls,
SceneRefreshPicker, SceneRefreshPicker,
SceneTimePicker, SceneTimePicker,
SceneTimeRange, SceneTimeRange,
SceneVariable, SceneVariable,
SceneVariableSet, SceneVariableSet,
TextBoxVariable, TextBoxVariable,
VariableValueSelectors,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { import {
AdhocVariableKind, AdhocVariableKind,
@ -191,7 +189,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
annotationLayers, annotationLayers,
}), }),
controls: new DashboardControls({ controls: new DashboardControls({
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
timePicker: new SceneTimePicker({ timePicker: new SceneTimePicker({
quickRanges: dashboard.timeSettings.quickRanges, quickRanges: dashboard.timeSettings.quickRanges,
}), }),

@ -5,7 +5,6 @@ import {
AdHocFiltersVariable, AdHocFiltersVariable,
behaviors, behaviors,
ConstantVariable, ConstantVariable,
SceneDataLayerControls,
SceneDataTransformer, SceneDataTransformer,
SceneGridLayout, SceneGridLayout,
SceneGridRow, SceneGridRow,
@ -836,7 +835,6 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} });
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
const dataLayers = scene.state.$data as DashboardDataLayerSet; const dataLayers = scene.state.$data as DashboardDataLayerSet;
expect(dataLayers.state.annotationLayers).toHaveLength(4); expect(dataLayers.state.annotationLayers).toHaveLength(4);
@ -864,7 +862,6 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} });
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
const dataLayers = scene.state.$data as DashboardDataLayerSet; const dataLayers = scene.state.$data as DashboardDataLayerSet;
expect(dataLayers.state.alertStatesLayer).toBeDefined(); expect(dataLayers.state.alertStatesLayer).toBeDefined();
@ -877,7 +874,6 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} });
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
const dataLayers = scene.state.$data as DashboardDataLayerSet; const dataLayers = scene.state.$data as DashboardDataLayerSet;
expect(dataLayers.state.alertStatesLayer).toBeDefined(); expect(dataLayers.state.alertStatesLayer).toBeDefined();

@ -9,7 +9,6 @@ import {
SceneGridRow, SceneGridRow,
SceneTimeRange, SceneTimeRange,
SceneVariableSet, SceneVariableSet,
VariableValueSelectors,
SceneRefreshPicker, SceneRefreshPicker,
SceneObject, SceneObject,
VizPanelMenu, VizPanelMenu,
@ -17,7 +16,6 @@ import {
VizPanelState, VizPanelState,
SceneGridItemLike, SceneGridItemLike,
SceneDataLayerProvider, SceneDataLayerProvider,
SceneDataLayerControls,
UserActionEvent, UserActionEvent,
SceneInteractionProfileEvent, SceneInteractionProfileEvent,
SceneObjectState, SceneObjectState,
@ -278,7 +276,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
$behaviors: behaviorList, $behaviors: behaviorList,
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }), $data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
controls: new DashboardControls({ controls: new DashboardControls({
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
timePicker: new SceneTimePicker({ timePicker: new SceneTimePicker({
quickRanges: oldModel.timepicker.quick_ranges, quickRanges: oldModel.timepicker.quick_ranges,
}), }),

Loading…
Cancel
Save