diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts index 1171d9ef776..6d9a2279563 100644 --- a/public/app/features/dashboard-scene/edit-pane/shared.ts +++ b/public/app/features/dashboard-scene/edit-pane/shared.ts @@ -17,6 +17,7 @@ import { SceneGridRowEditableElement } from '../scene/layout-default/SceneGridRo import { redoButtonId, undoButtonID } from '../scene/new-toolbar/RightActions'; import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement'; import { LocalVariableEditableElement } from '../settings/variables/LocalVariableEditableElement'; +import { VariableAdd, VariableAddEditableElement } from '../settings/variables/VariableAddEditableElement'; import { VariableEditableElement } from '../settings/variables/VariableEditableElement'; import { VariableSetEditableElement } from '../settings/variables/VariableSetEditableElement'; import { isSceneVariable } from '../settings/variables/utils'; @@ -61,6 +62,10 @@ export function getEditableElementFor(sceneObj: SceneObject | undefined): Editab return new VariableEditableElement(sceneObj); } + if (sceneObj instanceof VariableAdd) { + return new VariableAddEditableElement(sceneObj); + } + return undefined; } @@ -202,7 +207,7 @@ export const dashboardEditActions = { }), addVariable({ source, addedObject }: AddVariableActionHelperProps) { - const varsBeforeAddition = [...source.state.variables]; + const varsBeforeAddition = [...(source.state.variables ?? [])]; dashboardEditActions.addElement({ source, diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index e285b94e5e1..4052efc6bd4 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css'; import { VariableHide, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; import { sceneGraph, useSceneObjectState, @@ -14,6 +15,7 @@ import { import { useElementSelection, useStyles2 } from '@grafana/ui'; import { DashboardScene } from './DashboardScene'; +import { AddVariableButton } from './VariableControlsAddButton'; export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { const { variables } = sceneGraph.getVariables(dashboard)!.useState(); @@ -25,6 +27,7 @@ export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { .map((variable) => ( ))} + {config.featureToggles.dashboardNewLayouts ? : null} ); } diff --git a/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx new file mode 100644 index 00000000000..84e4f456578 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx @@ -0,0 +1,32 @@ +import { PointerEventHandler, useCallback } from 'react'; + +import { Trans } from '@grafana/i18n'; +import { Button } from '@grafana/ui'; + +import { openAddVariablePane } from '../settings/variables/VariableAddEditableElement'; +import { DashboardInteractions } from '../utils/interactions'; + +import { DashboardScene } from './DashboardScene'; + +export function AddVariableButton({ dashboard }: { dashboard: DashboardScene }) { + const { isEditing } = dashboard.useState(); + + const handlePointerDown: PointerEventHandler = useCallback( + (evt) => { + evt.stopPropagation(); + openAddVariablePane(dashboard); + DashboardInteractions.addVariableButtonClicked({ source: 'variable_controls' }); + }, + [dashboard] + ); + + if (!isEditing) { + return null; + } + + return ( + + ); +} diff --git a/public/app/features/dashboard-scene/settings/variables/VariableAddEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableAddEditableElement.tsx new file mode 100644 index 00000000000..f32c63a083a --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/VariableAddEditableElement.tsx @@ -0,0 +1,116 @@ +import { css } from '@emotion/css'; +import { useCallback, useId, useMemo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Trans, t } from '@grafana/i18n'; +import { sceneGraph, SceneObjectBase, SceneObjectRef, SceneObjectState, SceneVariableSet } from '@grafana/scenes'; +import { Box, Card, Stack, useStyles2 } from '@grafana/ui'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; + +import { dashboardEditActions } from '../../edit-pane/shared'; +import { DashboardScene } from '../../scene/DashboardScene'; +import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement'; + +import { EditableVariableType, getNextAvailableId, getVariableScene, getVariableTypeSelectOptions } from './utils'; + +export function openAddVariablePane(dashboard: DashboardScene) { + const element = new VariableAdd({ dashboardRef: dashboard.getRef() }); + dashboard.state.editPane.selectObject(element, element.state.key!, { force: true, multi: false }); +} + +export interface VariableAddState extends SceneObjectState { + dashboardRef: SceneObjectRef; +} + +export class VariableAdd extends SceneObjectBase {} + +function useEditPaneOptions( + this: VariableAddEditableElement, + variableAdd: VariableAdd +): OptionsPaneCategoryDescriptor[] { + const id = useId(); + const options = useMemo(() => { + return new OptionsPaneCategoryDescriptor({ title: '', id: 'variables' }).addItem( + new OptionsPaneItemDescriptor({ + title: '', + id, + skipField: true, + render: () => , + }) + ); + }, [variableAdd, id]); + + return [options]; +} + +export class VariableAddEditableElement implements EditableDashboardElement { + public readonly isEditableDashboardElement = true; + public readonly typeName = 'Variable'; + + public constructor(private variableAdd: VariableAdd) {} + + public getEditableElementInfo(): EditableDashboardElementInfo { + return { + typeName: t('dashboard.edit-pane.elements.variable-set', 'Variables'), + icon: 'x', + instanceName: t('dashboard.edit-pane.elements.variable-set', 'Variables'), + }; + } + + public useEditPaneOptions = useEditPaneOptions.bind(this, this.variableAdd); +} + +function VariableTypeSelection({ variableAdd }: { variableAdd: VariableAdd }) { + const options = useMemo(() => getVariableTypeSelectOptions(), []); + const styles = useStyles2(getStyles); + + const onAddVariable = useCallback( + (type: EditableVariableType) => { + const dashboard = variableAdd.state.dashboardRef.resolve(); + const variablesSet = sceneGraph.getVariables(dashboard); + + if (!(variablesSet instanceof SceneVariableSet)) { + return; + } + + const newVar = getVariableScene(type, { name: getNextAvailableId(type, variablesSet.state.variables ?? []) }); + dashboardEditActions.addVariable({ source: variablesSet, addedObject: newVar }); + dashboard.state.editPane.selectObject(newVar, newVar.state.key!, { force: true, multi: false }); + }, + [variableAdd] + ); + + return ( + + + Choose variable type + + + {options.map((option) => ( + onAddVariable(option.value!)} + key={option.value} + title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} + data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)} + > + {option.label} + {option.description} + + ))} + + + ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + cardDescription: css({ + fontSize: theme.typography.bodySmall.fontSize, + marginTop: theme.spacing(0), + }), + }; +} diff --git a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx index 0cefe5cec8a..462b7e7bf03 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx @@ -1,22 +1,20 @@ import { css } from '@emotion/css'; -import { useId, useMemo } from 'react'; -import { useToggle } from 'react-use'; +import { useCallback, useId, useMemo } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { SceneVariable, SceneVariableSet } from '@grafana/scenes'; -import { Box, Button, Card, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Box, Button, Stack, Text, useStyles2 } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; -import { dashboardEditActions } from '../../edit-pane/shared'; import { DashboardScene } from '../../scene/DashboardScene'; import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement'; import { DashboardInteractions } from '../../utils/interactions'; import { getDashboardSceneFor } from '../../utils/utils'; -import { EditableVariableType, getNextAvailableId, getVariableScene, getVariableTypeSelectOptions } from './utils'; +import { openAddVariablePane } from './VariableAddEditableElement'; function useEditPaneOptions(this: VariableSetEditableElement, set: SceneVariableSet): OptionsPaneCategoryDescriptor[] { const variableListId = useId(); @@ -33,6 +31,7 @@ function useEditPaneOptions(this: VariableSetEditableElement, set: SceneVariable return [options]; } + export class VariableSetEditableElement implements EditableDashboardElement { public readonly isEditableDashboardElement = true; public readonly typeName = 'Variable'; @@ -57,30 +56,20 @@ export class VariableSetEditableElement implements EditableDashboardElement { export function VariableList({ set }: { set: SceneVariableSet }) { const { variables } = set.useState(); const styles = useStyles2(getStyles); - const [isAdding, setIsAdding] = useToggle(false); const canAdd = set.parent instanceof DashboardScene; - const onEditVariable = (variable: SceneVariable) => { - const { editPane } = getDashboardSceneFor(set).state; - editPane.selectObject(variable, variable.state.key!); - }; + const onEditVariable = useCallback( + (variable: SceneVariable) => { + const { editPane } = getDashboardSceneFor(set).state; + editPane.selectObject(variable, variable.state.key!); + }, + [set] + ); - const onAddVariable = (type: EditableVariableType) => { - const { variables } = set.state; - const nextName = getNextAvailableId(type, variables); - const newVar = getVariableScene(type, { name: nextName }); - - dashboardEditActions.addVariable({ - source: set, - addedObject: newVar, - }); - - setIsAdding(false); - }; - - if (isAdding) { - return ; - } + const onAddVariable = useCallback(() => { + openAddVariablePane(getDashboardSceneFor(set)); + DashboardInteractions.addVariableButtonClicked({ source: 'edit_pane' }); + }, [set]); return ( @@ -103,10 +92,7 @@ export function VariableList({ set }: { set: SceneVariableSet }) { icon="plus" size="sm" variant="secondary" - onClick={() => { - DashboardInteractions.addVariableButtonClicked({ source: 'edit_pane' }); - setIsAdding(); - }} + onClick={onAddVariable} data-testid={selectors.components.PanelEditor.ElementEditPane.addVariableButton} > Add variable @@ -117,38 +103,6 @@ export function VariableList({ set }: { set: SceneVariableSet }) { ); } -interface VariableTypeSelectionProps { - onAddVariable: (type: EditableVariableType) => void; -} - -function VariableTypeSelection({ onAddVariable }: VariableTypeSelectionProps) { - const options = getVariableTypeSelectOptions(); - const styles = useStyles2(getStyles); - - return ( - - - Choose variable type - - - {options.map((option) => ( - onAddVariable(option.value!)} - key={option.value} - title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} - data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)} - > - {option.label} - {option.description} - - ))} - - - ); -} - function getStyles(theme: GrafanaTheme2) { return { variableItem: css({ @@ -177,9 +131,5 @@ function getStyles(theme: GrafanaTheme2) { }, }, }), - cardDescription: css({ - fontSize: theme.typography.bodySmall.fontSize, - marginTop: theme.spacing(0), - }), }; } diff --git a/public/app/features/dashboard-scene/utils/interactions.ts b/public/app/features/dashboard-scene/utils/interactions.ts index e95040ae2c8..701d9f37a99 100644 --- a/public/app/features/dashboard-scene/utils/interactions.ts +++ b/public/app/features/dashboard-scene/utils/interactions.ts @@ -69,7 +69,7 @@ export const DashboardInteractions = { // dashboards_add_variable_button_clicked // when a user clicks on ‘Add Variable’ or ‘New Variable’ - addVariableButtonClicked: (properties: { source: 'edit_pane' | 'settings_pane' }) => { + addVariableButtonClicked: (properties: { source: 'edit_pane' | 'settings_pane' | 'variable_controls' }) => { reportDashboardInteraction('add_variable_button_clicked', properties); }, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index aa1e0d71471..d85d332d402 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -6294,6 +6294,9 @@ "content-variable-not-referenced-other-variables-dashboard": "This variable is not referenced by other variables or dashboard.", "content-variable-referenced-other-variables-dashboard": "This variable is referenced by other variables or dashboard." }, + "variable-controls": { + "add-variable": "Add" + }, "variable-editor-form": { "aria-label-variable-editor-form": "Variable editor form", "back-to-list": "Back to list",