Dashboards: Fix adhoc filter click when panel has no panel-level datasource (#115576)
* V2: Panel datasource is defined only for mixed ds * if getDatasourceFromQueryRunner only returns ds.type, resolve to full ds ref throgh ds service --------- Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
This commit is contained in:
co-authored by
Haris Rozajac
parent
0b58cd3900
commit
30ad61e0e9
@@ -1,10 +1,10 @@
|
||||
import { AdHocVariableModel, EventBusSrv, GroupByVariableModel, VariableModel } from '@grafana/data';
|
||||
import { BackendSrv, config, setBackendSrv } from '@grafana/runtime';
|
||||
import { GroupByVariable, sceneGraph } from '@grafana/scenes';
|
||||
import { GroupByVariable, sceneGraph, SceneQueryRunner } from '@grafana/scenes';
|
||||
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { findVizPanelByKey } from '../utils/utils';
|
||||
import { findVizPanelByKey, getQueryRunnerFor } from '../utils/utils';
|
||||
|
||||
import { getAdHocFilterVariableFor, setDashboardPanelContext } from './setDashboardPanelContext';
|
||||
|
||||
@@ -159,6 +159,23 @@ describe('setDashboardPanelContext', () => {
|
||||
// Verify existing filter value updated
|
||||
expect(variable.state.filters[1].operator).toBe('!=');
|
||||
});
|
||||
|
||||
it('Should use existing adhoc filter when panel has no panel-level datasource because queries have all the same datasources (v2 behavior)', () => {
|
||||
const { scene, context } = buildTestScene({ existingFilterVariable: true, panelDatasourceUndefined: true });
|
||||
|
||||
const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' });
|
||||
variable.setState({ filters: [] });
|
||||
|
||||
context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' });
|
||||
|
||||
// Should use the existing adhoc filter variable, not create a new one
|
||||
expect(variable.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]);
|
||||
|
||||
// Verify no new adhoc variables were created
|
||||
const variables = sceneGraph.getVariables(scene);
|
||||
const adhocVars = variables.state.variables.filter((v) => v.state.type === 'adhoc');
|
||||
expect(adhocVars.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFiltersBasedOnGrouping', () => {
|
||||
@@ -312,6 +329,7 @@ interface SceneOptions {
|
||||
existingFilterVariable?: boolean;
|
||||
existingGroupByVariable?: boolean;
|
||||
groupByDatasourceUid?: string;
|
||||
panelDatasourceUndefined?: boolean;
|
||||
}
|
||||
|
||||
function buildTestScene(options: SceneOptions) {
|
||||
@@ -385,6 +403,19 @@ function buildTestScene(options: SceneOptions) {
|
||||
});
|
||||
|
||||
const vizPanel = findVizPanelByKey(scene, 'panel-4')!;
|
||||
|
||||
// Simulate v2 dashboard behavior where non-mixed panels don't have panel-level datasource
|
||||
// but the queries have their own datasources
|
||||
if (options.panelDatasourceUndefined) {
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
if (queryRunner instanceof SceneQueryRunner) {
|
||||
queryRunner.setState({
|
||||
datasource: undefined,
|
||||
queries: [{ refId: 'A', datasource: { uid: 'my-ds-uid', type: 'prometheus' } }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const context: PanelContext = {
|
||||
eventBus: new EventBusSrv(),
|
||||
eventsScope: 'global',
|
||||
|
||||
@@ -6,7 +6,12 @@ import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||
import { annotationServer } from 'app/features/annotations/api';
|
||||
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
||||
import {
|
||||
getDashboardSceneFor,
|
||||
getDatasourceFromQueryRunner,
|
||||
getPanelIdForVizPanel,
|
||||
getQueryRunnerFor,
|
||||
} from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
|
||||
@@ -121,7 +126,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
context.eventBus.publish(new AnnotationChangeEvent({ id }));
|
||||
};
|
||||
|
||||
context.onAddAdHocFilter = (newFilter: AdHocFilterItem) => {
|
||||
context.onAddAdHocFilter = async (newFilter: AdHocFilterItem) => {
|
||||
const dashboard = getDashboardSceneFor(vizPanel);
|
||||
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
@@ -129,7 +134,19 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
return;
|
||||
}
|
||||
|
||||
const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource);
|
||||
let datasource = getDatasourceFromQueryRunner(queryRunner);
|
||||
|
||||
// If the datasource is type-only (e.g. it's possible that only group is set in V2 schema queries)
|
||||
// we need to resolve it to a full datasource
|
||||
if (datasource && !datasource.uid) {
|
||||
const datasourceToLoad = await getDataSourceSrv().get(datasource);
|
||||
datasource = {
|
||||
uid: datasourceToLoad.uid,
|
||||
type: datasourceToLoad.type,
|
||||
};
|
||||
}
|
||||
|
||||
const filterVar = getAdHocFilterVariableFor(dashboard, datasource);
|
||||
updateAdHocFilterVariable(filterVar, newFilter);
|
||||
};
|
||||
|
||||
@@ -141,7 +158,8 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
return [];
|
||||
}
|
||||
|
||||
const groupByVar = getGroupByVariableFor(dashboard, queryRunner.state.datasource);
|
||||
const datasource = getDatasourceFromQueryRunner(queryRunner);
|
||||
const groupByVar = getGroupByVariableFor(dashboard, datasource);
|
||||
|
||||
if (!groupByVar) {
|
||||
return [];
|
||||
@@ -158,7 +176,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
.filter((item) => item !== undefined);
|
||||
};
|
||||
|
||||
context.onAddAdHocFilters = (items: AdHocFilterItem[]) => {
|
||||
context.onAddAdHocFilters = async (items: AdHocFilterItem[]) => {
|
||||
const dashboard = getDashboardSceneFor(vizPanel);
|
||||
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
@@ -166,7 +184,18 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
return;
|
||||
}
|
||||
|
||||
const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource);
|
||||
let datasource = getDatasourceFromQueryRunner(queryRunner);
|
||||
|
||||
// If the datasource is type-only (e.g. it's possible that only group is set in V2 schema queries)
|
||||
// we need to resolve it to a full datasource
|
||||
if (datasource && !datasource.uid) {
|
||||
const datasourceToLoad = await getDataSourceSrv().get(datasource);
|
||||
datasource = {
|
||||
uid: datasourceToLoad.uid,
|
||||
type: datasourceToLoad.type,
|
||||
};
|
||||
}
|
||||
const filterVar = getAdHocFilterVariableFor(dashboard, datasource);
|
||||
bulkUpdateAdHocFiltersVariable(filterVar, items);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { AdHocFiltersVariable, GroupByVariable, sceneGraph, SceneObject, SceneQueryRunner } from '@grafana/scenes';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
|
||||
import { getDatasourceFromQueryRunner } from './utils';
|
||||
|
||||
export function verifyDrilldownApplicability(
|
||||
sourceObject: SceneObject,
|
||||
queriesDataSource: DataSourceRef | undefined,
|
||||
@@ -26,7 +28,7 @@ export async function getDrilldownApplicability(
|
||||
return;
|
||||
}
|
||||
|
||||
const datasource = queryRunner.state.datasource;
|
||||
const datasource = getDatasourceFromQueryRunner(queryRunner);
|
||||
const queries = queryRunner.state.data?.request?.targets;
|
||||
|
||||
const ds = await getDataSourceSrv().get(datasource?.uid);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sceneGraph, VizPanel } from '@grafana/scenes';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getExploreUrl } from 'app/core/utils/explore';
|
||||
|
||||
import { getQueryRunnerFor } from './utils';
|
||||
import { getDatasourceFromQueryRunner, getQueryRunnerFor } from './utils';
|
||||
|
||||
export function getViewPanelUrl(vizPanel: VizPanel) {
|
||||
return locationUtil.getUrlForPartial(locationService.getLocation(), {
|
||||
@@ -27,10 +27,11 @@ export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise<string | u
|
||||
}
|
||||
|
||||
const timeRange = sceneGraph.getTimeRange(vizPanel);
|
||||
const datasource = getDatasourceFromQueryRunner(queryRunner);
|
||||
|
||||
return getExploreUrl({
|
||||
queries: queryRunner.state.queries,
|
||||
dsRef: queryRunner.state.datasource,
|
||||
dsRef: datasource,
|
||||
timeRange: timeRange.state.value,
|
||||
scopedVars: { __sceneObject: { value: vizPanel } },
|
||||
adhocFilters: queryRunner.state.data?.request?.filters,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDataSourceRef, IntervalVariableModel } from '@grafana/data';
|
||||
import { DataSourceRef, getDataSourceRef, IntervalVariableModel } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
import {
|
||||
@@ -237,6 +237,26 @@ export function getQueryRunnerFor(sceneObject: SceneObject | undefined): SceneQu
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the datasource from a query runner.
|
||||
* When no panel-level datasource is set, it means all queries use the same datasource,
|
||||
* so we extract the datasource from the first query.
|
||||
*/
|
||||
export function getDatasourceFromQueryRunner(queryRunner: SceneQueryRunner): DataSourceRef | null | undefined {
|
||||
// Panel-level datasource is set for mixed datasource panels
|
||||
if (queryRunner.state.datasource) {
|
||||
return queryRunner.state.datasource;
|
||||
}
|
||||
|
||||
// No panel-level datasource means all queries share the same datasource
|
||||
const firstQuery = queryRunner.state.queries?.[0];
|
||||
if (firstQuery?.datasource) {
|
||||
return firstQuery.datasource;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getDashboardSceneFor(sceneObject: SceneObject): DashboardScene {
|
||||
const root = sceneObject.getRoot();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user