Dashboards: Edit Pane - Ad Hoc Filter Variables (#105304)
Dashboards: Edit Pane - Ad Hoc Filter Variables
This commit is contained in:
+32
-6
@@ -34,6 +34,7 @@ describe('AdHocVariableForm', () => {
|
||||
datasource: defaultDatasource,
|
||||
onDataSourceChange,
|
||||
infoText: 'Test Info',
|
||||
datasourceSupported: true,
|
||||
};
|
||||
|
||||
it('should render the form with the provided data source', async () => {
|
||||
@@ -42,14 +43,9 @@ describe('AdHocVariableForm', () => {
|
||||
const dataSourcePicker = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect
|
||||
);
|
||||
const infoText = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText
|
||||
);
|
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(infoText).toBeInTheDocument();
|
||||
expect(infoText).toHaveTextContent('Test Info');
|
||||
});
|
||||
|
||||
it('should call the onDataSourceChange callback when the data source is changed', async () => {
|
||||
@@ -116,6 +112,7 @@ describe('AdHocVariableForm', () => {
|
||||
...defaultProps,
|
||||
defaultKeys: [{ text: 'test', value: 'test' }],
|
||||
onDefaultKeysChange: mockOnStaticKeysChange,
|
||||
datasourceSupported: true,
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
@@ -124,11 +121,40 @@ describe('AdHocVariableForm', () => {
|
||||
expect(mockOnStaticKeysChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnStaticKeysChange).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.AdHocFiltersVariable.datasourceSelect
|
||||
);
|
||||
|
||||
const allowCustomValueCheckbox = renderer.queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
const alertText = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText
|
||||
);
|
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(allowCustomValueCheckbox).not.toBeInTheDocument();
|
||||
expect(alertText).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
async function setup(props?: React.ComponentProps<typeof AdHocVariableForm>) {
|
||||
return {
|
||||
renderer: await act(() => render(<AdHocVariableForm onDataSourceChange={jest.fn()} {...props} />)),
|
||||
renderer: await act(() =>
|
||||
render(
|
||||
<AdHocVariableForm onDataSourceChange={jest.fn()} datasourceSupported={props!.datasourceSupported} {...props} />
|
||||
)
|
||||
),
|
||||
user: userEvent.setup(),
|
||||
};
|
||||
}
|
||||
|
||||
+29
-15
@@ -2,8 +2,9 @@ import { FormEvent, useCallback } from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { EditorField } from '@grafana/plugin-ui';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { Alert, CodeEditor, Field, Switch } from '@grafana/ui';
|
||||
import { Alert, CodeEditor, Field, Switch, Box } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
|
||||
@@ -18,6 +19,8 @@ export interface AdHocVariableFormProps {
|
||||
defaultKeys?: MetricFindValue[];
|
||||
onDefaultKeysChange?: (keys?: MetricFindValue[]) => void;
|
||||
onAllowCustomValueChange?: (event: FormEvent<HTMLInputElement>) => void;
|
||||
inline?: boolean;
|
||||
datasourceSupported: boolean;
|
||||
}
|
||||
|
||||
export function AdHocVariableForm({
|
||||
@@ -28,6 +31,8 @@ export function AdHocVariableForm({
|
||||
onDefaultKeysChange,
|
||||
onAllowCustomValueChange,
|
||||
defaultKeys,
|
||||
inline,
|
||||
datasourceSupported,
|
||||
}: AdHocVariableFormProps) {
|
||||
const updateStaticKeys = useCallback(
|
||||
(csvContent: string) => {
|
||||
@@ -44,25 +49,34 @@ export function AdHocVariableForm({
|
||||
|
||||
return (
|
||||
<>
|
||||
<VariableLegend>
|
||||
<Trans i18nKey="dashboard-scene.ad-hoc-variable-form.adhoc-options">Ad-hoc options</Trans>
|
||||
</VariableLegend>
|
||||
<Field
|
||||
label={t('dashboard-scene.ad-hoc-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.ad-hoc-variable-form.adhoc-options">Ad-hoc options</Trans>
|
||||
</VariableLegend>
|
||||
)}
|
||||
|
||||
{infoText ? (
|
||||
<Box marginBottom={2}>
|
||||
<EditorField
|
||||
label={t('dashboard-scene.ad-hoc-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 === false ? (
|
||||
<Alert
|
||||
title={infoText}
|
||||
severity="info"
|
||||
title={t(
|
||||
'dashboard-scene.ad-hoc-variable-form.alert-not-supported',
|
||||
'This data source does not support ad hoc filters'
|
||||
)}
|
||||
severity="warning"
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{onDefaultKeysChange && (
|
||||
{datasourceSupported && onDefaultKeysChange && (
|
||||
<>
|
||||
<Field
|
||||
label={t(
|
||||
@@ -102,7 +116,7 @@ export function AdHocVariableForm({
|
||||
</>
|
||||
)}
|
||||
|
||||
{onAllowCustomValueChange && (
|
||||
{datasourceSupported && onAllowCustomValueChange && (
|
||||
<VariableCheckboxField
|
||||
value={allowCustomValue ?? true}
|
||||
name="Allow custom values"
|
||||
|
||||
+41
-7
@@ -1,4 +1,4 @@
|
||||
import { render, act } from '@testing-library/react';
|
||||
import { render, act, waitFor, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
@@ -15,9 +15,10 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { setRunRequest } from '@grafana/runtime';
|
||||
import { AdHocFiltersVariable } 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 { AdHocFiltersVariableEditor } from './AdHocFiltersVariableEditor';
|
||||
import { AdHocFiltersVariableEditor, getAdHocFilterOptions } from './AdHocFiltersVariableEditor';
|
||||
|
||||
const defaultDatasource = mockDataSource({
|
||||
name: 'Default Test Data Source',
|
||||
@@ -31,6 +32,8 @@ const promDatasource = mockDataSource({
|
||||
type: 'prometheus',
|
||||
});
|
||||
|
||||
let getTagKeysMock: Function | undefined = () => [];
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => ({
|
||||
@@ -41,6 +44,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
query: jest.fn(),
|
||||
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor),
|
||||
},
|
||||
getTagKeys: getTagKeysMock,
|
||||
}),
|
||||
getList: () => [defaultDatasource, promDatasource],
|
||||
getInstanceSettings: () => ({ ...defaultDatasource }),
|
||||
@@ -62,24 +66,28 @@ const runRequestMock = jest.fn().mockReturnValue(
|
||||
setRunRequest(runRequestMock);
|
||||
|
||||
describe('AdHocFiltersVariableEditor', () => {
|
||||
beforeEach(() => {
|
||||
getTagKeysMock = () => [];
|
||||
});
|
||||
|
||||
it('renders AdHocVariableForm with correct props', async () => {
|
||||
getTagKeysMock = undefined;
|
||||
|
||||
const { renderer } = await setup();
|
||||
const dataSourcePicker = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect
|
||||
);
|
||||
const infoText = renderer.getByTestId(
|
||||
const infoText = renderer.queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText
|
||||
);
|
||||
const allowCustomValueCheckbox = renderer.getByTestId(
|
||||
const allowCustomValueCheckbox = renderer.queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
expect(allowCustomValueCheckbox).toBeInTheDocument();
|
||||
expect(allowCustomValueCheckbox).toBeChecked();
|
||||
expect(allowCustomValueCheckbox).not.toBeInTheDocument();
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(infoText).toBeInTheDocument();
|
||||
expect(infoText).toHaveTextContent('This data source does not support ad hoc filters yet.');
|
||||
});
|
||||
|
||||
it('should update the variable data source when data source picker is changed', async () => {
|
||||
@@ -104,6 +112,7 @@ describe('AdHocFiltersVariableEditor', () => {
|
||||
});
|
||||
|
||||
it('should update the variable default keys when the default keys option is disabled', async () => {
|
||||
getTagKeysMock = () => Promise.resolve(['key1', 'key2']);
|
||||
const { renderer, variable, user } = await setup(undefined, true);
|
||||
|
||||
// Simulate toggling default options off
|
||||
@@ -113,6 +122,31 @@ describe('AdHocFiltersVariableEditor', () => {
|
||||
|
||||
expect(variable.state.defaultKeys).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return an OptionsPaneItemDescriptor that renders Editor', async () => {
|
||||
const variable = new AdHocFiltersVariable({
|
||||
name: 'test',
|
||||
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type },
|
||||
});
|
||||
|
||||
const result = getAdHocFilterOptions(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(props?: React.ComponentProps<typeof AdHocFiltersVariableEditor>, withDefaultKeys = false) {
|
||||
|
||||
+20
-2
@@ -1,15 +1,18 @@
|
||||
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 { AdHocFiltersVariable } from '@grafana/scenes';
|
||||
import { AdHocFiltersVariable, SceneVariable } from '@grafana/scenes';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { AdHocVariableForm } from '../components/AdHocVariableForm';
|
||||
|
||||
interface AdHocFiltersVariableEditorProps {
|
||||
variable: AdHocFiltersVariable;
|
||||
onRunQuery: (variable: AdHocFiltersVariable) => void;
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProps) {
|
||||
@@ -22,7 +25,7 @@ export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProp
|
||||
|
||||
const message = datasourceSettings?.getTagKeys
|
||||
? 'Ad hoc filters are applied automatically to all queries that target this data source'
|
||||
: 'This data source does not support ad hoc filters yet.';
|
||||
: 'This data source does not support ad hoc filters.';
|
||||
|
||||
const onDataSourceChange = (ds: DataSourceInstanceSettings) => {
|
||||
const dsRef = getDataSourceRef(ds);
|
||||
@@ -52,6 +55,21 @@ export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProp
|
||||
defaultKeys={defaultKeys}
|
||||
onDefaultKeysChange={onDefaultKeysChange}
|
||||
onAllowCustomValueChange={onAllowCustomValueChange}
|
||||
inline={props.inline}
|
||||
datasourceSupported={datasourceSettings?.getTagKeys ? true : false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function getAdHocFilterOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] {
|
||||
if (!(variable instanceof AdHocFiltersVariable)) {
|
||||
console.warn('getAdHocFilterOptions: variable is not an AdHocFiltersVariable');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
new OptionsPaneItemDescriptor({
|
||||
render: () => <AdHocFiltersVariableEditor variable={variable} onRunQuery={noop} inline={true} />,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
+2
-2
@@ -406,7 +406,7 @@ describe('QueryVariableEditor', () => {
|
||||
|
||||
// 3. Assert Editor's key elements are rendered
|
||||
// DataSourcePicker's Field
|
||||
expect(within(modal).getByLabelText('Data source')).toBeInTheDocument();
|
||||
expect(within(modal).getByLabelText('Target data source')).toBeInTheDocument();
|
||||
// Regex input placeholder
|
||||
expect(within(modal).getByPlaceholderText(/text>.*value/i)).toBeInTheDocument();
|
||||
// Sort select (check for its current value display)
|
||||
@@ -451,7 +451,7 @@ describe('Editor', () => {
|
||||
render(<Editor variable={variable} />);
|
||||
});
|
||||
|
||||
const dataSourcePicker = screen.getByLabelText('Data source');
|
||||
const dataSourcePicker = screen.getByLabelText('Target data source');
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ export function Editor({ variable }: { variable: QueryVariable }) {
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
label={t('dashboard-scene.query-variable-editor-form.label-data-source', 'Data source')}
|
||||
label={t('dashboard-scene.query-variable-editor-form.label-target-data-source', 'Target data source')}
|
||||
htmlFor="data-source-picker"
|
||||
>
|
||||
<DataSourcePicker current={selectedDatasource} onChange={onDataSourceChange} variables={true} width={30} />
|
||||
|
||||
@@ -24,7 +24,7 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
|
||||
import { getIntervalsQueryFromNewIntervalModel } from '../../utils/utils';
|
||||
|
||||
import { getCustomVariableOptions } from './components/CustomVariableForm';
|
||||
import { AdHocFiltersVariableEditor } from './editors/AdHocFiltersVariableEditor';
|
||||
import { AdHocFiltersVariableEditor, getAdHocFilterOptions } from './editors/AdHocFiltersVariableEditor';
|
||||
import { ConstantVariableEditor, getConstantVariableOptions } from './editors/ConstantVariableEditor';
|
||||
import { CustomVariableEditor } from './editors/CustomVariableEditor';
|
||||
import { DataSourceVariableEditor } from './editors/DataSourceVariableEditor';
|
||||
@@ -81,6 +81,7 @@ export const EDITABLE_VARIABLES: Record<EditableVariableType, EditableVariableCo
|
||||
name: 'Ad hoc filters',
|
||||
description: 'Add key/value filters on the fly',
|
||||
editor: AdHocFiltersVariableEditor,
|
||||
getOptions: getAdHocFilterOptions,
|
||||
},
|
||||
groupby: {
|
||||
name: 'Group by',
|
||||
|
||||
@@ -61,6 +61,7 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
|
||||
datasource={variable.datasource ?? undefined}
|
||||
onDataSourceChange={this.onDatasourceChanged}
|
||||
infoText={extended?.infoText}
|
||||
datasourceSupported={variable.datasource === undefined ? false : true} // legacy behavior - will show data source settings even if not supported
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4020,6 +4020,7 @@
|
||||
"dashboard-scene": {
|
||||
"ad-hoc-variable-form": {
|
||||
"adhoc-options": "Ad-hoc options",
|
||||
"alert-not-supported": "This data source does not support ad hoc filters",
|
||||
"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}}",
|
||||
"label-data-source": "Data source",
|
||||
@@ -4244,6 +4245,7 @@
|
||||
"description-examples": "Named capture groups can be used to separate the display text and value (<1>see examples</1>).",
|
||||
"description-optional": "Optional, if you want to extract part of a series name or metric node segment.",
|
||||
"label-data-source": "Data source",
|
||||
"label-target-data-source": "Target data source",
|
||||
"query-options": "Query options",
|
||||
"selection-options": "Selection options"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user