mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Use auto and only use AdHocFiltersVariable to manage filters (#81318)
This commit is contained in:
@@ -246,7 +246,7 @@
|
||||
"@grafana/o11y-ds-frontend": "workspace:*",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^2.6.5",
|
||||
"@grafana/scenes": "^3.2.1",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -182,6 +182,10 @@ export const Pages = {
|
||||
stepCountIntervalSelect: 'data-testid interval variable step count input',
|
||||
minIntervalInput: 'data-testid interval variable mininum interval input',
|
||||
},
|
||||
AdHocFiltersVariable: {
|
||||
datasourceSelect: Components.DataSourcePicker.inputV2,
|
||||
infoText: 'data-testid ad-hoc filters variable info text',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PanelContext } from '@grafana/ui';
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { findVizPanelByKey } from '../utils/utils';
|
||||
|
||||
import { getAdHocFilterSetFor, setDashboardPanelContext } from './setDashboardPanelContext';
|
||||
import { getAdHocFilterVariableFor, setDashboardPanelContext } from './setDashboardPanelContext';
|
||||
|
||||
const postFn = jest.fn();
|
||||
const putFn = jest.fn();
|
||||
@@ -132,26 +132,26 @@ describe('setDashboardPanelContext', () => {
|
||||
|
||||
context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' });
|
||||
|
||||
const set = getAdHocFilterSetFor(scene, { uid: 'my-ds-uid' });
|
||||
const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' });
|
||||
|
||||
expect(set.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]);
|
||||
expect(variable.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]);
|
||||
});
|
||||
|
||||
it('Should update and add filter to existing set', () => {
|
||||
const { scene, context } = buildTestScene({ existingFilterSet: true });
|
||||
const { scene, context } = buildTestScene({ existingFilterVariable: true });
|
||||
|
||||
const set = getAdHocFilterSetFor(scene, { uid: 'my-ds-uid' });
|
||||
const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' });
|
||||
|
||||
set.setState({ filters: [{ key: 'existing', value: 'world', operator: '=' }] });
|
||||
variable.setState({ filters: [{ key: 'existing', value: 'world', operator: '=' }] });
|
||||
|
||||
context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' });
|
||||
|
||||
expect(set.state.filters.length).toBe(2);
|
||||
expect(variable.state.filters.length).toBe(2);
|
||||
|
||||
// Can update existing filter value without adding a new filter
|
||||
context.onAddAdHocFilter!({ key: 'hello', value: 'world2', operator: '=' });
|
||||
// Verify existing filter value updated
|
||||
expect(set.state.filters[1].value).toBe('world2');
|
||||
expect(variable.state.filters[1].value).toBe('world2');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -163,7 +163,7 @@ interface SceneOptions {
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
orgCanEdit?: boolean;
|
||||
existingFilterSet?: boolean;
|
||||
existingFilterVariable?: boolean;
|
||||
}
|
||||
|
||||
function buildTestScene(options: SceneOptions) {
|
||||
@@ -198,7 +198,7 @@ function buildTestScene(options: SceneOptions) {
|
||||
},
|
||||
],
|
||||
templating: {
|
||||
list: options.existingFilterSet
|
||||
list: options.existingFilterVariable
|
||||
? [
|
||||
{
|
||||
type: 'adhoc',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data';
|
||||
import { AdHocFilterSet, dataLayers, SceneDataLayers, VizPanel } from '@grafana/scenes';
|
||||
import { AdHocFiltersVariable, dataLayers, SceneDataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||
import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api';
|
||||
@@ -111,8 +111,8 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
return;
|
||||
}
|
||||
|
||||
const filterSet = getAdHocFilterSetFor(dashboard, queryRunner.state.datasource);
|
||||
updateAdHocFilterSet(filterSet, newFilter);
|
||||
const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource);
|
||||
updateAdHocFilterVariable(filterVar, newFilter);
|
||||
};
|
||||
|
||||
context.onUpdateData = (frames: DataFrame[]): Promise<boolean> => {
|
||||
@@ -149,33 +149,37 @@ function reRunBuiltInAnnotationsLayer(scene: DashboardScene) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getAdHocFilterSetFor(scene: DashboardScene, ds: DataSourceRef | null | undefined) {
|
||||
const controls = scene.state.controls ?? [];
|
||||
export function getAdHocFilterVariableFor(scene: DashboardScene, ds: DataSourceRef | null | undefined) {
|
||||
const variables = sceneGraph.getVariables(scene);
|
||||
|
||||
for (const control of controls) {
|
||||
if (control instanceof AdHocFilterSet) {
|
||||
if (control.state.datasource === ds || control.state.datasource?.uid === ds?.uid) {
|
||||
return control;
|
||||
for (const variable of variables.state.variables) {
|
||||
if (sceneUtils.isAdHocVariable(variable)) {
|
||||
const filtersDs = variable.state.datasource;
|
||||
if (filtersDs === ds || filtersDs?.uid === ds?.uid) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newSet = new AdHocFilterSet({ datasource: ds });
|
||||
|
||||
// Add it to the scene
|
||||
scene.setState({
|
||||
controls: [controls[0], newSet, ...controls.slice(1)],
|
||||
const newVariable = new AdHocFiltersVariable({
|
||||
name: 'Filters',
|
||||
datasource: ds,
|
||||
});
|
||||
|
||||
return newSet;
|
||||
// Add it to the scene
|
||||
variables.setState({
|
||||
variables: [...variables.state.variables, newVariable],
|
||||
});
|
||||
|
||||
return newVariable;
|
||||
}
|
||||
|
||||
function updateAdHocFilterSet(filterSet: AdHocFilterSet, newFilter: AdHocFilterItem) {
|
||||
function updateAdHocFilterVariable(filterVar: AdHocFiltersVariable, newFilter: AdHocFilterItem) {
|
||||
// Check if we need to update an existing filter
|
||||
for (const filter of filterSet.state.filters) {
|
||||
for (const filter of filterVar.state.filters) {
|
||||
if (filter.key === newFilter.key) {
|
||||
filterSet.setState({
|
||||
filters: filterSet.state.filters.map((f) => {
|
||||
filterVar.setState({
|
||||
filters: filterVar.state.filters.map((f) => {
|
||||
if (f.key === newFilter.key) {
|
||||
return newFilter;
|
||||
}
|
||||
@@ -187,7 +191,7 @@ function updateAdHocFilterSet(filterSet: AdHocFilterSet, newFilter: AdHocFilterI
|
||||
}
|
||||
|
||||
// Add new filter
|
||||
filterSet.setState({
|
||||
filters: [...filterSet.state.filters, newFilter],
|
||||
filterVar.setState({
|
||||
filters: [...filterVar.state.filters, newFilter],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -455,6 +455,16 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"filters": [],
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
{
|
||||
"auto": true,
|
||||
"auto_count": 30,
|
||||
@@ -525,14 +535,6 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
|
||||
"skipUrlSync": true,
|
||||
"type": "constant",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
],
|
||||
},
|
||||
"time": {
|
||||
@@ -757,6 +759,16 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"filters": [],
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
{
|
||||
"auto": true,
|
||||
"auto_count": 30,
|
||||
@@ -827,14 +839,6 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr
|
||||
"skipUrlSync": true,
|
||||
"type": "constant",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
],
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { setRunRequest } from '@grafana/runtime';
|
||||
import {
|
||||
AdHocFiltersVariable,
|
||||
ConstantVariable,
|
||||
CustomVariable,
|
||||
DataSourceVariable,
|
||||
@@ -344,4 +345,60 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle AdHocFiltersVariable', () => {
|
||||
const variable = new AdHocFiltersVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
filters: [
|
||||
{
|
||||
key: 'filterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
baseFilters: [
|
||||
{
|
||||
key: 'baseFilterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToVariables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"baseFilters": [
|
||||
{
|
||||
"key": "baseFilterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"datasource": {
|
||||
"type": "fake-std",
|
||||
"uid": "fake-std",
|
||||
},
|
||||
"description": "test-desc",
|
||||
"filters": [
|
||||
{
|
||||
"key": "filterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"label": "test-label",
|
||||
"name": "test",
|
||||
"type": "adhoc",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,6 +104,16 @@ export function sceneVariablesSetToVariables(set: SceneVariables) {
|
||||
},
|
||||
query: variable.state.value,
|
||||
});
|
||||
} else if (sceneUtils.isAdHocVariable(variable)) {
|
||||
variables.push({
|
||||
...commonProperties,
|
||||
name: variable.state.name!,
|
||||
type: 'adhoc',
|
||||
datasource: variable.state.datasource,
|
||||
// @ts-expect-error
|
||||
baseFilters: variable.state.baseFilters,
|
||||
filters: variable.state.filters,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Unsupported variable type');
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
AdHocFilterSet,
|
||||
AdHocFiltersVariable,
|
||||
behaviors,
|
||||
ConstantVariable,
|
||||
CustomVariable,
|
||||
DataSourceVariable,
|
||||
QueryVariable,
|
||||
@@ -120,11 +121,11 @@ describe('transformSaveModelToScene', () => {
|
||||
expect(scene.state?.$timeRange?.state.timeZone).toEqual('America/New_York');
|
||||
expect(scene.state?.$timeRange?.state.weekStart).toEqual('saturday');
|
||||
|
||||
expect(scene.state?.$variables?.state.variables).toHaveLength(1);
|
||||
expect(scene.state?.$variables?.state.variables).toHaveLength(2);
|
||||
expect(scene.state?.$variables?.getByName('constant')).toBeInstanceOf(ConstantVariable);
|
||||
expect(scene.state?.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable);
|
||||
expect(dashboardControls).toBeDefined();
|
||||
expect(dashboardControls).toBeInstanceOf(DashboardControls);
|
||||
expect(dashboardControls.state.variableControls[1]).toBeInstanceOf(AdHocFilterSet);
|
||||
expect((dashboardControls.state.variableControls[1] as AdHocFilterSet).state.name).toBe('CoolFilters');
|
||||
expect(dashboardControls.state.timeControls).toHaveLength(2);
|
||||
expect(dashboardControls.state.timeControls[0]).toBeInstanceOf(SceneTimePicker);
|
||||
expect(dashboardControls.state.timeControls[1]).toBeInstanceOf(SceneRefreshPicker);
|
||||
@@ -789,6 +790,60 @@ describe('transformSaveModelToScene', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate adhoc variable', () => {
|
||||
const variable: TypedVariableModel = {
|
||||
id: 'adhoc',
|
||||
global: false,
|
||||
index: 0,
|
||||
state: LoadingState.Done,
|
||||
error: null,
|
||||
name: 'adhoc',
|
||||
label: 'Adhoc Label',
|
||||
description: 'Adhoc Description',
|
||||
type: 'adhoc',
|
||||
rootStateKey: 'N4XLmH5Vz',
|
||||
datasource: {
|
||||
uid: 'gdev-prometheus',
|
||||
type: 'prometheus',
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
key: 'filterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
baseFilters: [
|
||||
{
|
||||
key: 'baseFilterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
hide: 0,
|
||||
skipUrlSync: false,
|
||||
};
|
||||
|
||||
const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable;
|
||||
const filterVarState = migrated.state;
|
||||
|
||||
expect(migrated).toBeInstanceOf(AdHocFiltersVariable);
|
||||
expect(filterVarState).toEqual({
|
||||
key: expect.any(String),
|
||||
description: 'Adhoc Description',
|
||||
hide: 0,
|
||||
label: 'Adhoc Label',
|
||||
name: 'adhoc',
|
||||
skipUrlSync: false,
|
||||
type: 'adhoc',
|
||||
filterExpression: 'filterTest="test"',
|
||||
filters: [{ key: 'filterTest', operator: '=', value: 'test' }],
|
||||
baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }],
|
||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||
applyMode: 'auto',
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['system'])('should throw for unsupported (yet) variables', (type) => {
|
||||
const variable = {
|
||||
name: 'query0',
|
||||
@@ -856,7 +911,7 @@ describe('transformSaveModelToScene', () => {
|
||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||
|
||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf(
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf(
|
||||
SceneDataLayerControls
|
||||
);
|
||||
|
||||
@@ -886,7 +941,7 @@ describe('transformSaveModelToScene', () => {
|
||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||
|
||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf(
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf(
|
||||
SceneDataLayerControls
|
||||
);
|
||||
|
||||
@@ -902,7 +957,7 @@ describe('transformSaveModelToScene', () => {
|
||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||
|
||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf(
|
||||
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf(
|
||||
SceneDataLayerControls
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AdHocVariableModel, TypedVariableModel, VariableModel } from '@grafana/data';
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
VizPanel,
|
||||
@@ -24,9 +24,9 @@ import {
|
||||
SceneDataLayers,
|
||||
SceneDataLayerProvider,
|
||||
SceneDataLayerControls,
|
||||
AdHocFilterSet,
|
||||
TextBoxVariable,
|
||||
UserActionEvent,
|
||||
AdHocFiltersVariable,
|
||||
} from '@grafana/scenes';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking';
|
||||
@@ -173,24 +173,11 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]):
|
||||
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
|
||||
let variables: SceneVariableSet | undefined = undefined;
|
||||
let layers: SceneDataLayerProvider[] = [];
|
||||
let filtersSets: AdHocFilterSet[] = [];
|
||||
|
||||
if (oldModel.templating?.list?.length) {
|
||||
const variableObjects = oldModel.templating.list
|
||||
.map((v) => {
|
||||
try {
|
||||
if (isAdhocVariable(v)) {
|
||||
filtersSets.push(
|
||||
new AdHocFilterSet({
|
||||
name: v.name,
|
||||
datasource: v.datasource,
|
||||
filters: v.filters ?? [],
|
||||
baseFilters: v.baseFilters ?? [],
|
||||
})
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return createSceneVariableFromVariableModel(v);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -282,7 +269,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
||||
: undefined,
|
||||
controls: [
|
||||
new DashboardControls({
|
||||
variableControls: [new VariableValueSelectors({}), ...filtersSets, new SceneDataLayerControls()],
|
||||
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
||||
timeControls: [
|
||||
new SceneTimePicker({}),
|
||||
new SceneRefreshPicker({
|
||||
@@ -305,6 +292,18 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
||||
name: variable.name,
|
||||
label: variable.label,
|
||||
};
|
||||
if (variable.type === 'adhoc') {
|
||||
return new AdHocFiltersVariable({
|
||||
...commonProperties,
|
||||
description: variable.description,
|
||||
skipUrlSync: variable.skipUrlSync,
|
||||
hide: variable.hide,
|
||||
datasource: variable.datasource,
|
||||
applyMode: 'auto',
|
||||
filters: variable.filters ?? [],
|
||||
baseFilters: variable.baseFilters ?? [],
|
||||
});
|
||||
}
|
||||
if (variable.type === 'custom') {
|
||||
return new CustomVariable({
|
||||
...commonProperties,
|
||||
@@ -480,8 +479,6 @@ export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike {
|
||||
});
|
||||
}
|
||||
|
||||
const isAdhocVariable = (v: VariableModel): v is AdHocVariableModel => v.type === 'adhoc';
|
||||
|
||||
const getLimitedDescriptionReporter = () => {
|
||||
const reportedPanels: string[] = [];
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
VizPanel,
|
||||
SceneDataTransformer,
|
||||
SceneVariableSet,
|
||||
AdHocFilterSet,
|
||||
LocalValueVariable,
|
||||
SceneRefreshPicker,
|
||||
} from '@grafana/scenes';
|
||||
@@ -101,17 +100,6 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
|
||||
refreshIntervals = control.state.intervals;
|
||||
}
|
||||
}
|
||||
|
||||
const variableControls = state.controls[0].state.variableControls;
|
||||
for (const control of variableControls) {
|
||||
if (control instanceof AdHocFilterSet) {
|
||||
variables.push({
|
||||
name: control.state.name!,
|
||||
type: 'adhoc',
|
||||
datasource: control.state.datasource,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.$behaviors) {
|
||||
|
||||
@@ -10,7 +10,14 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { setPluginImportUtils, setRunRequest } from '@grafana/runtime';
|
||||
import { SceneVariableSet, CustomVariable, SceneGridItem, SceneGridLayout, VizPanel } from '@grafana/scenes';
|
||||
import {
|
||||
SceneVariableSet,
|
||||
CustomVariable,
|
||||
SceneGridItem,
|
||||
SceneGridLayout,
|
||||
VizPanel,
|
||||
AdHocFiltersVariable,
|
||||
} from '@grafana/scenes';
|
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
|
||||
|
||||
@@ -97,11 +104,16 @@ describe('VariablesEditView', () => {
|
||||
query: 'test3, test4, $customVar',
|
||||
value: 'test3',
|
||||
},
|
||||
{
|
||||
type: 'adhoc',
|
||||
name: 'adhoc',
|
||||
},
|
||||
];
|
||||
const variables = variableView.getVariables();
|
||||
expect(variables).toHaveLength(2);
|
||||
expect(variables).toHaveLength(3);
|
||||
expect(variables[0].state).toMatchObject(expectedVariables[0]);
|
||||
expect(variables[1].state).toMatchObject(expectedVariables[1]);
|
||||
expect(variables[2].state).toMatchObject(expectedVariables[2]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,7 +129,7 @@ describe('VariablesEditView', () => {
|
||||
const variables = variableView.getVariables();
|
||||
const variable = variables[0];
|
||||
variableView.onDuplicated(variable.state.name);
|
||||
expect(variableView.getVariables()).toHaveLength(3);
|
||||
expect(variableView.getVariables()).toHaveLength(4);
|
||||
expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar');
|
||||
});
|
||||
|
||||
@@ -125,7 +137,7 @@ describe('VariablesEditView', () => {
|
||||
const variableIdentifier = 'customVar';
|
||||
variableView.onDuplicated(variableIdentifier);
|
||||
variableView.onDuplicated(variableIdentifier);
|
||||
expect(variableView.getVariables()).toHaveLength(4);
|
||||
expect(variableView.getVariables()).toHaveLength(5);
|
||||
expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar_1');
|
||||
expect(variableView.getVariables()[2].state.name).toBe('copy_of_customVar');
|
||||
});
|
||||
@@ -133,7 +145,7 @@ describe('VariablesEditView', () => {
|
||||
it('should delete a variable', () => {
|
||||
const variableIdentifier = 'customVar';
|
||||
variableView.onDelete(variableIdentifier);
|
||||
expect(variableView.getVariables()).toHaveLength(1);
|
||||
expect(variableView.getVariables()).toHaveLength(2);
|
||||
expect(variableView.getVariables()[0].state.name).toBe('customVar2');
|
||||
});
|
||||
|
||||
@@ -147,7 +159,7 @@ describe('VariablesEditView', () => {
|
||||
|
||||
it('should keep the same order of variables with invalid indexes', () => {
|
||||
const fromIndex = 0;
|
||||
const toIndex = 2;
|
||||
const toIndex = 3;
|
||||
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
@@ -163,11 +175,11 @@ describe('VariablesEditView', () => {
|
||||
const previousVariable = variableView.getVariables()[1] as CustomVariable;
|
||||
variableView.onEdit('customVar2');
|
||||
|
||||
variableView.onTypeChange('constant');
|
||||
expect(variableView.getVariables()).toHaveLength(2);
|
||||
variableView.onTypeChange('adhoc');
|
||||
expect(variableView.getVariables()).toHaveLength(3);
|
||||
const variable = variableView.getVariables()[1];
|
||||
expect(variable).not.toBe(previousVariable);
|
||||
expect(variable.state.type).toBe('constant');
|
||||
expect(variable.state.type).toBe('adhoc');
|
||||
|
||||
// Values to be kept between the old and new variable
|
||||
expect(variable.state.name).toEqual(previousVariable.state.name);
|
||||
@@ -184,9 +196,9 @@ describe('VariablesEditView', () => {
|
||||
|
||||
it('should add default new query variable when onAdd is called', () => {
|
||||
variableView.onAdd();
|
||||
expect(variableView.getVariables()).toHaveLength(3);
|
||||
expect(variableView.getVariables()[2].state.name).toBe('query0');
|
||||
expect(variableView.getVariables()[2].state.type).toBe('query');
|
||||
expect(variableView.getVariables()).toHaveLength(4);
|
||||
expect(variableView.getVariables()[3].state.name).toBe('query0');
|
||||
expect(variableView.getVariables()[3].state.type).toBe('query');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -266,6 +278,17 @@ async function buildTestScene() {
|
||||
value: '$customVar',
|
||||
text: '$customVar',
|
||||
}),
|
||||
new AdHocFiltersVariable({
|
||||
type: 'adhoc',
|
||||
name: 'adhoc',
|
||||
filters: [
|
||||
{
|
||||
key: 'test',
|
||||
operator: '=',
|
||||
value: 'testValue',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
body: new SceneGridLayout({
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
SceneObjectBase,
|
||||
SceneVariable,
|
||||
SceneVariables,
|
||||
sceneGraph,
|
||||
AdHocFilterSet,
|
||||
} from '@grafana/scenes';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneVariable, SceneVariables, sceneGraph } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
@@ -46,7 +39,7 @@ export class VariablesEditView extends SceneObjectBase<VariablesEditViewState> i
|
||||
return variables.findIndex((variable) => variable.state.name === identifier);
|
||||
};
|
||||
|
||||
private replaceEditVariable = (newVariable: SceneVariable | AdHocFilterSet) => {
|
||||
private replaceEditVariable = (newVariable: SceneVariable) => {
|
||||
// Find the index of the variable to be deleted
|
||||
const variableIndex = this.state.editIndex ?? -1;
|
||||
const { variables } = this.getVariableSet().state;
|
||||
@@ -58,18 +51,10 @@ export class VariablesEditView extends SceneObjectBase<VariablesEditViewState> i
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVariable instanceof AdHocFilterSet) {
|
||||
// TODO: Update controls in adding this fiter set to the dashboard
|
||||
} else {
|
||||
const updatedVariables = [
|
||||
...variables.slice(0, variableIndex),
|
||||
newVariable,
|
||||
...variables.slice(variableIndex + 1),
|
||||
];
|
||||
const updatedVariables = [...variables.slice(0, variableIndex), newVariable, ...variables.slice(variableIndex + 1)];
|
||||
|
||||
// Update the state or the variables array
|
||||
this.getVariableSet().setState({ variables: updatedVariables });
|
||||
}
|
||||
// Update the state or the variables array
|
||||
this.getVariableSet().setState({ variables: updatedVariables });
|
||||
};
|
||||
|
||||
public onDelete = (identifier: string) => {
|
||||
@@ -158,12 +143,9 @@ export class VariablesEditView extends SceneObjectBase<VariablesEditViewState> i
|
||||
const variableIndex = variables.length;
|
||||
//add the new variable to the end of the array
|
||||
const defaultNewVariable = getVariableDefault(variables);
|
||||
if (defaultNewVariable instanceof AdHocFilterSet) {
|
||||
// TODO: Update controls in adding this fiter set to the dashboard
|
||||
} else {
|
||||
this.getVariableSet().setState({ variables: [...this.getVariables(), defaultNewVariable] });
|
||||
this.setState({ editIndex: variableIndex });
|
||||
}
|
||||
|
||||
this.getVariableSet().setState({ variables: [...this.getVariables(), defaultNewVariable] });
|
||||
this.setState({ editIndex: variableIndex });
|
||||
};
|
||||
|
||||
public onTypeChange = (type: EditableVariableType) => {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { act, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
||||
|
||||
import { AdHocVariableForm } from './AdHocVariableForm';
|
||||
|
||||
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,
|
||||
getList: () => [defaultDatasource, promDatasource],
|
||||
getInstanceSettings: () => ({ ...defaultDatasource }),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('AdHocVariableForm', () => {
|
||||
it('should render the form with the provided data source', async () => {
|
||||
const onDataSourceChange = jest.fn();
|
||||
const { renderer } = await setup({
|
||||
datasource: defaultDatasource,
|
||||
onDataSourceChange,
|
||||
infoText: 'Test Info',
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const onDataSourceChange = jest.fn();
|
||||
const { renderer, user } = await setup({
|
||||
datasource: defaultDatasource,
|
||||
onDataSourceChange,
|
||||
infoText: 'Test Info',
|
||||
});
|
||||
|
||||
// Simulate changing the data source
|
||||
await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2));
|
||||
await user.click(renderer.getByText(/prom/i));
|
||||
|
||||
expect(onDataSourceChange).toHaveBeenCalledTimes(1);
|
||||
expect(onDataSourceChange).toHaveBeenCalledWith(promDatasource, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
async function setup(props?: React.ComponentProps<typeof AdHocVariableForm>) {
|
||||
return {
|
||||
renderer: await act(() => render(<AdHocVariableForm onDataSourceChange={jest.fn()} {...props} />)),
|
||||
user: userEvent.setup(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { Alert, Field } from '@grafana/ui';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
|
||||
import { VariableLegend } from './VariableLegend';
|
||||
|
||||
interface AdHocVariableFormProps {
|
||||
datasource?: DataSourceRef;
|
||||
onDataSourceChange: (dsSettings: DataSourceInstanceSettings) => void;
|
||||
infoText?: string;
|
||||
}
|
||||
|
||||
export function AdHocVariableForm({ datasource, infoText, onDataSourceChange }: AdHocVariableFormProps) {
|
||||
return (
|
||||
<>
|
||||
<VariableLegend>Ad-hoc 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.AdHocFiltersVariable.infoText}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { render, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import {
|
||||
FieldType,
|
||||
LoadingState,
|
||||
PanelData,
|
||||
VariableSupportType,
|
||||
getDefaultTimeRange,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { setRunRequest } from '@grafana/runtime/src';
|
||||
import { AdHocFiltersVariable } from '@grafana/scenes';
|
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
|
||||
|
||||
import { AdHocFiltersVariableEditor } from './AdHocFiltersVariableEditor';
|
||||
|
||||
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 }),
|
||||
}),
|
||||
}));
|
||||
|
||||
const runRequestMock = jest.fn().mockReturnValue(
|
||||
of<PanelData>({
|
||||
state: LoadingState.Done,
|
||||
series: [
|
||||
toDataFrame({
|
||||
fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }],
|
||||
}),
|
||||
],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
})
|
||||
);
|
||||
|
||||
setRunRequest(runRequestMock);
|
||||
|
||||
describe('AdHocFiltersVariableEditor', () => {
|
||||
it('renders AdHocVariableForm with correct props', async () => {
|
||||
const { renderer } = await setup();
|
||||
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('This data source does not support ad hoc filters 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' });
|
||||
});
|
||||
});
|
||||
|
||||
async function setup(props?: React.ComponentProps<typeof AdHocFiltersVariableEditor>) {
|
||||
const onRunQuery = jest.fn();
|
||||
const variable = new AdHocFiltersVariable({
|
||||
name: 'adhocVariable',
|
||||
type: 'adhoc',
|
||||
label: 'Ad hoc filters',
|
||||
description: 'Ad hoc filters are applied automatically to all queries that target this data source',
|
||||
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type },
|
||||
filters: [
|
||||
{
|
||||
key: 'test',
|
||||
operator: '=',
|
||||
value: 'testValue',
|
||||
},
|
||||
],
|
||||
baseFilters: [
|
||||
{
|
||||
key: 'baseTest',
|
||||
operator: '=',
|
||||
value: 'baseTestValue',
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
renderer: await act(() =>
|
||||
render(<AdHocFiltersVariableEditor variable={variable} onRunQuery={onRunQuery} {...props} />)
|
||||
),
|
||||
variable,
|
||||
user: userEvent.setup(),
|
||||
mocks: { onRunQuery },
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,40 @@
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { AdHocFiltersVariable } from '@grafana/scenes';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
|
||||
import { AdHocVariableForm } from '../components/AdHocVariableForm';
|
||||
|
||||
interface AdHocFiltersVariableEditorProps {
|
||||
variable: AdHocFiltersVariable;
|
||||
onChange: (variable: AdHocFiltersVariable) => void;
|
||||
onRunQuery: (variable: AdHocFiltersVariable) => void;
|
||||
}
|
||||
|
||||
export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProps) {
|
||||
return <div>AdHocFiltersVariableEditor</div>;
|
||||
const { variable } = props;
|
||||
const datasourceRef = variable.useState().datasource ?? undefined;
|
||||
|
||||
const { value: datasourceSettings } = useAsync(async () => {
|
||||
return await getDataSourceSrv().get(datasourceRef);
|
||||
}, [datasourceRef]);
|
||||
|
||||
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.';
|
||||
|
||||
const onDataSourceChange = (ds: DataSourceInstanceSettings) => {
|
||||
const dsRef: DataSourceRef = {
|
||||
uid: ds.uid,
|
||||
type: ds.type,
|
||||
};
|
||||
|
||||
variable.setState({
|
||||
datasource: dsRef,
|
||||
});
|
||||
};
|
||||
|
||||
return <AdHocVariableForm datasource={datasourceRef} infoText={message} onDataSourceChange={onDataSourceChange} />;
|
||||
}
|
||||
|
||||
@@ -172,10 +172,10 @@ describe('getVariableScene', () => {
|
||||
['adhoc', AdHocFiltersVariable],
|
||||
['groupby', GroupByVariable],
|
||||
['textbox', TextBoxVariable],
|
||||
])('should return the scene variable instance for the given editable variable type', () => {
|
||||
])('should return the scene variable instance for the given editable variable type', (type, instanceType) => {
|
||||
const initialState = { name: 'MyVariable' };
|
||||
const sceneVariable = getVariableScene('custom', initialState);
|
||||
expect(sceneVariable).toBeInstanceOf(CustomVariable);
|
||||
const sceneVariable = getVariableScene(type as EditableVariableType, initialState);
|
||||
expect(sceneVariable).toBeInstanceOf(instanceType);
|
||||
expect(sceneVariable.state.name).toBe(initialState.name);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
IntervalVariable,
|
||||
TextBoxVariable,
|
||||
QueryVariable,
|
||||
AdHocFilterSet,
|
||||
GroupByVariable,
|
||||
SceneVariable,
|
||||
MultiValueVariable,
|
||||
AdHocFiltersVariable,
|
||||
SceneVariableState,
|
||||
} from '@grafana/scenes';
|
||||
import { VariableType } from '@grafana/schema';
|
||||
@@ -124,8 +124,7 @@ export function getVariableScene(type: EditableVariableType, initialState: Commo
|
||||
case 'datasource':
|
||||
return new DataSourceVariable(initialState);
|
||||
case 'adhoc':
|
||||
// TODO: Initialize properly AdHocFilterSet with initialState
|
||||
return new AdHocFilterSet({ name: initialState.name });
|
||||
return new AdHocFiltersVariable(initialState);
|
||||
case 'groupby':
|
||||
return new GroupByVariable(initialState);
|
||||
case 'textbox':
|
||||
|
||||
@@ -10,7 +10,7 @@ export function getVariablesCompatibility(sceneObject: SceneObject): TypedVariab
|
||||
// Sadly templateSrv.getVariables returns TypedVariableModel but sceneVariablesSetToVariables return persisted schema model
|
||||
// They look close to identical (differ in what is optional in some places).
|
||||
// The way templateSrv.getVariables is used it should not matter. it is mostly used to get names of all variables (for query editors).
|
||||
// So type and name are important. Maybe some external data sourcess also check current value so that is also important.
|
||||
// So type and name are important. Maybe some external data sources also check current value so that is also important.
|
||||
// @ts-expect-error
|
||||
return legacyModels;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ export class AddToFiltersGraphAction extends SceneObjectBase<AddToFiltersGraphAc
|
||||
|
||||
const labelName = Object.keys(labels)[0];
|
||||
|
||||
variable.state.set.setState({
|
||||
variable.setState({
|
||||
filters: [
|
||||
...variable.state.set.state.filters,
|
||||
...variable.state.filters,
|
||||
{
|
||||
key: labelName,
|
||||
operator: '=',
|
||||
|
||||
@@ -11,7 +11,7 @@ export function getLabelOptions(scenObject: SceneObject, variable: QueryVariable
|
||||
return [];
|
||||
}
|
||||
|
||||
const filters = labelFilters.state.set.state.filters;
|
||||
const filters = labelFilters.state.filters;
|
||||
|
||||
for (const option of variable.getOptionsForSelect()) {
|
||||
const filterExists = filters.find((f) => f.key === option.value);
|
||||
|
||||
@@ -137,7 +137,7 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
||||
// Add metric to adhoc filters baseFilter
|
||||
const filterVar = sceneGraph.lookupVariable(VAR_FILTERS, this);
|
||||
if (filterVar instanceof AdHocFiltersVariable) {
|
||||
filterVar.state.set.setState({
|
||||
filterVar.setState({
|
||||
baseFilters: getBaseFiltersForMetric(evt.payload),
|
||||
});
|
||||
}
|
||||
@@ -208,7 +208,7 @@ function getVariableSet(initialDS?: string, metric?: string, initialFilters?: Ad
|
||||
value: initialDS,
|
||||
pluginId: metric === LOGS_METRIC ? 'loki' : 'prometheus',
|
||||
}),
|
||||
AdHocFiltersVariable.create({
|
||||
new AdHocFiltersVariable({
|
||||
name: VAR_FILTERS,
|
||||
datasource: trailDS,
|
||||
layout: 'vertical',
|
||||
|
||||
@@ -23,7 +23,7 @@ export function DataTrailCard({ trail, onSelect, onDelete }: Props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filters = filtersVariable.state.set.state.filters;
|
||||
const filters = filtersVariable.state.filters;
|
||||
const dsValue = getDataSource(trail);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,13 @@ import { BOOKMARKED_TRAILS_KEY, RECENT_TRAILS_KEY } from '../shared';
|
||||
|
||||
import { SerializedTrail, getTrailStore } from './TrailStore';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getTemplateSrv: () => ({
|
||||
getAdhocFilters: jest.fn().mockReturnValue([{ key: 'origKey', operator: '=', value: '' }]),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TrailStore', () => {
|
||||
beforeAll(() => {
|
||||
let localStore: Record<string, string> = {};
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
||||
import { DataSourceType } from 'app/features/alerting/unified/utils/datasource';
|
||||
|
||||
import { adHocBuilder } from '../shared/testing/builders';
|
||||
|
||||
import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor';
|
||||
|
||||
const promDsMock = mockDataSource({
|
||||
name: 'Prometheus',
|
||||
type: DataSourceType.Prometheus,
|
||||
});
|
||||
|
||||
const lokiDsMock = mockDataSource({
|
||||
name: 'Loki',
|
||||
type: DataSourceType.Loki,
|
||||
});
|
||||
|
||||
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => {
|
||||
return {
|
||||
getDataSourceSrv: () => ({
|
||||
get: () => {
|
||||
return Promise.resolve(promDsMock);
|
||||
},
|
||||
getList: () => [promDsMock, lokiDsMock],
|
||||
getInstanceSettings: (v: string) => {
|
||||
if (v === 'Prometheus') {
|
||||
return promDsMock;
|
||||
}
|
||||
return lokiDsMock;
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const props = {
|
||||
extended: {
|
||||
dataSources: [
|
||||
{ text: 'Prometheus', value: null }, // default datasource
|
||||
{ text: 'Loki', value: { type: 'loki-ds', uid: 'abc' } },
|
||||
],
|
||||
} as ComponentProps<typeof AdHocVariableEditor>['extended'],
|
||||
variable: adHocBuilder().withId('adhoc').withRootStateKey('key').withName('adhoc').build(),
|
||||
onPropChange: jest.fn(),
|
||||
|
||||
// connected actions
|
||||
initAdHocVariableEditor: jest.fn(),
|
||||
changeVariableDatasource: jest.fn(),
|
||||
};
|
||||
|
||||
describe('AdHocVariableEditor', () => {
|
||||
beforeEach(() => {
|
||||
props.changeVariableDatasource.mockReset();
|
||||
});
|
||||
|
||||
it('has a datasource select menu', async () => {
|
||||
render(<AdHocVariableEditor {...props} />);
|
||||
|
||||
expect(await screen.getByTestId(selectors.components.DataSourcePicker.container)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls the callback when changing the datasource', async () => {
|
||||
render(<AdHocVariableEditor {...props} />);
|
||||
const selectEl = screen
|
||||
.getByTestId(selectors.components.DataSourcePicker.container)
|
||||
.getElementsByTagName('input')[0];
|
||||
await userEvent.click(selectEl);
|
||||
await userEvent.click(screen.getByText('Loki'));
|
||||
|
||||
expect(props.changeVariableDatasource).toBeCalledWith(
|
||||
{ type: 'adhoc', id: 'adhoc', rootStateKey: 'key' },
|
||||
{ type: 'loki', uid: 'mock-ds-3' }
|
||||
);
|
||||
});
|
||||
|
||||
it('renders informational text', () => {
|
||||
const extended = {
|
||||
...props.extended,
|
||||
infoText: "Here's a message that should help you",
|
||||
};
|
||||
render(<AdHocVariableEditor {...props} extended={extended} />);
|
||||
|
||||
const alert = screen.getByText("Here's a message that should help you");
|
||||
expect(alert).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -2,11 +2,9 @@ import React, { PureComponent } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data';
|
||||
import { Alert, Field } from '@grafana/ui';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { AdHocVariableForm } from 'app/features/dashboard-scene/settings/variables/components/AdHocVariableForm';
|
||||
import { StoreState } from 'app/types';
|
||||
|
||||
import { VariableLegend } from '../../dashboard-scene/settings/variables/components/VariableLegend';
|
||||
import { initialVariableEditorState } from '../editor/reducer';
|
||||
import { getAdhocVariableEditorState } from '../editor/selectors';
|
||||
import { VariableEditorProps } from '../editor/types';
|
||||
@@ -58,23 +56,13 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
|
||||
|
||||
render() {
|
||||
const { variable, extended } = this.props;
|
||||
const infoText = extended?.infoText ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VariableLegend>Ad-hoc options</VariableLegend>
|
||||
<Field label="Data source" htmlFor="data-source-picker">
|
||||
<DataSourcePicker
|
||||
current={variable.datasource}
|
||||
onChange={this.onDatasourceChanged}
|
||||
width={30}
|
||||
variables={true}
|
||||
noDefault
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{infoText ? <Alert title={infoText} severity="info" /> : null}
|
||||
</>
|
||||
<AdHocVariableForm
|
||||
datasource={variable.datasource ?? undefined}
|
||||
onDataSourceChange={this.onDatasourceChanged}
|
||||
infoText={extended?.infoText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -4020,9 +4020,9 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes@npm:^2.6.5":
|
||||
version: 2.6.5
|
||||
resolution: "@grafana/scenes@npm:2.6.5"
|
||||
"@grafana/scenes@npm:^3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "@grafana/scenes@npm:3.2.1"
|
||||
dependencies:
|
||||
"@grafana/e2e-selectors": "npm:10.0.2"
|
||||
react-grid-layout: "npm:1.3.4"
|
||||
@@ -4034,7 +4034,7 @@ __metadata:
|
||||
"@grafana/runtime": 10.0.3
|
||||
"@grafana/schema": 10.0.3
|
||||
"@grafana/ui": 10.0.3
|
||||
checksum: 10/68fe91a5a0c8f80b679126f3525b74b29ce3f9ad92bc558eaaf39693235137348b90796c2b02aa7c1c7929586e60df6024e139993a416ef37d4c875e548dc855
|
||||
checksum: 10/5e93c0dcdfbd7cfed977d650fb0744c9b8107b1c92af2ae6d6e9f2f61bb9ba7f3cb5ad394fa3389aaef0d892a18c6a12fedf5454a4210bdcc6797a272ddbc625
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -18026,7 +18026,7 @@ __metadata:
|
||||
"@grafana/o11y-ds-frontend": "workspace:*"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:^2.6.5"
|
||||
"@grafana/scenes": "npm:^3.2.1"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/tsconfig": "npm:^1.3.0-rc1"
|
||||
|
||||
Reference in New Issue
Block a user