diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts new file mode 100644 index 00000000000..ce5465c1e33 --- /dev/null +++ b/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts @@ -0,0 +1,212 @@ +import { Locator } from '@playwright/test'; + +import { test, expect, DashboardPage, E2ESelectorGroups } from '@grafana/plugin-e2e'; + +import { flows } from './utils'; + +test.use({ + featureToggles: { + kubernetesDashboards: true, + dashboardNewLayouts: true, + dashboardUndoRedo: true, + groupByVariable: true, + }, +}); + +test.use({ + viewport: { width: 1920, height: 1080 }, +}); + +const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; +const DASHBOARD_NAME = 'Test variable output'; + +test.describe( + 'Dashboard edit - Custom variable', + { + tag: ['@dashboards'], + }, + () => { + let addButton: Locator | undefined; + let rows: Locator | undefined; + let valueInputs: Locator | undefined; + let labelInputs: Locator | undefined; + let deleteButtons: Locator | undefined; + + const getAddButton = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { + addButton = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.StaticOptionsEditor.addButton + ); + await expect(addButton).toBeVisible(); + }; + + const refetchItems = (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { + rows = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.StaticOptionsEditor.row + ); + + valueInputs = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.StaticOptionsEditor.valueInput + ); + + labelInputs = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.StaticOptionsEditor.labelInput + ); + + deleteButtons = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.StaticOptionsEditor.deleteButton + ); + }; + + const checkRows = async (length: number) => { + expect(await rows!.all()).toHaveLength(length); + }; + + const fillValue = async (text: string, index: number) => { + await valueInputs!.nth(index).fill(text); + }; + + const fillLabel = async (text: string, index: number) => { + await labelInputs!.nth(index).fill(text); + }; + + const fillLabelValue = async (value: string, label: string, index: number) => { + await fillValue(value, index); + await fillLabel(label, index); + }; + + const openModal = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.optionsOpenButton) + .click(); + + await getAddButton(dashboardPage, selectors); + + refetchItems(dashboardPage, selectors); + }; + + const closeModal = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.closeButton) + .click(); + }; + + const checkItems = async (items: Array<[string, string?]>) => { + for (let i = 0; i < items.length; i++) { + const [value, label] = items[i]; + await expect(valueInputs!.nth(i)).toHaveValue(value); + await expect(labelInputs!.nth(i)).toHaveValue(label ?? ''); + } + }; + + const checkPreview = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups, labels: string[]) => { + const previewOptions = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption + ); + + for (let i = 0; i < labels.length; i++) { + expect(await previewOptions.nth(i).textContent()).toBe(labels[i]); + } + }; + + const addItem = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups, value = '', label = '') => { + await addButton!.click(); + refetchItems(dashboardPage, selectors); + await fillLabelValue(value ?? '', label ?? '', (await rows!.all()).length - 1); + }; + + const removeItem = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups, index: number) => { + await deleteButtons!.nth(index).click(); + refetchItems(dashboardPage, selectors); + }; + + test.beforeEach(() => { + valueInputs = undefined; + labelInputs = undefined; + deleteButtons = undefined; + }); + + test('can add a new custom variable', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ uid: PAGE_UNDER_TEST }); + await expect(page.getByText(DASHBOARD_NAME)).toBeVisible(); + + // common steps to add a new variable + await flows.newEditPaneVariableClick(dashboardPage, selectors); + await flows.newEditPanelCommonVariableInputs(dashboardPage, selectors, { + type: 'custom', + name: 'foo', + label: 'Foo', + value: '', + }); + + await openModal(dashboardPage, selectors); + await checkRows(1); + await addItem(dashboardPage, selectors); + await checkRows(2); + await fillValue('first value', 0); + await fillLabelValue('second value', 'second label', 1); + await addItem(dashboardPage, selectors, 'third value', 'third label'); + await addItem(dashboardPage, selectors, 'fourth value', 'fourth value'); + await removeItem(dashboardPage, selectors, 2); + await checkRows(3); + await checkPreview(dashboardPage, selectors, ['first value', 'second label', 'fourth value']); + await closeModal(dashboardPage, selectors); + + // assert variable is visible and has the correct values + const variableLabel = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.SubMenu.submenuItemLabels('Foo') + ); + await expect(variableLabel).toBeVisible(); + await expect(variableLabel).toContainText('Foo'); + await expect( + dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('first value') + ) + ).toBeVisible(); + + // check that variable deletion works + await dashboardPage.getByGrafanaSelector(selectors.components.EditPaneHeader.deleteButton).click(); + await expect(variableLabel).toBeHidden(); + }); + + test('can edit a custom variable', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: PAGE_UNDER_TEST, + queryParams: new URLSearchParams({ orgId: '1', editview: 'variables' }), + }); + await expect(page.getByText(DASHBOARD_NAME)).toBeVisible(); + + // Create a custom variable in the dashboard settings page + await dashboardPage.getByGrafanaSelector(selectors.components.CallToActionCard.buttonV2('Add variable')).click(); + const typeSelect = dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2) + .locator('input'); + await typeSelect.fill('Custom'); + await typeSelect.press('Enter'); + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2) + .fill('foo'); + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2) + .fill('Foo'); + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput) + .fill('first value, second label : second value, fourth value : fourth value'); + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.General.applyButton) + .click(); + await dashboardPage + .getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.backToDashboardButton) + .click(); + + // Open the modal editor in the side pane + await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.Outline.section).click(); + await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.Outline.node('Variables')).click(); + await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.Outline.item('foo')).click(); + await openModal(dashboardPage, selectors); + + // Check the items + await checkItems([['first value'], ['second value', 'second label'], ['fourth value']]); + await checkPreview(dashboardPage, selectors, ['first value', 'second label', 'fourth value']); + }); + } +); diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-edit-variables.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-edit-variables.spec.ts index 54991dfd623..4b5ea9574f8 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboards-edit-variables.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboards-edit-variables.spec.ts @@ -20,46 +20,6 @@ test.describe( tag: ['@dashboards'], }, () => { - test('can add a new custom variable', async ({ gotoDashboardPage, selectors, page }) => { - const dashboardPage = await gotoDashboardPage({ uid: PAGE_UNDER_TEST }); - await expect(page.getByText(DASHBOARD_NAME)).toBeVisible(); - - const variable: Variable = { - type: 'custom', - name: 'foo', - label: 'Foo', - value: 'one,two,three', - }; - - // common steps to add a new variable - await flows.newEditPaneVariableClick(dashboardPage, selectors); - await flows.newEditPanelCommonVariableInputs(dashboardPage, selectors, variable); - - // set the custom variable value - const customValueInput = dashboardPage.getByGrafanaSelector( - selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput - ); - await customValueInput.fill(variable.value); - await customValueInput.blur(); - - // assert the dropdown for the variable is visible and has the correct values - const variableLabel = dashboardPage.getByGrafanaSelector( - selectors.pages.Dashboard.SubMenu.submenuItemLabels(variable.label!) - ); - await expect(variableLabel).toBeVisible(); - await expect(variableLabel).toContainText(variable.label!); - - const values = variable.value.split(','); - const firstValueLink = dashboardPage.getByGrafanaSelector( - selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(values[0]) - ); - await expect(firstValueLink).toBeVisible(); - - // check that variable deletion works - await dashboardPage.getByGrafanaSelector(selectors.components.EditPaneHeader.deleteButton).click(); - await expect(variableLabel).toBeHidden(); - }); - test('can add a new constant variable', async ({ gotoDashboardPage, selectors, page }) => { const dashboardPage = await gotoDashboardPage({ uid: PAGE_UNDER_TEST }); await expect(page.getByText(DASHBOARD_NAME)).toBeVisible(); diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1d3a1535b9b..cb9a72a644b 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2189,19 +2189,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/settings/variables/editors/QueryVariableEditor.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, - "public/app/features/dashboard-scene/settings/variables/utils.ts": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - }, - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "public/app/features/dashboard-scene/sharing/ShareButton/share-externally/EmailShare/ConfigEmailSharing/ConfigEmailSharing.tsx": { "no-restricted-syntax": { "count": 1 diff --git a/package.json b/package.json index d8fe5451d99..bce42eab31e 100644 --- a/package.json +++ b/package.json @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.39.3", - "@grafana/scenes-react": "6.39.3", + "@grafana/scenes": "6.39.4", + "@grafana/scenes-react": "6.39.4", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index b45357f05f7..7716ccc1436 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -500,24 +500,9 @@ export const versionedPages = { queryOptionsQueryInput: { '10.4.0': 'data-testid Variable editor Form Default Variable Query Editor textarea', }, - queryOptionsStaticOptionsRow: { - [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options row', - }, queryOptionsStaticOptionsToggle: { [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options toggle', }, - queryOptionsStaticOptionsLabelInput: { - [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Label input', - }, - queryOptionsStaticOptionsValueInput: { - [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Value input', - }, - queryOptionsStaticOptionsDeleteButton: { - [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Delete button', - }, - queryOptionsStaticOptionsAddButton: { - [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Add button', - }, queryOptionsStaticOptionsOrderDropdown: { [MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Order dropdown', }, @@ -559,6 +544,12 @@ export const versionedPages = { customValueInput: { [MIN_GRAFANA_VERSION]: 'data-testid custom-variable-input', }, + optionsOpenButton: { + [MIN_GRAFANA_VERSION]: 'data-testid custom-variable-options-open-button', + }, + closeButton: { + [MIN_GRAFANA_VERSION]: 'data-testid custom-variable-close-button', + }, }, IntervalVariable: { intervalsValueInput: { @@ -607,6 +598,26 @@ export const versionedPages = { ['12.3.0']: 'data-testid switch variable disabled value input', }, }, + StaticOptionsEditor: { + addButton: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Add button', + }, + labelInput: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Label input', + }, + valueInput: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Value input', + }, + moveButton: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Move button', + }, + deleteButton: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Delete button', + }, + row: { + [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Static Options Row', + }, + }, }, }, }, diff --git a/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx index f11037fb80c..b3c78330156 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx @@ -1,16 +1,11 @@ import { FormEvent } from 'react'; -import { lastValueFrom } from 'rxjs'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { CustomVariable, SceneVariable } from '@grafana/scenes'; -import { TextArea } from '@grafana/ui'; -import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; - -import { VariableLegend } from '../components/VariableLegend'; -import { VariableTextAreaField } from '../components/VariableTextAreaField'; import { SelectionOptionsForm } from './SelectionOptionsForm'; +import { VariableLegend } from './VariableLegend'; +import { VariableTextAreaField } from './VariableTextAreaField'; interface CustomVariableFormProps { query: string; @@ -71,41 +66,3 @@ export function CustomVariableForm({ ); } - -export function getCustomVariableOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] { - if (!(variable instanceof CustomVariable)) { - return []; - } - - return [ - new OptionsPaneItemDescriptor({ - title: t('dashboard.edit-pane.variable.custom-options.values', 'Values separated by comma'), - id: 'custom-variable-values', - render: (descriptor) => , - }), - ]; -} - -function ValuesTextField({ variable, id }: { variable: CustomVariable; id?: string }) { - const { query } = variable.useState(); - - const onBlur = async (event: FormEvent) => { - variable.setState({ query: event.currentTarget.value }); - await lastValueFrom(variable.validateAndUpdate!()); - }; - - return ( -