DashboardDS: Fix datasource annotations not hiding on toggle hide (#115024)
* fix dashboard datasource annotations not hiding on toggle hide * cleanup
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
FieldType,
|
||||
DataFrame,
|
||||
AdHocVariableFilter,
|
||||
DataTopic,
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test';
|
||||
import { setPluginImportUtils } from '@grafana/runtime';
|
||||
@@ -774,8 +775,124 @@ describe('DashboardDatasource', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Annotation Handling', () => {
|
||||
it('should NOT include annotations from source panel in regular query response', async () => {
|
||||
const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 });
|
||||
|
||||
let rsp: DataQueryResponse | undefined;
|
||||
observable.subscribe({ next: (data) => (rsp = data) });
|
||||
|
||||
// Should only have series data, no annotations
|
||||
expect(rsp?.data.length).toBe(1);
|
||||
expect(rsp?.data[0].fields[0].values).toEqual([1, 2, 3]);
|
||||
|
||||
// Verify no annotation frames are included
|
||||
const annotationFrames = rsp?.data.filter((frame) => frame.meta?.dataTopic === DataTopic.Annotations);
|
||||
expect(annotationFrames?.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return annotations as series when query topic is DataTopic.Annotations', async () => {
|
||||
const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1, topic: DataTopic.Annotations });
|
||||
|
||||
let rsp: DataQueryResponse | undefined;
|
||||
observable.subscribe({ next: (data) => (rsp = data) });
|
||||
|
||||
// Should return annotation data as series (with dataTopic changed to Series)
|
||||
expect(rsp?.data.length).toBe(1);
|
||||
expect(rsp?.data[0].name).toBe('Test Annotation');
|
||||
// The dataTopic should be changed to Series when querying for annotations
|
||||
expect(rsp?.data[0].meta?.dataTopic).toBe(DataTopic.Series);
|
||||
});
|
||||
|
||||
it('should not leak annotations when source panel has annotations and toggle is off', async () => {
|
||||
// This test ensures that when annotations are toggled off at the dashboard level,
|
||||
// DashboardDS panels don't continue showing them from the source panel's cached data
|
||||
const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 });
|
||||
|
||||
let rsp: DataQueryResponse | undefined;
|
||||
observable.subscribe({ next: (data) => (rsp = data) });
|
||||
|
||||
// Verify that annotations from source panel are NOT included in response
|
||||
// This is critical for annotation toggle to work correctly on DashboardDS panels
|
||||
const hasAnnotations = rsp?.data.some(
|
||||
(frame) => frame.meta?.dataTopic === DataTopic.Annotations || frame.name === 'Test Annotation'
|
||||
);
|
||||
expect(hasAnnotations).toBe(false);
|
||||
});
|
||||
|
||||
it('should only return series data even when source has both series and annotations', async () => {
|
||||
const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 });
|
||||
|
||||
let rsp: DataQueryResponse | undefined;
|
||||
observable.subscribe({ next: (data) => (rsp = data) });
|
||||
|
||||
// All returned frames should be series data, not annotations
|
||||
rsp?.data.forEach((frame) => {
|
||||
expect(frame.meta?.dataTopic).not.toBe(DataTopic.Annotations);
|
||||
});
|
||||
|
||||
// Should have the series data from the source panel
|
||||
expect(rsp?.data[0].fields[0].values).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupWithAnnotations(query: DashboardQuery, requestId?: string) {
|
||||
const annotationFrame: DataFrame = {
|
||||
name: 'Test Annotation',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000], config: {} },
|
||||
{ name: 'text', type: FieldType.string, values: ['Annotation 1', 'Annotation 2'], config: {} },
|
||||
],
|
||||
length: 2,
|
||||
meta: {
|
||||
dataTopic: DataTopic.Annotations,
|
||||
},
|
||||
};
|
||||
|
||||
const sourceData = new SceneDataTransformer({
|
||||
$data: new SceneDataNode({
|
||||
data: {
|
||||
series: [arrayToDataFrame([1, 2, 3])],
|
||||
annotations: [annotationFrame],
|
||||
state: LoadingState.Done,
|
||||
timeRange: getDefaultTimeRange(),
|
||||
},
|
||||
}),
|
||||
transformations: [],
|
||||
});
|
||||
|
||||
const scene = new SceneFlexLayout({
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
body: new VizPanel({
|
||||
key: getVizPanelKeyForPanelId(1),
|
||||
$data: sourceData,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const ds = new DashboardDatasource({} as DataSourceInstanceSettings);
|
||||
|
||||
const observable = ds.query({
|
||||
timezone: 'utc',
|
||||
targets: [query],
|
||||
requestId: requestId ?? '',
|
||||
interval: '',
|
||||
intervalMs: 0,
|
||||
range: getDefaultTimeRange(),
|
||||
scopedVars: {
|
||||
__sceneObject: new SafeSerializableSceneObject(scene),
|
||||
},
|
||||
app: '',
|
||||
startTime: 0,
|
||||
});
|
||||
|
||||
return { observable, sourceData };
|
||||
}
|
||||
|
||||
function setup(query: DashboardQuery, requestId?: string) {
|
||||
const sourceData = new SceneDataTransformer({
|
||||
$data: new SceneDataNode({
|
||||
|
||||
@@ -122,8 +122,9 @@ export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
|
||||
query: DashboardQuery,
|
||||
filters: AdHocVariableFilter[]
|
||||
): DataFrame[] {
|
||||
const annotations = data.annotations ?? [];
|
||||
// When querying for annotations topic, return the source panel's annotations as series data
|
||||
if (query.topic === DataTopic.Annotations) {
|
||||
const annotations = data.annotations ?? [];
|
||||
return annotations.map((frame) => ({
|
||||
...frame,
|
||||
meta: {
|
||||
@@ -131,34 +132,34 @@ export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
|
||||
dataTopic: DataTopic.Series,
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
const series = data.series.map((s) => {
|
||||
return {
|
||||
...s,
|
||||
fields: s.fields.map((field: Field) => ({
|
||||
...field,
|
||||
config: {
|
||||
...field.config,
|
||||
// Enable AdHoc filtering for string and numeric fields only when per-panel setting is enabled
|
||||
filterable: query.adHocFiltersEnabled
|
||||
? field.type === FieldType.string || field.type === FieldType.number
|
||||
: field.config.filterable,
|
||||
},
|
||||
state: {
|
||||
...field.state,
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
if (!query.adHocFiltersEnabled || filters.length === 0) {
|
||||
return [...series, ...annotations];
|
||||
}
|
||||
|
||||
// Apply AdHoc filters to series data
|
||||
const filteredSeries = series.map((frame) => this.applyAdHocFilters(frame, filters));
|
||||
return [...filteredSeries, ...annotations];
|
||||
}
|
||||
|
||||
// For regular queries, only return series data
|
||||
const series = data.series.map((s) => {
|
||||
return {
|
||||
...s,
|
||||
fields: s.fields.map((field: Field) => ({
|
||||
...field,
|
||||
config: {
|
||||
...field.config,
|
||||
// Enable AdHoc filtering for string and numeric fields only when per-panel setting is enabled
|
||||
filterable: query.adHocFiltersEnabled
|
||||
? field.type === FieldType.string || field.type === FieldType.number
|
||||
: field.config.filterable,
|
||||
},
|
||||
state: {
|
||||
...field.state,
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
if (!query.adHocFiltersEnabled || filters.length === 0) {
|
||||
return series;
|
||||
}
|
||||
|
||||
// Apply AdHoc filters to series data
|
||||
return series.map((frame) => this.applyAdHocFilters(frame, filters));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user