GroupBy variable core integration (#82185)

* Bump scenes

* Make GroupByVariableModel a VariableWithOptions

* Serialise/deserialise group by variable

* WIP: Group by variable editor

* WIP tests

* Group by variable tests

* add feature toggle and gate variable creation behind it

* Fix types

* Do not resolve DS variable

* Do not show the message if no DS is selected

* Now groupby has options and current

* Update public/app/features/dashboard-scene/settings/variables/components/GroupByVariableForm.test.tsx

Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>

* don't allow creating groupby if toggle is off + update tests

* add unit tests

* remove groupByKeys

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
pull/82465/head
Dominik Prokop 1 year ago committed by GitHub
parent 269fa400f0
commit f016f95298
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  2. 5
      packages/grafana-data/src/types/templateVars.ts
  3. 5
      packages/grafana-e2e-selectors/src/selectors/pages.ts
  4. 9
      pkg/services/featuremgmt/registry.go
  5. 1
      pkg/services/featuremgmt/toggles_gen.csv
  6. 4
      pkg/services/featuremgmt/toggles_gen.go
  7. 2041
      pkg/services/featuremgmt/toggles_gen.json
  8. 91
      public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts
  9. 17
      public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts
  10. 112
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
  11. 21
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
  12. 114
      public/app/features/dashboard-scene/settings/variables/components/GroupByVariableForm.test.tsx
  13. 84
      public/app/features/dashboard-scene/settings/variables/components/GroupByVariableForm.tsx
  14. 103
      public/app/features/dashboard-scene/settings/variables/editors/GroupByVariableEditor.test.tsx
  15. 43
      public/app/features/dashboard-scene/settings/variables/editors/GroupByVariableEditor.tsx
  16. 65
      public/app/features/dashboard-scene/settings/variables/utils.test.ts
  17. 11
      public/app/features/dashboard-scene/settings/variables/utils.ts
  18. 2
      public/app/features/variables/guard.test.ts
  19. 4
      public/app/features/variables/state/__tests__/fixtures.ts

@ -181,4 +181,5 @@ export interface FeatureToggles {
groupToNestedTableTransformation?: boolean;
newPDFRendering?: boolean;
kubernetesAggregator?: boolean;
groupByVariable?: boolean;
}

@ -1,5 +1,4 @@
import { LoadingState } from './data';
import { MetricFindValue } from './datasource';
import { DataSourceRef } from './query';
export type VariableType = TypedVariableModel['type'];
@ -66,11 +65,9 @@ export interface AdHocVariableModel extends BaseVariableModel {
baseFilters?: AdHocVariableFilter[];
}
export interface GroupByVariableModel extends BaseVariableModel {
export interface GroupByVariableModel extends VariableWithOptions {
type: 'groupby';
datasource: DataSourceRef | null;
groupByKeys: string[];
defaultOptions?: MetricFindValue[];
multi: true;
}

@ -182,6 +182,11 @@ export const Pages = {
stepCountIntervalSelect: 'data-testid interval variable step count input',
minIntervalInput: 'data-testid interval variable mininum interval input',
},
GroupByVariable: {
dataSourceSelect: Components.DataSourcePicker.inputV2,
infoText: 'data-testid group by variable info text',
modeToggle: 'data-testid group by variable mode toggle',
},
AdHocFiltersVariable: {
datasourceSelect: Components.DataSourcePicker.inputV2,
infoText: 'data-testid ad-hoc filters variable info text',

@ -1212,6 +1212,15 @@ var (
Owner: grafanaAppPlatformSquad,
RequiresRestart: true,
},
{
Name: "groupByVariable",
Description: "Enable groupBy variable support in scenes dashboards",
Stage: FeatureStageExperimental,
Owner: grafanaDashboardsSquad,
AllowSelfServe: false,
HideFromDocs: true,
HideFromAdminPage: true,
},
}
)

@ -162,3 +162,4 @@ nodeGraphDotLayout,experimental,@grafana/observability-traces-and-profiling,fals
groupToNestedTableTransformation,preview,@grafana/dataviz-squad,false,false,true
newPDFRendering,experimental,@grafana/sharing-squad,false,false,false
kubernetesAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false
groupByVariable,experimental,@grafana/dashboards-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
162 groupToNestedTableTransformation preview @grafana/dataviz-squad false false true
163 newPDFRendering experimental @grafana/sharing-squad false false false
164 kubernetesAggregator experimental @grafana/grafana-app-platform-squad false true false
165 groupByVariable experimental @grafana/dashboards-squad false false false

@ -658,4 +658,8 @@ const (
// FlagKubernetesAggregator
// Enable grafana aggregator
FlagKubernetesAggregator = "kubernetesAggregator"
// FlagGroupByVariable
// Enable groupBy variable support in scenes dashboards
FlagGroupByVariable = "groupByVariable"
)

File diff suppressed because it is too large Load Diff

@ -11,12 +11,13 @@ import {
toDataFrame,
VariableSupportType,
} from '@grafana/data';
import { setRunRequest } from '@grafana/runtime';
import { config, setRunRequest } from '@grafana/runtime';
import {
AdHocFiltersVariable,
ConstantVariable,
CustomVariable,
DataSourceVariable,
GroupByVariable,
QueryVariable,
SceneVariableSet,
TextBoxVariable,
@ -401,4 +402,92 @@ describe('sceneVariablesSetToVariables', () => {
}
`);
});
describe('when the groupByVariable feature toggle is enabled', () => {
beforeAll(() => {
config.featureToggles.groupByVariable = true;
});
afterAll(() => {
config.featureToggles.groupByVariable = false;
});
it('should handle GroupByVariable', () => {
const variable = new GroupByVariable({
name: 'test',
label: 'test-label',
description: 'test-desc',
datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [
{
text: 'Foo',
value: 'foo',
},
{
text: 'Bar',
value: 'bar',
},
],
});
const set = new SceneVariableSet({
variables: [variable],
});
const result = sceneVariablesSetToVariables(set);
expect(result).toHaveLength(1);
expect(result[0]).toMatchInlineSnapshot(`
{
"current": {
"text": [],
"value": [],
},
"datasource": {
"type": "fake-std",
"uid": "fake-std",
},
"description": "test-desc",
"label": "test-label",
"name": "test",
"options": [
{
"text": "Foo",
"value": "foo",
},
{
"text": "Bar",
"value": "bar",
},
],
"type": "groupby",
}
`);
});
});
describe('when the groupByVariable feature toggle is disabled', () => {
it('should not handle GroupByVariable and throw an error', () => {
const variable = new GroupByVariable({
name: 'test',
label: 'test-label',
description: 'test-desc',
datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [
{
text: 'Foo',
value: 'foo',
},
{
text: 'Bar',
value: 'bar',
},
],
});
const set = new SceneVariableSet({
variables: [variable],
});
expect(() => sceneVariablesSetToVariables(set)).toThrow('Unsupported variable type');
});
});
});

@ -1,3 +1,4 @@
import { config } from '@grafana/runtime';
import { SceneVariables, sceneUtils } from '@grafana/scenes';
import { VariableHide, VariableModel, VariableRefresh, VariableSort } from '@grafana/schema';
@ -104,6 +105,22 @@ export function sceneVariablesSetToVariables(set: SceneVariables) {
},
query: variable.state.value,
});
} else if (sceneUtils.isGroupByVariable(variable) && config.featureToggles.groupByVariable) {
variables.push({
...commonProperties,
datasource: variable.state.datasource,
// Only persist the statically defined options
options: variable.state.defaultOptions?.map((option) => ({
text: option.text,
value: String(option.value),
})),
current: {
// @ts-expect-error
text: variable.state.text,
// @ts-expect-error
value: variable.state.value,
},
});
} else if (sceneUtils.isAdHocVariable(variable)) {
variables.push({
...commonProperties,

@ -7,6 +7,7 @@ import {
IntervalVariableModel,
TypedVariableModel,
TextBoxVariableModel,
GroupByVariableModel,
} from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { config } from '@grafana/runtime';
@ -16,6 +17,7 @@ import {
ConstantVariable,
CustomVariable,
DataSourceVariable,
GroupByVariable,
QueryVariable,
SceneDataLayerControls,
SceneDataLayers,
@ -844,6 +846,116 @@ describe('transformSaveModelToScene', () => {
});
});
describe('when groupByVariable feature toggle is enabled', () => {
beforeAll(() => {
config.featureToggles.groupByVariable = true;
});
afterAll(() => {
config.featureToggles.groupByVariable = false;
});
it('should migrate groupby variable', () => {
const variable: GroupByVariableModel = {
id: 'groupby',
global: false,
index: 0,
state: LoadingState.Done,
error: null,
name: 'groupby',
label: 'GroupBy Label',
description: 'GroupBy Description',
type: 'groupby',
rootStateKey: 'N4XLmH5Vz',
datasource: {
uid: 'gdev-prometheus',
type: 'prometheus',
},
multi: true,
options: [
{
selected: false,
text: 'Foo',
value: 'foo',
},
{
selected: false,
text: 'Bar',
value: 'bar',
},
],
current: {},
query: '',
hide: 0,
skipUrlSync: false,
};
const migrated = createSceneVariableFromVariableModel(variable) as GroupByVariable;
const groupbyVarState = migrated.state;
expect(migrated).toBeInstanceOf(GroupByVariable);
expect(groupbyVarState).toEqual({
key: expect.any(String),
description: 'GroupBy Description',
hide: 0,
defaultOptions: [
{
selected: false,
text: 'Foo',
value: 'foo',
},
{
selected: false,
text: 'Bar',
value: 'bar',
},
],
isMulti: true,
layout: 'horizontal',
noValueOnClear: true,
label: 'GroupBy Label',
name: 'groupby',
skipUrlSync: false,
type: 'groupby',
baseFilters: [],
options: [],
text: [],
value: [],
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
applyMode: 'auto',
});
});
});
describe('when groupByVariable feature toggle is disabled', () => {
it('should not migrate groupby variable and throw an error instead', () => {
const variable: GroupByVariableModel = {
id: 'groupby',
global: false,
index: 0,
state: LoadingState.Done,
error: null,
name: 'groupby',
label: 'GroupBy Label',
description: 'GroupBy Description',
type: 'groupby',
rootStateKey: 'N4XLmH5Vz',
datasource: {
uid: 'gdev-prometheus',
type: 'prometheus',
},
multi: true,
options: [],
current: {},
query: '',
hide: 0,
skipUrlSync: false,
};
expect(() => createSceneVariableFromVariableModel(variable)).toThrow('Scenes: Unsupported variable type');
});
});
it.each(['system'])('should throw for unsupported (yet) variables', (type) => {
const variable = {
name: 'query0',

@ -26,6 +26,7 @@ import {
SceneDataLayerControls,
TextBoxVariable,
UserActionEvent,
GroupByVariable,
AdHocFiltersVariable,
} from '@grafana/scenes';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
@ -291,6 +292,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
const commonProperties = {
name: variable.name,
label: variable.label,
description: variable.description,
};
if (variable.type === 'adhoc') {
return new AdHocFiltersVariable({
@ -309,7 +311,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
...commonProperties,
value: variable.current?.value ?? '',
text: variable.current?.text ?? '',
description: variable.description,
query: variable.query,
isMulti: variable.multi,
allValue: variable.allValue || undefined,
@ -323,7 +325,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
...commonProperties,
value: variable.current?.value ?? '',
text: variable.current?.text ?? '',
description: variable.description,
query: variable.query,
datasource: variable.datasource,
sort: variable.sort,
@ -342,7 +344,6 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
...commonProperties,
value: variable.current?.value ?? '',
text: variable.current?.text ?? '',
description: variable.description,
regex: variable.regex,
pluginId: variable.query,
allValue: variable.allValue || undefined,
@ -358,7 +359,6 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
return new IntervalVariable({
...commonProperties,
value: currentInterval,
description: variable.description,
intervals: intervals,
autoEnabled: variable.auto,
autoStepCount: variable.auto_count,
@ -370,7 +370,6 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
} else if (variable.type === 'constant') {
return new ConstantVariable({
...commonProperties,
description: variable.description,
value: variable.query,
skipUrlSync: variable.skipUrlSync,
hide: variable.hide,
@ -378,11 +377,21 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
} else if (variable.type === 'textbox') {
return new TextBoxVariable({
...commonProperties,
description: variable.description,
value: variable.query,
skipUrlSync: variable.skipUrlSync,
hide: variable.hide,
});
} else if (config.featureToggles.groupByVariable && variable.type === 'groupby') {
return new GroupByVariable({
...commonProperties,
datasource: variable.datasource,
value: variable.current?.value || [],
text: variable.current?.text || [],
skipUrlSync: variable.skipUrlSync,
hide: variable.hide,
// @ts-expect-error
defaultOptions: variable.options,
});
} else {
throw new Error(`Scenes: Unsupported variable type ${variable.type}`);
}

@ -0,0 +1,114 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { byTestId } from 'testing-library-selector';
import { VariableSupportType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { mockDataSource } from 'app/features/alerting/unified/mocks';
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
import { GroupByVariableForm, GroupByVariableFormProps } from './GroupByVariableForm';
const defaultDatasource = mockDataSource({
name: 'Default Test Data Source',
type: 'test',
});
const promDatasource = mockDataSource({
name: 'Prometheus',
type: 'prometheus',
});
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'),
getDataSourceSrv: () => ({
get: async () => ({
...defaultDatasource,
variables: {
getType: () => VariableSupportType.Custom,
query: jest.fn(),
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor),
},
}),
getList: () => [defaultDatasource, promDatasource],
getInstanceSettings: () => ({ ...defaultDatasource }),
}),
}));
describe('GroupByVariableForm', () => {
const onDataSourceChangeMock = jest.fn();
const onDefaultOptionsChangeMock = jest.fn();
const defaultProps: GroupByVariableFormProps = {
onDataSourceChange: onDataSourceChangeMock,
onDefaultOptionsChange: onDefaultOptionsChangeMock,
};
function setup(props?: Partial<GroupByVariableFormProps>) {
return {
renderer: render(<GroupByVariableForm {...defaultProps} {...props} />),
user: userEvent.setup(),
};
}
beforeEach(() => {
jest.clearAllMocks();
});
it('should call onDataSourceChange when changing the datasource', async () => {
const {
renderer: { getByTestId },
} = setup();
const dataSourcePicker = getByTestId(selectors.components.DataSourcePicker.inputV2);
await userEvent.click(dataSourcePicker);
await userEvent.click(screen.getByText(/prometheus/i));
expect(onDataSourceChangeMock).toHaveBeenCalledTimes(1);
expect(onDataSourceChangeMock).toHaveBeenCalledWith(promDatasource, undefined);
});
it('should not render code editor when no default options provided', async () => {
const {
renderer: { queryByTestId },
} = setup();
const codeEditor = queryByTestId(selectors.components.CodeEditor.container);
expect(codeEditor).not.toBeInTheDocument();
});
it('should render code editor when default options provided', async () => {
const {
renderer: { getByTestId },
} = setup({ defaultOptions: [{ text: 'test', value: 'test' }] });
const codeEditor = getByTestId(selectors.components.CodeEditor.container);
await byTestId(selectors.components.CodeEditor.container).find();
expect(codeEditor).toBeInTheDocument();
});
it('should call onDefaultOptionsChange when providing static options', async () => {
const {
renderer: { getByTestId },
} = setup();
const toggle = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle);
await userEvent.click(toggle);
expect(onDefaultOptionsChangeMock).toHaveBeenCalledTimes(1);
expect(onDefaultOptionsChangeMock).toHaveBeenCalledWith([]);
});
it('should call onDefaultOptionsChange when toggling off static options', async () => {
const {
renderer: { getByTestId },
} = setup({ defaultOptions: [{ text: 'test', value: 'test' }] });
const toggle = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle);
await userEvent.click(toggle);
expect(onDefaultOptionsChangeMock).toHaveBeenCalledTimes(1);
expect(onDefaultOptionsChangeMock).toHaveBeenCalledWith(undefined);
});
});

@ -0,0 +1,84 @@
import React, { useCallback } from 'react';
import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { DataSourceRef } from '@grafana/schema';
import { Alert, CodeEditor, Field, Switch } from '@grafana/ui';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
import { VariableLegend } from './VariableLegend';
export interface GroupByVariableFormProps {
datasource?: DataSourceRef;
onDataSourceChange: (dsSettings: DataSourceInstanceSettings) => void;
onDefaultOptionsChange: (options?: MetricFindValue[]) => void;
infoText?: string;
defaultOptions?: MetricFindValue[];
}
export function GroupByVariableForm({
datasource,
defaultOptions,
infoText,
onDataSourceChange,
onDefaultOptionsChange,
}: GroupByVariableFormProps) {
const updateDefaultOptions = useCallback(
(csvContent: string) => {
const df = readCSV('key,value\n' + csvContent)[0];
const options = [];
for (let i = 0; i < df.length; i++) {
options.push({ text: df.fields[0].values[i], value: df.fields[1].values[i] });
}
onDefaultOptionsChange(options);
},
[onDefaultOptionsChange]
);
return (
<>
<VariableLegend>Group by options</VariableLegend>
<Field label="Data source" htmlFor="data-source-picker">
<DataSourcePicker current={datasource} onChange={onDataSourceChange} width={30} variables={true} noDefault />
</Field>
{infoText ? (
<Alert
title={infoText}
severity="info"
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.infoText}
/>
) : null}
<Field
label="Use static Group By dimensions"
description="Provide dimensions as CSV: dimensionId, dimensionName "
>
<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}
/>
)}
</>
);
}

@ -0,0 +1,103 @@
import { act, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
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 { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
import { GroupByVariableEditor } from './GroupByVariableEditor';
const defaultDatasource = mockDataSource({
name: 'Default Test Data Source',
uid: 'test-ds',
type: 'test',
});
const promDatasource = mockDataSource({
name: 'Prometheus',
uid: 'prometheus',
type: 'prometheus',
});
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'),
getDataSourceSrv: () => ({
get: async () => ({
...defaultDatasource,
variables: {
getType: () => VariableSupportType.Custom,
query: jest.fn(),
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor),
},
}),
getList: () => [defaultDatasource, promDatasource],
getInstanceSettings: () => ({ ...defaultDatasource }),
}),
}));
describe('GroupByVariableEditor', () => {
it('renders AdHocVariableForm with correct props', async () => {
const { renderer } = await setup();
const dataSourcePicker = renderer.getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.dataSourceSelect
);
const infoText = renderer.getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.infoText);
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 () => {
const { renderer, variable, user } = await setup();
// Simulate changing the data source
await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2));
await user.click(renderer.getByText(/prom/i));
expect(variable.state.datasource).toEqual({ uid: 'prometheus', type: 'prometheus' });
});
it('should update the variable default options when static options are enabled', async () => {
const { renderer, variable, user } = await setup();
// Simulate toggling static options on
await user.click(
renderer.getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle)
);
expect(variable.state.defaultOptions).toEqual([]);
});
it('should update the variable default options when static options are disabled', async () => {
const { renderer, variable, user } = await setup([{ text: 'A', value: 'A' }]);
// Simulate toggling static options off
await user.click(
renderer.getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.GroupByVariable.modeToggle)
);
expect(variable.state.defaultOptions).toEqual(undefined);
});
});
async function setup(defaultOptions?: MetricFindValue[]) {
const onRunQuery = jest.fn();
const variable = new GroupByVariable({
name: 'groupByVariable',
type: 'groupby',
label: 'Group By',
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type },
defaultOptions,
});
return {
renderer: await act(() => render(<GroupByVariableEditor variable={variable} onRunQuery={onRunQuery} />)),
variable,
user: userEvent.setup(),
mocks: { onRunQuery },
};
}

@ -1,12 +1,51 @@
import React from 'react';
import { useAsync } from 'react-use';
import { DataSourceInstanceSettings, DataSourceRef, MetricFindValue } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { GroupByVariable } from '@grafana/scenes';
import { GroupByVariableForm } from '../components/GroupByVariableForm';
interface GroupByVariableEditorProps {
variable: GroupByVariable;
onChange: (variable: GroupByVariable) => void;
onRunQuery: () => void;
}
export function GroupByVariableEditor(props: GroupByVariableEditorProps) {
return <div>GroupByVariableEditor</div>;
const { variable, onRunQuery } = props;
const { datasource: datasourceRef, defaultOptions } = variable.useState();
const { value: datasource } = useAsync(async () => {
return await getDataSourceSrv().get(datasourceRef);
}, [variable.state]);
const message = datasource?.getTagKeys
? 'Group by dimensions are applied automatically to all queries that target this data source'
: 'This data source does not support group by variable yet.';
const onDataSourceChange = async (ds: DataSourceInstanceSettings) => {
const dsRef: DataSourceRef = {
uid: ds.uid,
type: ds.type,
};
variable.setState({ datasource: dsRef });
onRunQuery();
};
const onDefaultOptionsChange = async (defaultOptions?: MetricFindValue[]) => {
variable.setState({ defaultOptions });
onRunQuery();
};
return (
<GroupByVariableForm
defaultOptions={defaultOptions}
datasource={datasourceRef ?? undefined}
infoText={datasourceRef ? message : undefined}
onDataSourceChange={onDataSourceChange}
onDefaultOptionsChange={onDefaultOptionsChange}
/>
);
}

@ -1,5 +1,5 @@
import { DataSourceApi } from '@grafana/data';
import { setTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { config, setTemplateSrv, TemplateSrv } from '@grafana/runtime';
import {
CustomVariable,
ConstantVariable,
@ -99,26 +99,61 @@ describe('isEditableVariableType', () => {
});
describe('getVariableTypeSelectOptions', () => {
it('should contain all editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length);
describe('when groupByVariable is enabled', () => {
beforeAll(() => {
config.featureToggles.groupByVariable = true;
});
afterAll(() => {
config.featureToggles.groupByVariable = false;
});
EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => {
expect(EDITABLE_VARIABLES).toHaveProperty(type);
it('should contain all editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length);
EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => {
expect(EDITABLE_VARIABLES).toHaveProperty(type);
});
});
it('should return an array of selectable values for editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(8);
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
const variableTypeConfig = EDITABLE_VARIABLES[editableType];
expect(option.value).toBe(editableType);
expect(option.label).toBe(variableTypeConfig.name);
expect(option.description).toBe(variableTypeConfig.description);
});
});
});
it('should return an array of selectable values for editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(8);
describe('when groupByVariable is disabled', () => {
it('should contain all editable variable types except groupby', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length - 1);
EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => {
expect(EDITABLE_VARIABLES).toHaveProperty(type);
});
});
it('should return an array of selectable values for editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(7);
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
const variableTypeConfig = EDITABLE_VARIABLES[editableType];
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
const variableTypeConfig = EDITABLE_VARIABLES[editableType];
expect(option.value).toBe(editableType);
expect(option.label).toBe(variableTypeConfig.name);
expect(option.description).toBe(variableTypeConfig.description);
expect(option.value).toBe(editableType);
expect(option.label).toBe(variableTypeConfig.name);
expect(option.description).toBe(variableTypeConfig.description);
});
});
});
});

@ -1,7 +1,7 @@
import { chain } from 'lodash';
import { DataSourceInstanceSettings, SelectableValue } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { config, getDataSourceSrv } from '@grafana/runtime';
import {
ConstantVariable,
CustomVariable,
@ -95,11 +95,18 @@ export const EDITABLE_VARIABLES_SELECT_ORDER: EditableVariableType[] = [
];
export function getVariableTypeSelectOptions(): Array<SelectableValue<EditableVariableType>> {
return EDITABLE_VARIABLES_SELECT_ORDER.map((variableType) => ({
const results = EDITABLE_VARIABLES_SELECT_ORDER.map((variableType) => ({
label: EDITABLE_VARIABLES[variableType].name,
value: variableType,
description: EDITABLE_VARIABLES[variableType].description,
}));
if (!config.featureToggles.groupByVariable) {
// Remove group by variable type if feature toggle is off
return results.filter((option) => option.value !== 'groupby');
}
return results;
}
export function getVariableEditor(type: EditableVariableType) {

@ -165,7 +165,7 @@ describe('type guards', () => {
const variableFactsObj: Record<VariableType | ExtraVariableTypes, VariableFacts> = {
query: { variable: createQueryVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
adhoc: { variable: createAdhocVariable(), isMulti: false, hasOptions: false, hasCurrent: false },
groupby: { variable: createGroupByVariable(), isMulti: true, hasOptions: false, hasCurrent: false },
groupby: { variable: createGroupByVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
constant: { variable: createConstantVariable(), isMulti: false, hasOptions: true, hasCurrent: true },
datasource: { variable: createDatasourceVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
interval: { variable: createIntervalVariable(), isMulti: false, hasOptions: true, hasCurrent: true },

@ -82,12 +82,14 @@ export function createAdhocVariable(input?: Partial<AdHocVariableModel>): AdHocV
export function createGroupByVariable(input?: Partial<GroupByVariableModel>): GroupByVariableModel {
return {
...createBaseVariableModel('groupby'),
query: '',
datasource: {
uid: 'abc-123',
type: 'prometheus',
},
groupByKeys: [],
multi: true,
current: createVariableOption('job'),
options: [createVariableOption('job'), createVariableOption('instance')],
...input,
};
}

Loading…
Cancel
Save