diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx index 105f747a867..d464023a07a 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx @@ -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); diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx index 40d836c7148..72510de5130 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx @@ -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 { + 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 + {!ds?.meta.annotations && ( + + + The selected data source does not support annotations. Please select a different data source. + + + )} { + {!ds?.meta.annotations && ( + + + The selected data source does not support annotations. Please select a different data source. + + + )} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 9cd2f6d1808..660271b1205 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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" } diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index ad7a25d21db..16be29878b7 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -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": "Ŗęşŧőřę Đäşĥþőäřđş" }