[v11.2.x] Dashboard: Fix Annotation runtime error when a data source does not support annotations (#92830)

Dashboard: Fix Annotation runtime error when a data source does not support annotations (#92504)

When data source does not support annotations, shows an inline Error in Annotation Settings page

(cherry picked from commit f8cd448441)
This commit is contained in:
Alexa V
2024-09-02 18:08:59 +02:00
committed by GitHub
parent 16b5104b7c
commit 15e50cf3a0
6 changed files with 121 additions and 24 deletions
@@ -1,7 +1,18 @@
import { map, of } from 'rxjs';
import { MockDataSourceApi } from 'test/mocks/datasource_srv';
import { AnnotationQuery, DataQueryRequest, DataSourceApi, LoadingState, PanelData } from '@grafana/data';
import {
AnnotationQuery,
DataQueryRequest,
DataSourceApi,
DataSourceInstanceSettings,
DataSourcePluginMeta,
getDataSourceUID,
LoadingState,
PanelData,
} from '@grafana/data';
import { SceneGridLayout, SceneTimeRange, dataLayers } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
@@ -29,11 +40,49 @@ const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request:
);
});
const noAnnotationsDsInstanceSettings = {
name: 'noAnnotationsDs',
uid: 'noAnnotationsDs',
meta: {
annotations: false,
} as DataSourcePluginMeta,
readOnly: false,
type: 'noAnnotations',
} as DataSourceInstanceSettings;
const grafanaDsInstanceSettings = {
name: 'Grafana',
uid: '-- Grafana --',
meta: {
annotations: true,
} as DataSourcePluginMeta,
readOnly: false,
type: 'grafana',
} as DataSourceInstanceSettings;
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getDataSourceSrv: () => {
return {
getInstanceSettings: jest.fn().mockResolvedValue({ uid: 'ds1' }),
//return default datasource when no ref is provided
getInstanceSettings: (ref: DataSourceRef | string | null) => {
if (!ref) {
return new MockDataSourceApi(noAnnotationsDsInstanceSettings);
}
if (getDataSourceUID(ref) === '-- Grafana --') {
return new MockDataSourceApi(grafanaDsInstanceSettings);
}
return jest.fn().mockResolvedValue({ uid: 'ds1' });
},
get: (ref: DataSourceRef) => {
if (getDataSourceUID(ref) === 'noAnnotationsDs') {
return Promise.resolve(new MockDataSourceApi(noAnnotationsDsInstanceSettings));
}
if (getDataSourceUID(ref) === '-- Grafana --') {
return Promise.resolve(new MockDataSourceApi(grafanaDsInstanceSettings));
}
return Promise.resolve(new MockDataSourceApi('ds1'));
},
};
},
getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => {
@@ -52,17 +101,23 @@ describe('AnnotationsEditView', () => {
beforeEach(async () => {
const result = await buildTestScene();
annotationsView = result.annotationsView;
jest.spyOn(console, 'error').mockImplementation();
});
it('should return the correct urlKey', () => {
expect(annotationsView.getUrlKey()).toBe('annotations');
});
it('should return undefined when datasource does not support annotations', () => {
const ds = annotationsView.getDataSourceRefForAnnotation();
expect(ds).toBe(undefined);
expect(console.error).toHaveBeenCalledWith('Default datasource does not support annotations');
});
it('should add a new annotation and group it with the other annotations', () => {
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
expect(dataLayers?.state.annotationLayers.length).toBe(1);
annotationsView.onNew();
expect(dataLayers?.state.annotationLayers.length).toBe(2);
@@ -1,4 +1,4 @@
import { AnnotationQuery, NavModel, NavModelItem, PageLayoutType, getDataSourceRef } from '@grafana/data';
import { AnnotationQuery, getDataSourceRef, NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, VizPanel, dataLayers } from '@grafana/scenes';
import { Page } from 'app/core/components/Page/Page';
@@ -51,11 +51,23 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
return this._dashboard;
}
public onNew = () => {
public getDataSourceRefForAnnotation = () => {
// get current default datasource ref from instance settings
// null is passed to get the default datasource
const defaultInstanceDS = getDataSourceSrv().getInstanceSettings(null);
// check for an annotation flag in the plugin json to see if it supports annotations
if (!defaultInstanceDS || !defaultInstanceDS.meta.annotations) {
console.error('Default datasource does not support annotations');
return undefined;
}
return getDataSourceRef(defaultInstanceDS);
};
public onNew = async () => {
const newAnnotationQuery: AnnotationQuery = {
name: newAnnotationName,
enable: true,
datasource: getDataSourceRef(getDataSourceSrv().getInstanceSettings(null)!),
datasource: this.getDataSourceRefForAnnotation(),
iconColor: 'red',
};
@@ -69,7 +81,6 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
data.addAnnotationLayer(newAnnotation);
this.setState({ editIndex: data.state.annotationLayers.length - 1 });
};
@@ -14,8 +14,9 @@ import { selectors } from '@grafana/e2e-selectors';
import { config, getDataSourceSrv } from '@grafana/runtime';
import { VizPanel } from '@grafana/scenes';
import { AnnotationPanelFilter } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen';
import { Button, Checkbox, Field, FieldSet, Input, MultiSelect, Select, useStyles2, Stack } from '@grafana/ui';
import { Button, Checkbox, Field, FieldSet, Input, MultiSelect, Select, useStyles2, Stack, Alert } from '@grafana/ui';
import { ColorValueEditor } from 'app/core/components/OptionsUI/color';
import { Trans } from 'app/core/internationalization';
import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
@@ -183,6 +184,13 @@ export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate
<Field label="Data source" htmlFor="data-source-picker">
<DataSourcePicker annotations variables current={annotation.datasource} onChange={onDataSourceChange} />
</Field>
{!ds?.meta.annotations && (
<Alert title="No annotation support for this data source" severity="error">
<Trans i18nKey="errors.dashboard-settings.annotations.datasource">
The selected data source does not support annotations. Please select a different data source.
</Trans>
</Alert>
)}
<Field label="Enabled" description="When enabled the annotation query is issued every dashboard refresh">
<Checkbox
name="enable"
@@ -24,9 +24,11 @@ import {
Select,
useStyles2,
Stack,
Alert,
} from '@grafana/ui';
import { ColorValueEditor } from 'app/core/components/OptionsUI/color';
import config from 'app/core/config';
import { Trans } from 'app/core/internationalization';
import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor';
import { AngularEditorLoader } from 'app/features/dashboard-scene/settings/annotations/AngularEditorLoader';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
@@ -189,6 +191,13 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
<Field label="Data source" htmlFor="data-source-picker">
<DataSourcePicker annotations variables current={annotation.datasource} onChange={onDataSourceChange} />
</Field>
{!ds?.meta.annotations && (
<Alert title="No annotation support for this data source" severity="error">
<Trans i18nKey="errors.dashboard-settings.annotations.datasource">
The selected data source does not support annotations. Please select a different data source.
</Trans>
</Alert>
)}
<Field label="Enabled" description="When enabled the annotation query is issued every dashboard refresh">
<Checkbox name="enable" id="enable" value={annotation.enable} onChange={onChange} />
</Field>
+15 -8
View File
@@ -117,7 +117,7 @@
"parse-mode-warning-title": "Telegram messages are limited to 4096 UTF-8 characters."
},
"used-by_one": "Used by {{ count }} notification policy",
"used-by_other": "Used by {{ count }} notification policies"
"used-by_other": "Used by {{ count }} notification policy"
},
"grafana-rules": {
"export-rules": "Export rules",
@@ -234,15 +234,15 @@
},
"counts": {
"alertRule_one": "{{count}} alert rule",
"alertRule_other": "{{count}} alert rules",
"alertRule_other": "{{count}} alert rule",
"dashboard_one": "{{count}} dashboard",
"dashboard_other": "{{count}} dashboards",
"dashboard_other": "{{count}} dashboard",
"folder_one": "{{count}} folder",
"folder_other": "{{count}} folders",
"folder_other": "{{count}} folder",
"libraryPanel_one": "{{count}} library panel",
"libraryPanel_other": "{{count}} library panels",
"libraryPanel_other": "{{count}} library panel",
"total_one": "{{count}} item",
"total_other": "{{count}} items"
"total_other": "{{count}} item"
},
"dashboards-tree": {
"collapse-folder-button": "Collapse folder {{title}}",
@@ -675,6 +675,13 @@
"message": "No data sources found"
}
},
"errors": {
"dashboard-settings": {
"annotations": {
"datasource": "The selected data source does not support annotations. Please select a different data source."
}
}
},
"explore": {
"add-to-dashboard": "Add to dashboard",
"add-to-library-modal": {
@@ -1871,14 +1878,14 @@
"confirm-text": "Delete",
"delete-button": "Delete",
"delete-loading": "Deleting...",
"text_one": "This action will delete {{numberOfDashboards}} dashboard.",
"text_one": "This action will delete {{numberOfDashboards}} dashboards.",
"text_other": "This action will delete {{numberOfDashboards}} dashboards.",
"title": "Permanently Delete Dashboards"
},
"restore-modal": {
"restore-button": "Restore",
"restore-loading": "Restoring...",
"text_one": "This action will restore {{numberOfDashboards}} dashboard.",
"text_one": "This action will restore {{numberOfDashboards}} dashboards.",
"text_other": "This action will restore {{numberOfDashboards}} dashboards.",
"title": "Restore Dashboards"
}
+15 -8
View File
@@ -117,7 +117,7 @@
"parse-mode-warning-title": "Ŧęľęģřäm męşşäģęş äřę ľįmįŧęđ ŧő 4096 ŮŦF-8 čĥäřäčŧęřş."
},
"used-by_one": "Ůşęđ þy {{ count }} ʼnőŧįƒįčäŧįőʼn pőľįčy",
"used-by_other": "Ůşęđ þy {{ count }} ʼnőŧįƒįčäŧįőʼn pőľįčįęş"
"used-by_other": "Ůşęđ þy {{ count }} ʼnőŧįƒįčäŧįőʼn pőľįčy"
},
"grafana-rules": {
"export-rules": "Ēχpőřŧ řūľęş",
@@ -234,15 +234,15 @@
},
"counts": {
"alertRule_one": "{{count}} äľęřŧ řūľę",
"alertRule_other": "{{count}} äľęřŧ řūľęş",
"alertRule_other": "{{count}} äľęřŧ řūľę",
"dashboard_one": "{{count}} đäşĥþőäřđ",
"dashboard_other": "{{count}} đäşĥþőäřđş",
"dashboard_other": "{{count}} đäşĥþőäřđ",
"folder_one": "{{count}} ƒőľđęř",
"folder_other": "{{count}} ƒőľđęřş",
"folder_other": "{{count}} ƒőľđęř",
"libraryPanel_one": "{{count}} ľįþřäřy päʼnęľ",
"libraryPanel_other": "{{count}} ľįþřäřy päʼnęľş",
"libraryPanel_other": "{{count}} ľįþřäřy päʼnęľ",
"total_one": "{{count}} įŧęm",
"total_other": "{{count}} įŧęmş"
"total_other": "{{count}} įŧęm"
},
"dashboards-tree": {
"collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}",
@@ -675,6 +675,13 @@
"message": "Ńő đäŧä şőūřčęş ƒőūʼnđ"
}
},
"errors": {
"dashboard-settings": {
"annotations": {
"datasource": "Ŧĥę şęľęčŧęđ đäŧä şőūřčę đőęş ʼnőŧ şūppőřŧ äʼnʼnőŧäŧįőʼnş. Pľęäşę şęľęčŧ ä đįƒƒęřęʼnŧ đäŧä şőūřčę."
}
}
},
"explore": {
"add-to-dashboard": "Åđđ ŧő đäşĥþőäřđ",
"add-to-library-modal": {
@@ -1871,14 +1878,14 @@
"confirm-text": "Đęľęŧę",
"delete-button": "Đęľęŧę",
"delete-loading": "Đęľęŧįʼnģ...",
"text_one": "Ŧĥįş äčŧįőʼn ŵįľľ đęľęŧę {{numberOfDashboards}} đäşĥþőäřđ.",
"text_one": "Ŧĥįş äčŧįőʼn ŵįľľ đęľęŧę {{numberOfDashboards}} đäşĥþőäřđş.",
"text_other": "Ŧĥįş äčŧįőʼn ŵįľľ đęľęŧę {{numberOfDashboards}} đäşĥþőäřđş.",
"title": "Pęřmäʼnęʼnŧľy Đęľęŧę Đäşĥþőäřđş"
},
"restore-modal": {
"restore-button": "Ŗęşŧőřę",
"restore-loading": "Ŗęşŧőřįʼnģ...",
"text_one": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđ.",
"text_one": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđş.",
"text_other": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđş.",
"title": "Ŗęşŧőřę Đäşĥþőäřđş"
}