mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 15:32:27 +08:00
Dashboard edit panel e2e (#104775)
setup suite for edit panel; start adding test for custom variable edit
This commit is contained in:
@ -0,0 +1,60 @@
|
|||||||
|
import { e2e } from '../utils';
|
||||||
|
|
||||||
|
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
|
||||||
|
const DASHBOARD_NAME = 'Test variable output';
|
||||||
|
|
||||||
|
describe('Dashboard edit variables', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add a new custom variable', () => {
|
||||||
|
e2e.pages.Dashboards.visit();
|
||||||
|
|
||||||
|
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1` });
|
||||||
|
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||||
|
|
||||||
|
const variable: Variable = {
|
||||||
|
type: 'custom',
|
||||||
|
name: 'foo',
|
||||||
|
label: 'Foo',
|
||||||
|
value: 'one,two,three',
|
||||||
|
};
|
||||||
|
|
||||||
|
// common steps to add a new variable
|
||||||
|
flows.newEditPaneVariableClick();
|
||||||
|
flows.newEditPanelCommonVariableInputs(variable);
|
||||||
|
|
||||||
|
// set the custom variable value
|
||||||
|
e2e.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput().clear().type(variable.value).blur();
|
||||||
|
|
||||||
|
// assert the dropdown for the variable is visible and has the correct values
|
||||||
|
e2e.pages.Dashboard.SubMenu.submenuItemLabels(variable.label).should('be.visible').contains(variable.label);
|
||||||
|
const values = variable.value.split(',');
|
||||||
|
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(values[0]).should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Common flows for adding/editing variables
|
||||||
|
// TODO: maybe move to e2e flows
|
||||||
|
const flows = {
|
||||||
|
newEditPaneVariableClick() {
|
||||||
|
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
|
||||||
|
e2e.components.PanelEditor.Outline.section().should('be.visible').click();
|
||||||
|
e2e.components.PanelEditor.Outline.item('Variables').should('be.visible').click();
|
||||||
|
e2e.components.PanelEditor.ElementEditPane.addVariableButton().should('be.visible').click();
|
||||||
|
},
|
||||||
|
newEditPanelCommonVariableInputs(variable: Variable) {
|
||||||
|
e2e.components.PanelEditor.ElementEditPane.variableType(variable.type).should('be.visible').click();
|
||||||
|
e2e.components.PanelEditor.ElementEditPane.variableNameInput().clear().type(variable.name).blur();
|
||||||
|
e2e.components.PanelEditor.ElementEditPane.variableLabelInput().clear().type(variable.label).blur();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Variable = {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
value: string;
|
||||||
|
};
|
@ -30,6 +30,7 @@ rootForEnterpriseSuite="./e2e/extensions-suite"
|
|||||||
rootForOldArch="./e2e/old-arch"
|
rootForOldArch="./e2e/old-arch"
|
||||||
rootForKubernetesDashboards="./e2e/dashboards-suite"
|
rootForKubernetesDashboards="./e2e/dashboards-suite"
|
||||||
rootForSearchDashboards="./e2e/dashboards-search-suite"
|
rootForSearchDashboards="./e2e/dashboards-search-suite"
|
||||||
|
rootForEditDashboards="./e2e/dashboards-edit-v2-suite"
|
||||||
|
|
||||||
declare -A cypressConfig=(
|
declare -A cypressConfig=(
|
||||||
[screenshotsFolder]=./e2e/"${args[0]}"/screenshots
|
[screenshotsFolder]=./e2e/"${args[0]}"/screenshots
|
||||||
@ -149,6 +150,25 @@ case "$1" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
"dashboards-edit-v2")
|
||||||
|
env[kubernetesDashboards]=true
|
||||||
|
env[dashboardNewLayouts]=true
|
||||||
|
cypressConfig[specPattern]=$rootForEditDashboards/$testFilesForSingleSuite
|
||||||
|
cypressConfig[video]=false
|
||||||
|
case "$2" in
|
||||||
|
"debug")
|
||||||
|
echo -e "Debug mode"
|
||||||
|
env[SLOWMO]=1
|
||||||
|
PARAMS="--no-exit"
|
||||||
|
enterpriseSuite=$(basename "${args[2]}")
|
||||||
|
;;
|
||||||
|
"dev")
|
||||||
|
echo "Dev mode"
|
||||||
|
CMD="cypress open"
|
||||||
|
enterpriseSuite=$(basename "${args[2]}")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
"enterprise-smtp")
|
"enterprise-smtp")
|
||||||
env[SMTP_PLUGIN_ENABLED]=true
|
env[SMTP_PLUGIN_ENABLED]=true
|
||||||
cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite
|
cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
"e2e:old-arch": "./e2e/start-and-run-suite old-arch",
|
"e2e:old-arch": "./e2e/start-and-run-suite old-arch",
|
||||||
"e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2",
|
"e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2",
|
||||||
"e2e:dashboards-search": "./e2e/start-and-run-suite dashboards-search",
|
"e2e:dashboards-search": "./e2e/start-and-run-suite dashboards-search",
|
||||||
|
"e2e:dashboards-edit-v2": "./e2e/start-and-run-suite dashboards-edit-v2",
|
||||||
|
"e2e:dashboards-edit-v2:dev": "./e2e/start-and-run-suite dashboards-edit-v2 dev",
|
||||||
"e2e:debug": "./e2e/start-and-run-suite debug",
|
"e2e:debug": "./e2e/start-and-run-suite debug",
|
||||||
"e2e:dev": "./e2e/start-and-run-suite dev",
|
"e2e:dev": "./e2e/start-and-run-suite dev",
|
||||||
"e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live",
|
"e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live",
|
||||||
|
@ -532,6 +532,33 @@ export const versionedComponents = {
|
|||||||
measureButton: {
|
measureButton: {
|
||||||
'9.2.0': 'show measure tools',
|
'9.2.0': 'show measure tools',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Outline: {
|
||||||
|
section: {
|
||||||
|
'12.0.0': 'data-testid Outline section',
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
'12.0.0': (type: string) => `data-testid outline node ${type}`,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
'12.0.0': (type: string) => `data-testid outline item ${type}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ElementEditPane: {
|
||||||
|
variableType: {
|
||||||
|
'12.0.0': (type?: string) => `data-testid variable type ${type}`,
|
||||||
|
},
|
||||||
|
addVariableButton: {
|
||||||
|
'12.0.0': 'data-testid add variable button',
|
||||||
|
},
|
||||||
|
variableNameInput: {
|
||||||
|
'12.0.0': 'data-testid variable name input',
|
||||||
|
},
|
||||||
|
variableLabelInput: {
|
||||||
|
'12.0.0': 'data-testid variable label input',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
PanelInspector: {
|
PanelInspector: {
|
||||||
Data: {
|
Data: {
|
||||||
|
@ -3,6 +3,7 @@ import { Resizable } from 're-resizable';
|
|||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes';
|
import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes';
|
||||||
import {
|
import {
|
||||||
ElementSelectionContextItem,
|
ElementSelectionContextItem,
|
||||||
@ -272,11 +273,12 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
|
|||||||
role="button"
|
role="button"
|
||||||
onClick={() => setOutlineCollapsed(!outlineCollapsed)}
|
onClick={() => setOutlineCollapsed(!outlineCollapsed)}
|
||||||
className={styles.outlineCollapseButton}
|
className={styles.outlineCollapseButton}
|
||||||
|
data-testid={selectors.components.PanelEditor.Outline.section}
|
||||||
>
|
>
|
||||||
<Text weight="medium">
|
<Text weight="medium">
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans>
|
<Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Icon name="angle-up" />
|
<Icon name={outlineCollapsed ? 'angle-up' : 'angle-down'} />
|
||||||
</div>
|
</div>
|
||||||
{!outlineCollapsed && (
|
{!outlineCollapsed && (
|
||||||
<div className={styles.outlineContainer}>
|
<div className={styles.outlineContainer}>
|
||||||
|
@ -3,6 +3,7 @@ import { sortBy } from 'lodash';
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { SceneObject } from '@grafana/scenes';
|
import { SceneObject } from '@grafana/scenes';
|
||||||
import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui';
|
import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
@ -88,7 +89,12 @@ function DashboardOutlineNode({
|
|||||||
onPointerDown={onNodeClicked}
|
onPointerDown={onNodeClicked}
|
||||||
>
|
>
|
||||||
{elementInfo.isContainer && (
|
{elementInfo.isContainer && (
|
||||||
<button role="treeitem" className={styles.angleButton} onPointerDown={onToggleCollapse}>
|
<button
|
||||||
|
role="treeitem"
|
||||||
|
className={styles.angleButton}
|
||||||
|
onPointerDown={onToggleCollapse}
|
||||||
|
data-testid={selectors.components.PanelEditor.Outline.node(instanceName)}
|
||||||
|
>
|
||||||
<Icon name={!isCollapsed ? 'angle-down' : 'angle-right'} />
|
<Icon name={!isCollapsed ? 'angle-down' : 'angle-right'} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -96,6 +102,7 @@ function DashboardOutlineNode({
|
|||||||
role="button"
|
role="button"
|
||||||
className={cx(styles.nodeName, isCloned && styles.nodeNameClone)}
|
className={cx(styles.nodeName, isCloned && styles.nodeNameClone)}
|
||||||
onDoubleClick={outlineRename.onNameDoubleClicked}
|
onDoubleClick={outlineRename.onNameDoubleClicked}
|
||||||
|
data-testid={selectors.components.PanelEditor.Outline.item(instanceName)}
|
||||||
>
|
>
|
||||||
<Icon size="sm" name={elementInfo.icon} />
|
<Icon size="sm" name={elementInfo.icon} />
|
||||||
{outlineRename.isRenaming ? (
|
{outlineRename.isRenaming ? (
|
||||||
|
@ -11,7 +11,7 @@ interface Props {
|
|||||||
checkedIcon?: IconName;
|
checkedIcon?: IconName;
|
||||||
checkedLabel?: string;
|
checkedLabel?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
'data-testId'?: string;
|
'data-testid'?: string;
|
||||||
onClick: (evt: MouseEvent<HTMLDivElement>) => void;
|
onClick: (evt: MouseEvent<HTMLDivElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ export const ToolbarSwitch = ({
|
|||||||
checkedLabel,
|
checkedLabel,
|
||||||
disabled,
|
disabled,
|
||||||
onClick,
|
onClick,
|
||||||
'data-testId': dataTestId,
|
'data-testid': dataTestId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FormEvent, useMemo, useState } from 'react';
|
import { FormEvent, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { VariableHide } from '@grafana/data';
|
import { VariableHide } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes';
|
import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes';
|
||||||
import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui';
|
import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui';
|
||||||
@ -143,14 +144,27 @@ function VariableNameInput({ variable, isNewElement }: { variable: SceneVariable
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Field label={t('dashboard.edit-pane.variable.name', 'Name')} invalid={!!nameError} error={nameError}>
|
<Field label={t('dashboard.edit-pane.variable.name', 'Name')} invalid={!!nameError} error={nameError}>
|
||||||
<Input ref={ref} value={name} onChange={onChange} required onBlur={onBlur} />
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
value={name}
|
||||||
|
onChange={onChange}
|
||||||
|
required
|
||||||
|
onBlur={onBlur}
|
||||||
|
data-testid={selectors.components.PanelEditor.ElementEditPane.variableNameInput}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function VariableLabelInput({ variable }: VariableInputProps) {
|
function VariableLabelInput({ variable }: VariableInputProps) {
|
||||||
const { label } = variable.useState();
|
const { label } = variable.useState();
|
||||||
return <Input value={label} onChange={(e) => variable.setState({ label: e.currentTarget.value })} />;
|
return (
|
||||||
|
<Input
|
||||||
|
value={label}
|
||||||
|
onChange={(e) => variable.setState({ label: e.currentTarget.value })}
|
||||||
|
data-testid={selectors.components.PanelEditor.ElementEditPane.variableLabelInput}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function VariableDescriptionTextArea({ variable }: VariableInputProps) {
|
function VariableDescriptionTextArea({ variable }: VariableInputProps) {
|
||||||
|
@ -3,6 +3,7 @@ import { useMemo } from 'react';
|
|||||||
import { useToggle } from 'react-use';
|
import { useToggle } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { SceneVariable, SceneVariableSet } from '@grafana/scenes';
|
import { SceneVariable, SceneVariableSet } from '@grafana/scenes';
|
||||||
import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui';
|
import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
@ -87,7 +88,14 @@ function VariableList({ set }: { set: SceneVariableSet }) {
|
|||||||
))}
|
))}
|
||||||
{canAdd && (
|
{canAdd && (
|
||||||
<Box paddingBottom={1} display={'flex'}>
|
<Box paddingBottom={1} display={'flex'}>
|
||||||
<Button fullWidth icon="plus" size="sm" variant="secondary" onClick={setIsAdding}>
|
<Button
|
||||||
|
fullWidth
|
||||||
|
icon="plus"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={setIsAdding}
|
||||||
|
data-testid={selectors.components.PanelEditor.ElementEditPane.addVariableButton}
|
||||||
|
>
|
||||||
<Trans i18nKey="dashboard.edit-pane.variables.add-variable">Add variable</Trans>
|
<Trans i18nKey="dashboard.edit-pane.variables.add-variable">Add variable</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@ -115,6 +123,7 @@ function VariableTypeSelection({ onAddVariable }: VariableTypeSelectionProps) {
|
|||||||
onClick={() => onAddVariable(option.value!)}
|
onClick={() => onAddVariable(option.value!)}
|
||||||
key={option.value}
|
key={option.value}
|
||||||
title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')}
|
title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')}
|
||||||
|
data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)}
|
||||||
>
|
>
|
||||||
<Card.Heading>{option.label}</Card.Heading>
|
<Card.Heading>{option.label}</Card.Heading>
|
||||||
<Card.Description className={styles.cardDescription}>{option.description}</Card.Description>
|
<Card.Description className={styles.cardDescription}>{option.description}</Card.Description>
|
||||||
|
Reference in New Issue
Block a user