Dashboard group by variable edit pane (#106104)
* Dashboards: Group By Variable in edit pane
This commit is contained in:
+1
-2
@@ -1749,8 +1749,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/variables/components/GroupByVariableForm.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/variables/components/QueryVariableForm.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
|
||||
+24
@@ -45,6 +45,7 @@ describe('GroupByVariableForm', () => {
|
||||
onAllowCustomValueChange: onAllowCustomValueChangeMock,
|
||||
onDataSourceChange: onDataSourceChangeMock,
|
||||
onDefaultOptionsChange: onDefaultOptionsChangeMock,
|
||||
datasourceSupported: true,
|
||||
};
|
||||
|
||||
function setup(props?: Partial<GroupByVariableFormProps>) {
|
||||
@@ -130,4 +131,27 @@ describe('GroupByVariableForm', () => {
|
||||
expect(onDefaultOptionsChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onDefaultOptionsChangeMock).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('should render only datasource picker and alert when not supported', async () => {
|
||||
const mockOnAllowCustomValueChange = jest.fn();
|
||||
const { renderer } = await setup({
|
||||
...defaultProps,
|
||||
datasourceSupported: false,
|
||||
onAllowCustomValueChange: mockOnAllowCustomValueChange,
|
||||
});
|
||||
|
||||
const dataSourcePicker = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.dataSourceSelect
|
||||
);
|
||||
|
||||
const allowCustomValueCheckbox = renderer.queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
const alertText = renderer.getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.infoText);
|
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(allowCustomValueCheckbox).not.toBeInTheDocument();
|
||||
expect(alertText).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+76
-56
@@ -3,8 +3,9 @@ import { FormEvent, useCallback } from 'react';
|
||||
import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, useTranslate } from '@grafana/i18n';
|
||||
import { EditorField } from '@grafana/plugin-ui';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { Alert, CodeEditor, Field, Switch } from '@grafana/ui';
|
||||
import { Alert, Box, CodeEditor, Field, Switch } from '@grafana/ui';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
|
||||
import { VariableCheckboxField } from './VariableCheckboxField';
|
||||
@@ -18,6 +19,8 @@ export interface GroupByVariableFormProps {
|
||||
defaultOptions?: MetricFindValue[];
|
||||
allowCustomValue: boolean;
|
||||
onAllowCustomValueChange: (event: FormEvent<HTMLInputElement>) => void;
|
||||
inline?: boolean;
|
||||
datasourceSupported: boolean;
|
||||
}
|
||||
|
||||
export function GroupByVariableForm({
|
||||
@@ -28,6 +31,8 @@ export function GroupByVariableForm({
|
||||
onDefaultOptionsChange,
|
||||
allowCustomValue,
|
||||
onAllowCustomValueChange,
|
||||
inline,
|
||||
datasourceSupported,
|
||||
}: GroupByVariableFormProps) {
|
||||
const updateDefaultOptions = useCallback(
|
||||
(csvContent: string) => {
|
||||
@@ -45,70 +50,85 @@ export function GroupByVariableForm({
|
||||
|
||||
return (
|
||||
<>
|
||||
<VariableLegend>
|
||||
<Trans i18nKey="dashboard-scene.group-by-variable-form.group-by-options">Group by options</Trans>
|
||||
</VariableLegend>
|
||||
<Field
|
||||
label={t('dashboard-scene.group-by-variable-form.label-data-source', 'Data source')}
|
||||
htmlFor="data-source-picker"
|
||||
>
|
||||
<DataSourcePicker current={datasource} onChange={onDataSourceChange} width={30} variables={true} noDefault />
|
||||
</Field>
|
||||
{!inline && (
|
||||
<VariableLegend>
|
||||
<Trans i18nKey="dashboard-scene.group-by-variable-form.group-by-options">Group by options</Trans>
|
||||
</VariableLegend>
|
||||
)}
|
||||
|
||||
{infoText ? (
|
||||
<Box marginBottom={2}>
|
||||
<EditorField
|
||||
label={t('dashboard-scene.group-by-variable-form.label-data-source', 'Data source')}
|
||||
htmlFor="data-source-picker"
|
||||
tooltip={infoText}
|
||||
>
|
||||
<DataSourcePicker current={datasource} onChange={onDataSourceChange} width={30} variables={true} noDefault />
|
||||
</EditorField>
|
||||
</Box>
|
||||
|
||||
{!datasourceSupported ? (
|
||||
<Alert
|
||||
title={infoText}
|
||||
severity="info"
|
||||
title={t(
|
||||
'dashboard-scene.group-by-variable-form.alert-not-supported',
|
||||
'This data source does not support group by variables'
|
||||
)}
|
||||
severity="warning"
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.infoText}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Field
|
||||
label={t(
|
||||
'dashboard-scene.group-by-variable-form.label-use-static-group-by-dimensions',
|
||||
'Use static group dimensions'
|
||||
)}
|
||||
description={t(
|
||||
'dashboard-scene.group-by-variable-form.description-provide-dimensions-as-csv-dimension-name-dimension-id',
|
||||
'Provide dimensions as CSV: {{name}}, {{value}}',
|
||||
{ name: 'dimensionName', value: 'dimensionId' }
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle}
|
||||
value={defaultOptions !== undefined}
|
||||
onChange={(e) => {
|
||||
if (defaultOptions === undefined) {
|
||||
onDefaultOptionsChange([]);
|
||||
} else {
|
||||
onDefaultOptionsChange(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
{datasourceSupported && (
|
||||
<>
|
||||
<Field
|
||||
label={t(
|
||||
'dashboard-scene.group-by-variable-form.label-use-static-group-by-dimensions',
|
||||
'Use static group dimensions'
|
||||
)}
|
||||
description={t(
|
||||
'dashboard-scene.group-by-variable-form.description-provide-dimensions-as-csv-dimension-name-dimension-id',
|
||||
'Provide dimensions as CSV: {{name}}, {{value}}',
|
||||
{ name: 'dimensionName', value: 'dimensionId' }
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle}
|
||||
value={defaultOptions !== undefined}
|
||||
onChange={(e) => {
|
||||
if (defaultOptions === undefined) {
|
||||
onDefaultOptionsChange([]);
|
||||
} else {
|
||||
onDefaultOptionsChange(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{defaultOptions !== undefined && (
|
||||
<CodeEditor
|
||||
height={300}
|
||||
language="csv"
|
||||
value={defaultOptions.map((o) => `${o.text},${o.value}`).join('\n')}
|
||||
onBlur={updateDefaultOptions}
|
||||
onSave={updateDefaultOptions}
|
||||
showMiniMap={false}
|
||||
showLineNumbers={true}
|
||||
/>
|
||||
{defaultOptions !== undefined && (
|
||||
<CodeEditor
|
||||
height={300}
|
||||
language="csv"
|
||||
value={defaultOptions.map((o) => `${o.text},${o.value}`).join('\n')}
|
||||
onBlur={updateDefaultOptions}
|
||||
onSave={updateDefaultOptions}
|
||||
showMiniMap={false}
|
||||
showLineNumbers={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<VariableCheckboxField
|
||||
value={allowCustomValue}
|
||||
name="Allow custom values"
|
||||
description={t(
|
||||
'dashboard-scene.group-by-variable-form.description-enables-users-custom-values',
|
||||
'Enables users to add custom values to the list'
|
||||
)}
|
||||
onChange={onAllowCustomValueChange}
|
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch}
|
||||
/>
|
||||
{datasourceSupported && !inline && onAllowCustomValueChange && (
|
||||
<VariableCheckboxField
|
||||
value={allowCustomValue}
|
||||
name="Allow custom values"
|
||||
description={t(
|
||||
'dashboard-scene.group-by-variable-form.description-enables-users-custom-values',
|
||||
'Enables users to add custom values to the list'
|
||||
)}
|
||||
onChange={onAllowCustomValueChange}
|
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+31
-6
@@ -1,13 +1,14 @@
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { act, render, waitFor, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { MetricFindValue, VariableSupportType } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { GroupByVariable } from '@grafana/scenes';
|
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
|
||||
|
||||
import { GroupByVariableEditor } from './GroupByVariableEditor';
|
||||
import { getGroupByVariableOptions, GroupByVariableEditor } from './GroupByVariableEditor';
|
||||
|
||||
const defaultDatasource = mockDataSource({
|
||||
name: 'Default Test Data Source',
|
||||
@@ -31,6 +32,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
query: jest.fn(),
|
||||
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor),
|
||||
},
|
||||
getTagKeys: () => [],
|
||||
}),
|
||||
getList: () => [defaultDatasource, promDatasource],
|
||||
getInstanceSettings: () => ({ ...defaultDatasource }),
|
||||
@@ -43,8 +45,8 @@ describe('GroupByVariableEditor', () => {
|
||||
const dataSourcePicker = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.dataSourceSelect
|
||||
);
|
||||
const infoText = renderer.getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.infoText);
|
||||
const allowCustomValueCheckbox = renderer.getByTestId(
|
||||
|
||||
const allowCustomValueCheckbox = renderer.queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
@@ -52,8 +54,6 @@ describe('GroupByVariableEditor', () => {
|
||||
expect(allowCustomValueCheckbox).toBeChecked();
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(infoText).toBeInTheDocument();
|
||||
expect(infoText).toHaveTextContent('This data source does not support group by variable yet.');
|
||||
});
|
||||
|
||||
it('should update the variable data source when data source picker is changed', async () => {
|
||||
@@ -87,6 +87,31 @@ describe('GroupByVariableEditor', () => {
|
||||
|
||||
expect(variable.state.defaultOptions).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return an OptionsPaneItemDescriptor that renders Editor', async () => {
|
||||
const variable = new GroupByVariable({
|
||||
name: 'test',
|
||||
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type },
|
||||
});
|
||||
|
||||
const result = getGroupByVariableOptions(variable);
|
||||
|
||||
expect(result.length).toBe(1);
|
||||
const descriptor = result[0];
|
||||
|
||||
// Mock the parent property that OptionsPaneItem expects
|
||||
descriptor.parent = new OptionsPaneCategoryDescriptor({
|
||||
id: 'mock-parent-id',
|
||||
title: 'Mock Parent',
|
||||
});
|
||||
|
||||
render(descriptor.render());
|
||||
|
||||
await waitFor(() => {
|
||||
// Check that some part of the component renders
|
||||
expect(screen.getByText(/data source does not support/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function setup(defaultOptions?: MetricFindValue[]) {
|
||||
|
||||
+22
-3
@@ -1,26 +1,30 @@
|
||||
import { noop } from 'lodash';
|
||||
import { FormEvent } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourceInstanceSettings, MetricFindValue, getDataSourceRef } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { GroupByVariable } from '@grafana/scenes';
|
||||
import { GroupByVariable, SceneVariable } from '@grafana/scenes';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { GroupByVariableForm } from '../components/GroupByVariableForm';
|
||||
|
||||
interface GroupByVariableEditorProps {
|
||||
variable: GroupByVariable;
|
||||
onRunQuery: () => void;
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
export function GroupByVariableEditor(props: GroupByVariableEditorProps) {
|
||||
const { variable, onRunQuery } = props;
|
||||
const { variable, onRunQuery, inline } = props;
|
||||
const { datasource: datasourceRef, defaultOptions, allowCustomValue = true } = variable.useState();
|
||||
|
||||
const { value: datasource } = useAsync(async () => {
|
||||
return await getDataSourceSrv().get(datasourceRef);
|
||||
}, [variable.state]);
|
||||
|
||||
const message = datasource?.getTagKeys
|
||||
const supported = datasource?.getTagKeys !== undefined;
|
||||
const message = supported
|
||||
? 'Group by dimensions are applied automatically to all queries that target this data source'
|
||||
: 'This data source does not support group by variable yet.';
|
||||
|
||||
@@ -49,6 +53,21 @@ export function GroupByVariableEditor(props: GroupByVariableEditorProps) {
|
||||
onDefaultOptionsChange={onDefaultOptionsChange}
|
||||
allowCustomValue={allowCustomValue}
|
||||
onAllowCustomValueChange={onAllowCustomValueChange}
|
||||
inline={inline}
|
||||
datasourceSupported={supported}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function getGroupByVariableOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] {
|
||||
if (!(variable instanceof GroupByVariable)) {
|
||||
console.warn('getAdHocFilterOptions: variable is not an AdHocFiltersVariable');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
new OptionsPaneItemDescriptor({
|
||||
render: () => <GroupByVariableEditor variable={variable} onRunQuery={noop} inline={true} />,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { AdHocFiltersVariableEditor, getAdHocFilterOptions } from './editors/AdH
|
||||
import { ConstantVariableEditor, getConstantVariableOptions } from './editors/ConstantVariableEditor';
|
||||
import { CustomVariableEditor, getCustomVariableOptions } from './editors/CustomVariableEditor';
|
||||
import { DataSourceVariableEditor, getDataSourceVariableOptions } from './editors/DataSourceVariableEditor';
|
||||
import { GroupByVariableEditor } from './editors/GroupByVariableEditor';
|
||||
import { getGroupByVariableOptions, GroupByVariableEditor } from './editors/GroupByVariableEditor';
|
||||
import { getIntervalVariableOptions, IntervalVariableEditor } from './editors/IntervalVariableEditor';
|
||||
import { getQueryVariableOptions, QueryVariableEditor } from './editors/QueryVariableEditor';
|
||||
import { TextBoxVariableEditor, getTextBoxVariableOptions } from './editors/TextBoxVariableEditor';
|
||||
@@ -87,6 +87,7 @@ export const EDITABLE_VARIABLES: Record<EditableVariableType, EditableVariableCo
|
||||
name: 'Group by',
|
||||
description: 'Add keys to group by on the fly',
|
||||
editor: GroupByVariableEditor,
|
||||
getOptions: getGroupByVariableOptions,
|
||||
},
|
||||
textbox: {
|
||||
name: 'Textbox',
|
||||
|
||||
@@ -4964,6 +4964,7 @@
|
||||
}
|
||||
},
|
||||
"group-by-variable-form": {
|
||||
"alert-not-supported": "This data source does not support group by variables",
|
||||
"description-enables-users-custom-values": "Enables users to add custom values to the list",
|
||||
"description-provide-dimensions-as-csv-dimension-name-dimension-id": "Provide dimensions as CSV: {{name}}, {{value}}",
|
||||
"group-by-options": "Group by options",
|
||||
|
||||
Reference in New Issue
Block a user