From 4c6bbabc1ce442ee7bcf1e35ed0471d8362a810b Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:54:15 +0100 Subject: [PATCH] Dashboard: Migration - Dashboard Settings Variables (List, Duplicate, Delete) (#78917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Torkel Ödegaard --- .../scene/DashboardSceneUrlSync.ts | 2 +- .../transformSaveModelToScene.test.ts | 19 ++ .../transformSaveModelToScene.ts | 5 + .../settings/DashboardLinksEditView.tsx | 5 +- .../settings/GeneralSettingsEditView.test.tsx | 6 +- .../settings/GeneralSettingsEditView.tsx | 16 +- .../settings/VariablesEditView.test.tsx | 148 +++++++++++++++ .../settings/VariablesEditView.tsx | 125 +++++++++++-- .../dashboard-scene/settings/utils.ts | 19 +- .../settings/variables/VariableEditorList.tsx | 125 +++++++++++++ .../variables/VariableEditorListRow.tsx | 173 ++++++++++++++++++ 11 files changed, 601 insertions(+), 42 deletions(-) create mode 100644 public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts index 25902b406b18..abd5539e57f4 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts +++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts @@ -36,7 +36,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler { const update: Partial = {}; if (typeof values.editview === 'string' && meta.canEdit) { - update.editview = createDashboardEditViewFor(values.editview, this._scene.getRef()); + update.editview = createDashboardEditViewFor(values.editview); // If we are not in editing (for example after full page reload) if (!isEditing) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 136912c5a794..98fd0cb73105 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -122,6 +122,25 @@ describe('transformSaveModelToScene', () => { expect(scene.state.$behaviors![1]).toBeInstanceOf(behaviors.CursorSync); expect((scene.state.$behaviors![1] as behaviors.CursorSync).state.sync).toEqual(DashboardCursorSync.Crosshair); }); + + it('should initialize the Dashboard Scene with empty template variables', () => { + const dash = { + ...defaultDashboard, + title: 'test empty dashboard with no variables', + uid: 'test-uid', + time: { from: 'now-10h', to: 'now' }, + weekStart: 'saturday', + fiscalYearStartMonth: 2, + timezone: 'America/New_York', + templating: { + list: [], + }, + }; + const oldModel = new DashboardModel(dash); + + const scene = createDashboardSceneFromDashboardModel(oldModel); + expect(scene.state.$variables?.state.variables).toBeDefined(); + }); }); describe('when organizing panels as scene children', () => { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index cdf16c08bb74..54e7fcd04e5f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -195,6 +195,11 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) variables = new SceneVariableSet({ variables: variableObjects, }); + } else { + // Create empty variable set + variables = new SceneVariableSet({ + variables: [], + }); } if (oldModel.annotations?.list?.length) { diff --git a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.tsx b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.tsx index cdd14a67f54b..5be269c60924 100644 --- a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.tsx +++ b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.tsx @@ -9,6 +9,7 @@ import { Page } from 'app/core/components/Page/Page'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; +import { getDashboardSceneFor } from '../utils/utils'; import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync'; import { DashboardEditView, DashboardEditListViewState, useDashboardEditPageNav } from './utils'; @@ -26,8 +27,8 @@ export class DashboardLinksEditView extends SceneObjectBase) { - const { dashboardRef, editIndex } = model.useState(); - const dashboard = dashboardRef.resolve(); + const { editIndex } = model.useState(); + const dashboard = getDashboardSceneFor(model); const links = dashboard.state.links || []; const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx index dd862e6914a6..f767fb0d8143 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx @@ -112,6 +112,7 @@ describe('GeneralSettingsEditView', () => { }); async function buildTestScene() { + const settings = new GeneralSettingsEditView({}); const dashboard = new DashboardScene({ $timeRange: new SceneTimeRange({}), $behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Off })], @@ -143,10 +144,7 @@ async function buildTestScene() { }), ], }), - }); - - const settings = new GeneralSettingsEditView({ - dashboardRef: dashboard.getRef(), + editview: settings, }); activateFullSceneTree(dashboard); diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index f3bef069c6c5..c4ef84e831cd 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -1,14 +1,7 @@ import React, { ChangeEvent } from 'react'; import { PageLayoutType } from '@grafana/data'; -import { - behaviors, - SceneComponentProps, - SceneObjectBase, - SceneObjectRef, - SceneTimePicker, - sceneGraph, -} from '@grafana/scenes'; +import { behaviors, SceneComponentProps, SceneObjectBase, SceneTimePicker, sceneGraph } from '@grafana/scenes'; import { TimeZone } from '@grafana/schema'; import { Box, @@ -31,12 +24,11 @@ import { DashboardControls } from '../scene/DashboardControls'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; +import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; -export interface GeneralSettingsEditViewState extends DashboardEditViewState { - dashboardRef: SceneObjectRef; -} +export interface GeneralSettingsEditViewState extends DashboardEditViewState {} const EDITABLE_OPTIONS = [ { label: 'Editable', value: true }, @@ -54,7 +46,7 @@ export class GeneralSettingsEditView implements DashboardEditView { private get _dashboard(): DashboardScene { - return this.state.dashboardRef.resolve(); + return getDashboardSceneFor(this); } public getUrlKey(): string { diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx new file mode 100644 index 000000000000..1e71389e8a3b --- /dev/null +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx @@ -0,0 +1,148 @@ +import { SceneVariableSet, CustomVariable, SceneGridItem, SceneGridLayout } from '@grafana/scenes'; + +import { DashboardScene } from '../scene/DashboardScene'; +import { activateFullSceneTree } from '../utils/test-utils'; + +import { VariablesEditView } from './VariablesEditView'; + +describe('VariablesEditView', () => { + describe('Dashboard Variables state', () => { + let dashboard: DashboardScene; + let variableView: VariablesEditView; + + beforeEach(async () => { + const result = await buildTestScene(); + dashboard = result.dashboard; + variableView = result.variableView; + }); + + it('should return the correct urlKey', () => { + expect(variableView.getUrlKey()).toBe('variables'); + }); + + it('should return the dashboard', () => { + expect(variableView.getDashboard()).toBe(dashboard); + }); + + it('should return the list of variables', () => { + const expectedVariables = [ + { + type: 'custom', + name: 'customVar', + query: 'test, test2', + value: 'test', + }, + { + type: 'custom', + name: 'customVar2', + query: 'test3, test4', + value: 'test3', + }, + ]; + const variables = variableView.getVariables(); + expect(variables).toHaveLength(2); + expect(variables[0].state).toMatchObject(expectedVariables[0]); + expect(variables[1].state).toMatchObject(expectedVariables[1]); + }); + }); + + describe('Dashboard Variables actions', () => { + let variableView: VariablesEditView; + + beforeEach(async () => { + const result = await buildTestScene(); + variableView = result.variableView; + }); + + it('should duplicate a variable', () => { + const variables = variableView.getVariables(); + const variable = variables[0]; + variableView.onDuplicated(variable.state.name); + expect(variableView.getVariables()).toHaveLength(3); + expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar'); + }); + + it('should handle name when duplicating a variable twice', () => { + const variableIdentifier = 'customVar'; + variableView.onDuplicated(variableIdentifier); + variableView.onDuplicated(variableIdentifier); + expect(variableView.getVariables()).toHaveLength(4); + expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar_1'); + expect(variableView.getVariables()[2].state.name).toBe('copy_of_customVar'); + }); + + it('should delete a variable', () => { + const variableIdentifier = 'customVar'; + variableView.onDelete(variableIdentifier); + expect(variableView.getVariables()).toHaveLength(1); + expect(variableView.getVariables()[0].state.name).toBe('customVar2'); + }); + + it('should change order of variables', () => { + const fromIndex = 0; // customVar is first + const toIndex = 1; + variableView.onOrderChanged(fromIndex, toIndex); + expect(variableView.getVariables()[0].state.name).toBe('customVar2'); + expect(variableView.getVariables()[1].state.name).toBe('customVar'); + }); + + it('should keep the same order of variables with invalid indexes', () => { + const fromIndex = 0; + const toIndex = 2; + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + variableView.onOrderChanged(fromIndex, toIndex); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(variableView.getVariables()[0].state.name).toBe('customVar'); + expect(variableView.getVariables()[1].state.name).toBe('customVar2'); + + errorSpy.mockRestore(); + }); + }); +}); + +async function buildTestScene() { + const variableView = new VariablesEditView({}); + const dashboard = new DashboardScene({ + title: 'Dashboard with variables', + uid: 'dash-variables', + meta: { + canEdit: true, + }, + $variables: new SceneVariableSet({ + variables: [ + new CustomVariable({ + name: 'customVar', + query: 'test, test2', + }), + new CustomVariable({ + name: 'customVar2', + query: 'test3, test4', + }), + ], + }), + body: new SceneGridLayout({ + children: [ + new SceneGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: undefined, + }), + ], + }), + editview: variableView, + }); + + activateFullSceneTree(dashboard); + + await new Promise((r) => setTimeout(r, 1)); + + dashboard.onEnterEditMode(); + variableView.activate(); + + return { dashboard, variableView }; +} diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx index 60a02a431b0b..7f9999ad1b7a 100644 --- a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx @@ -1,30 +1,133 @@ import React from 'react'; import { PageLayoutType } from '@grafana/data'; -import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectBase, SceneVariables, sceneGraph } from '@grafana/scenes'; import { Page } from 'app/core/components/Page/Page'; +import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; - +import { VariableEditorList } from './variables/VariableEditorList'; export interface VariablesEditViewState extends DashboardEditViewState {} export class VariablesEditView extends SceneObjectBase implements DashboardEditView { + public static Component = VariableEditorSettingsListView; + public getUrlKey(): string { return 'variables'; } - static Component = ({ model }: SceneComponentProps) => { - const dashboard = getDashboardSceneFor(model); - const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); + public getDashboard(): DashboardScene { + return getDashboardSceneFor(this); + } + + public getVariableSet(): SceneVariables { + return sceneGraph.getVariables(this.getDashboard()); + } + + private getVariableIndex = (identifier: string) => { + const variables = this.getVariables(); + return variables.findIndex((variable) => variable.state.name === identifier); + }; + + public onDelete = (identifier: string) => { + // Find the index of the variable to be deleted + const variableIndex = this.getVariableIndex(identifier); + const { variables } = this.getVariableSet().state; + if (variableIndex === -1) { + // Handle the case where the variable is not found + console.error('Variable not found'); + return; + } + + // Create a new array excluding the variable to be deleted + const updatedVariables = [...variables.slice(0, variableIndex), ...variables.slice(variableIndex + 1)]; - return ( - - -
variables todo
-
- ); + // Update the state or the variables array + this.getVariableSet().setState({ variables: updatedVariables }); }; + + public getVariables() { + return this.getVariableSet().state.variables; + } + + public onDuplicated = (identifier: string) => { + const variableIndex = this.getVariableIndex(identifier); + const variables = this.getVariableSet().state.variables; + + if (variableIndex === -1) { + console.error('Variable not found'); + return; + } + + const originalVariable = variables[variableIndex]; + let copyNumber = 0; + let newName = `copy_of_${originalVariable.state.name}`; + + // Check if the name is unique, if not, increment the copy number + while (variables.some((v) => v.state.name === newName)) { + copyNumber++; + newName = `copy_of_${originalVariable.state.name}_${copyNumber}`; + } + + //clone the original variable + + const newVariable = originalVariable.clone(originalVariable.state); + // update state name of the new variable + newVariable.setState({ name: newName }); + + const updatedVariables = [ + ...variables.slice(0, variableIndex + 1), + newVariable, + ...variables.slice(variableIndex + 1), + ]; + + this.getVariableSet().setState({ variables: updatedVariables }); + }; + + public onOrderChanged = (fromIndex: number, toIndex: number) => { + const variables = this.getVariableSet().state.variables; + if (!this.getVariableSet()) { + return; + } + // check the index are within the variables array + if (fromIndex < 0 || fromIndex >= variables.length || toIndex < 0 || toIndex >= variables.length) { + console.error('Invalid index'); + return; + } + const updatedVariables = [...variables]; + // Remove the variable from the array + const movedItem = updatedVariables.splice(fromIndex, 1); + updatedVariables.splice(toIndex, 0, movedItem[0]); + const variablesScene = this.getVariableSet(); + variablesScene.setState({ variables: updatedVariables }); + }; + + public onEdit = (identifier: string) => { + return 'not implemented'; + }; +} + +function VariableEditorSettingsListView({ model }: SceneComponentProps) { + const dashboard = model.getDashboard(); + const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); + // get variables from dashboard state + const { onDelete, onDuplicated, onOrderChanged, onEdit } = model; + const { variables } = model.getVariableSet().useState(); + + return ( + + + {}} + onEdit={onEdit} + /> + + ); } diff --git a/public/app/features/dashboard-scene/settings/utils.ts b/public/app/features/dashboard-scene/settings/utils.ts index 3d29de0ee7d4..55956d5b5455 100644 --- a/public/app/features/dashboard-scene/settings/utils.ts +++ b/public/app/features/dashboard-scene/settings/utils.ts @@ -1,7 +1,7 @@ import { useLocation } from 'react-router-dom'; import { locationUtil, NavModelItem } from '@grafana/data'; -import { SceneObject, SceneObjectRef, SceneObjectState } from '@grafana/scenes'; +import { SceneObject, SceneObjectState } from '@grafana/scenes'; import { t } from 'app/core/internationalization'; import { getNavModel } from 'app/core/selectors/navModel'; import { useSelector } from 'app/types'; @@ -13,9 +13,7 @@ import { DashboardLinksEditView } from './DashboardLinksEditView'; import { GeneralSettingsEditView } from './GeneralSettingsEditView'; import { VariablesEditView } from './VariablesEditView'; -export interface DashboardEditViewState extends SceneObjectState { - dashboardRef: SceneObjectRef; -} +export interface DashboardEditViewState extends SceneObjectState {} export interface DashboardEditListViewState extends DashboardEditViewState { /** Index of the list item to edit */ @@ -63,19 +61,16 @@ export function useDashboardEditPageNav(dashboard: DashboardScene, currentEditVi return { navModel, pageNav }; } -export function createDashboardEditViewFor( - editview: string, - dashboardRef: SceneObjectRef -): DashboardEditView { +export function createDashboardEditViewFor(editview: string): DashboardEditView { switch (editview) { case 'annotations': - return new AnnotationsEditView({ dashboardRef }); + return new AnnotationsEditView({}); case 'variables': - return new VariablesEditView({ dashboardRef }); + return new VariablesEditView({}); case 'links': - return new DashboardLinksEditView({ dashboardRef }); + return new DashboardLinksEditView({}); case 'settings': default: - return new GeneralSettingsEditView({ dashboardRef }); + return new GeneralSettingsEditView({}); } } diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx new file mode 100644 index 000000000000..8cbe42568efe --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx @@ -0,0 +1,125 @@ +import { css } from '@emotion/css'; +import React, { ReactElement } from 'react'; +import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; + +import { selectors } from '@grafana/e2e-selectors'; +import { reportInteraction } from '@grafana/runtime'; +import { SceneVariable, SceneVariableState } from '@grafana/scenes'; +import { useStyles2, Stack } from '@grafana/ui'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; + +import { VariableEditorListRow } from './VariableEditorListRow'; + +export interface Props { + variables: Array>; + onAdd: () => void; + onChangeOrder: (fromIndex: number, toIndex: number) => void; + onDuplicate: (identifier: string) => void; + onDelete: (identifier: string) => void; + onEdit: (identifier: string) => void; +} + +export function VariableEditorList({ + variables, + onChangeOrder, + onDelete, + onDuplicate, + onAdd, + onEdit, +}: Props): ReactElement { + const styles = useStyles2(getStyles); + const onDragEnd = (result: DropResult) => { + if (!result.destination || !result.source) { + return; + } + reportInteraction('Variable drag and drop'); + onChangeOrder(result.source.index, result.destination.index); + }; + + return ( +
+
+ {variables.length === 0 && } + + {variables.length > 0 && ( + +
+ + + + + + + + + + {(provided) => ( + + {variables.map((variableScene, index) => { + const variableState = variableScene.state; + return ( + + ); + })} + {provided.placeholder} + + )} + + +
VariableDefinition +
+
+
+ )} +
+
+ ); +} + +function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement { + return ( +
+ + Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server + or sensor names in your metric queries you can use variables in their place. Variables are shown as + list boxes at the top of the dashboard. These drop-down lists make it easy to change the data + being displayed in your dashboard. Check out the + + Templates and variables documentation + + for more information. +

`, + }} + infoBoxTitle="What do variables do?" + onClick={(event) => { + event.preventDefault(); + onAdd(); + }} + /> +
+ ); +} + +const getStyles = () => ({ + tableContainer: css({ + overflow: 'scroll', + width: '100%', + }), +}); diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx new file mode 100644 index 000000000000..3850e071e88b --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx @@ -0,0 +1,173 @@ +import { css } from '@emotion/css'; +import React, { ReactElement, useState } from 'react'; +import { Draggable } from 'react-beautiful-dnd'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { reportInteraction } from '@grafana/runtime'; +import { QueryVariable, SceneVariable } from '@grafana/scenes'; +import { Button, ConfirmModal, Icon, IconButton, useStyles2, useTheme2 } from '@grafana/ui'; +import { hasOptions } from 'app/features/variables/guard'; + +export interface VariableEditorListRowProps { + index: number; + variable: SceneVariable; + onEdit: (identifier: string) => void; + onDuplicate: (identifier: string) => void; + onDelete: (identifier: string) => void; +} + +export function VariableEditorListRow({ + index, + variable, + onEdit: propsOnEdit, + onDuplicate: propsOnDuplicate, + onDelete: propsOnDelete, +}: VariableEditorListRowProps): ReactElement { + const theme = useTheme2(); + const styles = useStyles2(getStyles); + const definition = getDefinition(variable); + const variableState = variable.state; + const identifier = variableState.name; + const [showDeleteModal, setShowDeleteModal] = useState(false); + const handleDeleteVariableModal = (show: boolean) => () => { + setShowDeleteModal(show); + }; + const onDeleteVariable = () => { + reportInteraction('Delete variable'); + propsOnDelete(identifier); + }; + + return ( + + {(provided, snapshot) => ( + + + + + { + event.preventDefault(); + propsOnEdit(identifier); + }} + data-testid={selectors.pages.Dashboard.Settings.Variables.List.tableRowDefinitionFields(variableState.name)} + > + {definition} + + + +
+ { + event.preventDefault(); + reportInteraction('Duplicate variable'); + propsOnDuplicate(identifier); + }} + name="copy" + tooltip="Duplicate variable" + data-testid={selectors.pages.Dashboard.Settings.Variables.List.tableRowDuplicateButtons( + variableState.name + )} + /> + { + event.preventDefault(); + setShowDeleteModal(true); + }} + name="trash-alt" + tooltip="Remove variable" + data-testid={selectors.pages.Dashboard.Settings.Variables.List.tableRowRemoveButtons( + variableState.name + )} + /> + + +
+ +
+
+ + + )} +
+ ); +} + +function getDefinition(model: SceneVariable): string { + let definition = ''; + if (model instanceof QueryVariable) { + if (model.state.definition) { + definition = model.state.definition; + } else if (typeof model.state.query === 'string') { + definition = model.state.query; + } + } else if (hasOptions(model.state)) { + definition = model.state.query; + } + return definition; +} + +function getStyles(theme: GrafanaTheme2) { + return { + dragHandle: css({ + cursor: 'grab', + marginLeft: theme.spacing(1), + }), + column: css({ + width: '1%', + }), + nameLink: css({ + cursor: 'pointer', + color: theme.colors.primary.text, + }), + definitionColumn: css({ + width: '100%', + maxWidth: '200px', + cursor: 'pointer', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }), + iconPassed: css({ + color: theme.v1.palette.greenBase, + marginRight: theme.spacing(2), + }), + iconFailed: css({ + color: theme.v1.palette.orange, + marginRight: theme.spacing(2), + }), + icons: css({ + display: 'flex', + gap: theme.spacing(2), + alignItems: 'center', + }), + }; +}