diff --git a/packages/grafana-data/src/types/annotations.ts b/packages/grafana-data/src/types/annotations.ts index 5cae3e72d25..26867af8b2b 100644 --- a/packages/grafana-data/src/types/annotations.ts +++ b/packages/grafana-data/src/types/annotations.ts @@ -9,7 +9,7 @@ import { DataQuery, DataSourceRef } from './query'; * This JSON object is stored in the dashboard json model. */ export interface AnnotationQuery { - datasource?: DataSourceRef | string | null; + datasource?: DataSourceRef | null; enable: boolean; name: string; diff --git a/packages/grafana-data/src/utils/datasource.ts b/packages/grafana-data/src/utils/datasource.ts index 5c9c3c31d40..a57f51a1bdd 100644 --- a/packages/grafana-data/src/utils/datasource.ts +++ b/packages/grafana-data/src/utils/datasource.ts @@ -22,8 +22,8 @@ export function getDataSourceRef(ds: DataSourceInstanceSettings): DataSourceRef * * @public */ -export function isDataSourceRef(ref: DataSourceRef | string | null): ref is DataSourceRef { - return typeof ref === 'object' && (typeof ref?.uid === 'string' || typeof ref?.uid === 'undefined'); +export function isDataSourceRef(ref: DataSourceRef | string | null | undefined): ref is DataSourceRef { + return typeof ref === 'object' && typeof ref?.uid === 'string'; } /** diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index db3081bf74e..804490fe994 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -7,7 +7,7 @@ import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import { getBackendSrv } from '@grafana/runtime'; import { DashboardSrv } from '../dashboard/services/DashboardSrv'; -import DatasourceSrv from '../plugins/datasource_srv'; +import { DatasourceSrv } from '../plugins/datasource_srv'; import { DataQuery, DataSourceApi, rangeUtil } from '@grafana/data'; import { PanelModel } from 'app/features/dashboard/state'; import { getDefaultCondition } from './getAlertingValidationMessage'; diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 6d465e9df1d..eca52ac0931 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -17,7 +17,7 @@ import { RulerRulesConfigDTO, } from 'app/types/unified-alerting-dto'; import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace, CombinedRule } from 'app/types/unified-alerting'; -import DatasourceSrv from 'app/features/plugins/datasource_srv'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DataSourceSrv, GetDataSourceListFilters, config } from '@grafana/runtime'; import { AlertmanagerAlert, diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx index 10ab93456d6..01b9c873d30 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx @@ -1,27 +1,22 @@ import React, { useState } from 'react'; import { Checkbox, CollapsableSection, ColorValueEditor, Field, HorizontalGroup, Input } from '@grafana/ui'; import { DashboardModel } from '../../state/DashboardModel'; -import { AnnotationQuery, DataSourceInstanceSettings } from '@grafana/data'; +import { AnnotationQuery, DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data'; import { DataSourcePicker, getDataSourceSrv } from '@grafana/runtime'; import { useAsync } from 'react-use'; import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor'; import { AngularEditorLoader } from './AngularEditorLoader'; import { selectors } from '@grafana/e2e-selectors'; -export const newAnnotation: AnnotationQuery = { - name: 'New annotation', - enable: true, - datasource: null, - iconColor: 'red', -}; - type Props = { editIdx: number; dashboard: DashboardModel; }; +export const newAnnotationName = 'New annotation'; + export const AnnotationSettingsEdit: React.FC = ({ editIdx, dashboard }) => { - const [annotation, setAnnotation] = useState(editIdx !== null ? dashboard.annotations.list[editIdx] : newAnnotation); + const [annotation, setAnnotation] = useState(dashboard.annotations.list[editIdx]); const { value: ds } = useAsync(() => { return getDataSourceSrv().get(annotation.datasource); @@ -44,7 +39,7 @@ export const AnnotationSettingsEdit: React.FC = ({ editIdx, dashboard }) const onDataSourceChange = (ds: DataSourceInstanceSettings) => { onUpdate({ ...annotation, - datasource: ds.name, + datasource: getDataSourceRef(ds), }); }; @@ -63,7 +58,7 @@ export const AnnotationSettingsEdit: React.FC = ({ editIdx, dashboard }) }); }; - const isNewAnnotation = annotation.name === newAnnotation.name; + const isNewAnnotation = annotation.name === newAnnotationName; return (
diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx index 75139b62de1..60fd6d34614 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx @@ -50,7 +50,7 @@ export const AnnotationSettingsList: React.FC = ({ dashboard, onNew, onEd )} onEdit(idx)}> - {annotation.datasource || 'Default'} + {annotation.datasource?.uid} {idx !== 0 && ( diff --git a/public/app/features/dashboard/components/AnnotationSettings/index.tsx b/public/app/features/dashboard/components/AnnotationSettings/index.tsx index b8110598fb2..5c5ebb6cb18 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/index.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/index.tsx @@ -1,2 +1,2 @@ -export { AnnotationSettingsEdit } from './AnnotationSettingsEdit'; +export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit'; export { AnnotationSettingsList } from './AnnotationSettingsList'; diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index 9757a8ab2b7..83482884b08 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -180,7 +180,7 @@ it('handles a default datasource in a template variable', async () => { const dashboardModel = new DashboardModel(dashboard, {}, () => dashboard.templating.list); const exporter = new DashboardExporter(); const exported: any = await exporter.makeExportable(dashboardModel); - expect(exported.templating.list[0].datasource).toBe('${DS_GFDB}'); + expect(exported.templating.list[0].datasource.uid).toBe('${DS_GFDB}'); }); describe('given dashboard with repeated panels', () => { @@ -325,7 +325,7 @@ describe('given dashboard with repeated panels', () => { }); it('should replace datasource in annotation query', () => { - expect(exported.annotations.list[1].datasource).toBe('${DS_GFDB}'); + expect(exported.annotations.list[1].datasource.uid).toBe('${DS_GFDB}'); }); it('should add datasource as input', () => { diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx index cad37e66da1..07c3d3d2b76 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx @@ -6,79 +6,46 @@ import userEvent from '@testing-library/user-event'; import { selectors } from '@grafana/e2e-selectors'; import { setAngularLoader, setDataSourceSrv } from '@grafana/runtime'; import { AnnotationsSettings } from './AnnotationsSettings'; +import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; describe('AnnotationsSettings', () => { let dashboard: any; - const datasources: Record = { - Grafana: { - name: 'Grafana', - meta: { - type: 'datasource', + + const dataSources = { + grafana: mockDataSource( + { name: 'Grafana', - id: 'grafana', - info: { - logos: { - small: 'public/img/icn-datasource.svg', - }, - }, + uid: 'Grafana', + type: 'grafana', + isDefault: true, }, - }, - Testdata: { - name: 'Testdata', - id: 4, - meta: { - type: 'datasource', - name: 'TestData', - id: 'testdata', - info: { - logos: { - small: 'public/app/plugins/datasource/testdata/img/testdata.svg', - }, - }, + { annotations: true } + ), + Testdata: mockDataSource( + { + name: 'Testdata', + uid: 'Testdata', + type: 'testdata', + isDefault: true, }, - }, - Prometheus: { - name: 'Prometheus', - id: 33, - meta: { - type: 'datasource', + { annotations: true } + ), + Prometheus: mockDataSource( + { name: 'Prometheus', - id: 'prometheus', - info: { - logos: { - small: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg', - }, - }, + uid: 'Prometheus', + type: 'prometheus', }, - }, + { annotations: true } + ), }; + setDataSourceSrv(new MockDataSourceSrv(dataSources)); + const getTableBody = () => screen.getAllByRole('rowgroup')[1]; const getTableBodyRows = () => within(getTableBody()).getAllByRole('row'); beforeAll(() => { - setDataSourceSrv({ - getList() { - return Object.values(datasources).map((d) => d); - }, - getInstanceSettings(name: string) { - return name - ? { - name: datasources[name].name, - value: datasources[name].name, - meta: datasources[name].meta, - } - : { - name: datasources.Testdata.name, - value: datasources.Testdata.name, - meta: datasources.Testdata.meta, - }; - }, - get(name: string) { - return Promise.resolve(name ? datasources[name] : datasources.Testdata); - }, - } as any); - setAngularLoader({ load: () => ({ destroy: jest.fn(), @@ -96,7 +63,7 @@ describe('AnnotationsSettings', () => { list: [ { builtIn: 1, - datasource: 'Grafana', + datasource: { uid: 'Grafana', type: 'grafana' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -158,7 +125,7 @@ describe('AnnotationsSettings', () => { ...dashboard.annotations.list, { builtIn: 0, - datasource: 'Prometheus', + datasource: { uid: 'Prometheus', type: 'prometheus' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -167,7 +134,7 @@ describe('AnnotationsSettings', () => { }, { builtIn: 0, - datasource: 'Prometheus', + datasource: { uid: 'Prometheus', type: 'prometheus' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -207,7 +174,7 @@ describe('AnnotationsSettings', () => { expect(within(getTableBodyRows()[2]).queryByText(/annotations & alerts/i)).toBeInTheDocument(); }); - test('it renders a form for adding/editing annotations', () => { + test('it renders a form for adding/editing annotations', async () => { render(); userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))); @@ -224,7 +191,7 @@ describe('AnnotationsSettings', () => { userEvent.click(screen.getByText(/testdata/i)); - expect(screen.queryByText(/prometheus/i)).toBeVisible(); + expect(await screen.findByText(/Prometheus/i)).toBeVisible(); expect(screen.queryAllByText(/testdata/i)).toHaveLength(2); userEvent.click(screen.getByText(/prometheus/i)); diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx index 93fb799cc96..371b6c85422 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -1,7 +1,8 @@ +import { AnnotationQuery, getDataSourceRef } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; import React, { useState } from 'react'; import { DashboardModel } from '../../state/DashboardModel'; -import { AnnotationSettingsEdit, AnnotationSettingsList } from '../AnnotationSettings'; -import { newAnnotation } from '../AnnotationSettings/AnnotationSettingsEdit'; +import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from '../AnnotationSettings'; import { DashboardSettingsHeader } from './DashboardSettingsHeader'; interface Props { @@ -16,6 +17,13 @@ export const AnnotationsSettings: React.FC = ({ dashboard }) => { }; const onNew = () => { + const newAnnotation: AnnotationQuery = { + name: newAnnotationName, + enable: true, + datasource: getDataSourceRef(getDataSourceSrv().getInstanceSettings(null)!), + iconColor: 'red', + }; + dashboard.annotations.list = [...dashboard.annotations.list, { ...newAnnotation }]; setEditIdx(dashboard.annotations.list.length - 1); }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index 24be2e0fd98..36cc2d32b88 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -3,7 +3,7 @@ import { QueryGroup } from 'app/features/query/components/QueryGroup'; import { PanelModel } from '../../state'; import { locationService } from '@grafana/runtime'; import { QueryGroupDataSource, QueryGroupOptions } from 'app/types'; -import { DataQuery } from '@grafana/data'; +import { DataQuery, getDataSourceRef } from '@grafana/data'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; interface Props { @@ -42,6 +42,18 @@ export class PanelEditorQueries extends PureComponent { }; } + async componentDidMount() { + const { panel } = this.props; + + // If the panel model has no datasource property load the default data source property and update the persisted model + // Because this part of the panel model is not in redux yet we do a forceUpdate. + if (!panel.datasource) { + const ds = getDatasourceSrv().getInstanceSettings(null); + panel.datasource = getDataSourceRef(ds!); + this.forceUpdate(); + } + } + onRunQueries = () => { this.props.panel.refresh(); }; @@ -56,11 +68,9 @@ export class PanelEditorQueries extends PureComponent { onOptionsChange = (options: QueryGroupOptions) => { const { panel } = this.props; - const newDataSourceID = options.dataSource.default ? null : options.dataSource.uid!; - const dataSourceChanged = newDataSourceID !== panel.datasource?.uid; panel.updateQueries(options); - if (dataSourceChanged) { + if (options.dataSource.uid !== panel.datasource?.uid) { // trigger queries when changing data source setTimeout(this.onRunQueries, 10); } @@ -70,6 +80,12 @@ export class PanelEditorQueries extends PureComponent { render() { const { panel } = this.props; + + // If no panel data soruce set, wait with render. Will be set to default in componentDidMount + if (!panel.datasource) { + return null; + } + const options = this.buildQueryOptions(panel); return ( diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index a2836b818ff..1ac5b0d4d19 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -127,7 +127,7 @@ export class ShareSnapshot extends PureComponent { // remove annotation queries const annotations = dash.annotations.list.filter((annotation) => annotation.enable); - dash.annotations.list = annotations.map((annotation: any) => { + dash.annotations.list = annotations.map((annotation) => { return { name: annotation.name, enable: annotation.enable, diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index c54ce916e2e..201ba1ce202 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -16,6 +16,12 @@ jest.mock('app/core/services/context_srv', () => ({})); const dataSources = { prom: mockDataSource({ name: 'prom', + uid: 'prom-uid', + type: 'prometheus', + }), + prom2: mockDataSource({ + name: 'prom2', + uid: 'prom2-uid', type: 'prometheus', isDefault: true, }), @@ -186,7 +192,7 @@ describe('DashboardModel', () => { }); it('dashboard schema version should be set to latest', () => { - expect(model.schemaVersion).toBe(35); + expect(model.schemaVersion).toBe(36); }); it('graph thresholds should be migrated', () => { @@ -1827,11 +1833,11 @@ describe('DashboardModel', () => { }); it('should update panel datasource props to refs for named data source', () => { - expect(model.panels[0].datasource).toEqual({ type: 'prometheus', uid: 'mock-ds-2' }); + expect(model.panels[0].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); }); it('should update panel datasource props to refs for default data source', () => { - expect(model.panels[1].datasource).toEqual(null); + expect(model.panels[1].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); }); it('should update panel datasource props to refs for mixed data source', () => { @@ -1839,11 +1845,11 @@ describe('DashboardModel', () => { }); it('should update target datasource props to refs', () => { - expect(model.panels[2].targets[0].datasource).toEqual({ type: 'prometheus', uid: 'mock-ds-2' }); + expect(model.panels[2].targets[0].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); }); it('should update datasources in panels collapsed rows', () => { - expect(model.panels[3].panels[0].datasource).toEqual({ type: 'prometheus', uid: 'mock-ds-2' }); + expect(model.panels[3].panels[0].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); }); }); @@ -1869,7 +1875,8 @@ describe('DashboardModel', () => { }); }); - it('should not update panel datasource to that of query level ds', () => { + it('should use data source on query level as source of truth', () => { + expect(model.panels[0].targets[0]?.datasource?.uid).toEqual('prom-not-default-uid'); expect(model.panels[0].datasource?.uid).toEqual('prom-not-default-uid'); }); }); @@ -1910,6 +1917,80 @@ describe('DashboardModel', () => { `); }); }); + + describe('when migrating default (null) datasource', () => { + let model: DashboardModel; + + beforeEach(() => { + model = new DashboardModel({ + templating: { + list: [ + { + type: 'query', + name: 'var', + options: [{ text: 'A', value: 'A' }], + refresh: 0, + datasource: null, + }, + ], + }, + annotations: { + list: [ + { + datasource: null, + }, + { + datasource: 'prom', + }, + ], + }, + panels: [ + { + id: 2, + datasource: null, + targets: [ + { + datasource: null, + }, + ], + }, + { + id: 3, + targets: [ + { + refId: 'A', + }, + ], + }, + ], + schemaVersion: 35, + }); + }); + + it('should set data source to current default', () => { + expect(model.templating.list[0].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); + }); + + it('should migrate annotation null query to default ds', () => { + expect(model.annotations.list[1].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); + }); + + it('should migrate annotation query to refs', () => { + expect(model.annotations.list[2].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); + }); + + it('should update panel datasource props to refs for named data source', () => { + expect(model.panels[0].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); + }); + + it('should update panel datasource props even when undefined', () => { + expect(model.panels[1].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); + }); + + it('should update target datasource props to refs', () => { + expect(model.panels[0].targets[0].datasource).toEqual({ type: 'prometheus', uid: 'prom2-uid' }); + }); + }); }); function createRow(options: any, panelDescriptions: any[]) { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 3946a9eda08..7ee735ff5d4 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -45,7 +45,7 @@ import { config } from 'app/core/config'; import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module'; import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module'; import { AxisPlacement, GraphFieldConfig } from '@grafana/ui'; -import { getDataSourceSrv } from '@grafana/runtime'; +import { getDataSourceSrv, setDataSourceSrv } from '@grafana/runtime'; import { labelsToFieldsTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/labelsToFields'; import { mergeTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; import { @@ -55,6 +55,7 @@ import { } from 'app/plugins/datasource/cloudwatch/migrations'; import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from 'app/plugins/datasource/cloudwatch/types'; import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); @@ -65,39 +66,10 @@ export class DashboardMigrator { constructor(dashboardModel: DashboardModel) { this.dashboard = dashboardModel; - } - /** - * When changing default datasource which is stored as null Grafana get's into a mixed state where queries have - * data source uid & type set that is different from the now new default - */ - syncQueryDataSources() { - const dataSourceSrv = getDataSourceSrv(); - // This only happens in some unit tests that does not set a DataSourceSrv - if (!dataSourceSrv) { - return; - } - - const defaultDS = getDataSourceSrv().getInstanceSettings(null); - // if default ds is mixed then skip this - if (!defaultDS || defaultDS.meta.mixed) { - return; - } - - for (const panel of this.dashboard.panels) { - // only interested in panels that use default (null) data source - if (panel.datasource) { - continue; - } - - for (const target of panel.targets) { - // If query level data source is different from panel - if (target.datasource && target.datasource.uid !== defaultDS?.uid) { - // set panel level data source to data source on the query as this is more likely the correct one - // But impossible to say, and this changes the behavior of of what default means ahead of the big change to default - panel.datasource = target.datasource; - } - } + // for tests to pass + if (!getDataSourceSrv()) { + setDataSourceSrv(new DatasourceSrv()); } } @@ -105,7 +77,7 @@ export class DashboardMigrator { let i, j, k, n; const oldVersion = this.dashboard.schemaVersion; const panelUpgrades: PanelSchemeUpgradeHandler[] = []; - this.dashboard.schemaVersion = 35; + this.dashboard.schemaVersion = 36; if (oldVersion === this.dashboard.schemaVersion) { return; @@ -736,14 +708,14 @@ export class DashboardMigrator { // Replace datasource name with reference, uid and type if (oldVersion < 33) { panelUpgrades.push((panel) => { - panel.datasource = migrateDatasourceNameToRef(panel.datasource); + panel.datasource = migrateDatasourceNameToRef(panel.datasource, { returnDefaultAsNull: true }); if (!panel.targets) { return panel; } for (const target of panel.targets) { - const targetRef = migrateDatasourceNameToRef(target.datasource); + const targetRef = migrateDatasourceNameToRef(target.datasource, { returnDefaultAsNull: true }); if (targetRef != null) { target.datasource = targetRef; } @@ -766,6 +738,46 @@ export class DashboardMigrator { panelUpgrades.push(ensureXAxisVisibility); } + if (oldVersion < 36) { + // Migrate datasource to refs in annotations + for (const query of this.dashboard.annotations.list) { + query.datasource = migrateDatasourceNameToRef(query.datasource, { returnDefaultAsNull: false }); + } + + // Migrate datasource: null to current default + const defaultDs = getDataSourceSrv().getInstanceSettings(null); + if (defaultDs) { + for (const variable of this.dashboard.templating.list) { + if (variable.type === 'query' && variable.datasource === null) { + variable.datasource = getDataSourceRef(defaultDs); + } + } + + panelUpgrades.push((panel: PanelModel) => { + if (panel.targets) { + let panelDataSourceWasDefault = false; + if (panel.datasource == null && panel.targets.length > 0) { + panel.datasource = getDataSourceRef(defaultDs); + panelDataSourceWasDefault = true; + } + + for (const target of panel.targets) { + if (target.datasource && panelDataSourceWasDefault) { + // We can have situations when default ds changed and the panel level data source is different from the queries + // In this case we use the query level data source as source for truth + panel.datasource = target.datasource as DataSourceRef; + } + + if (target.datasource === null) { + target.datasource = getDataSourceRef(defaultDs); + } + } + } + return panel; + }); + } + } + if (panelUpgrades.length === 0) { return; } @@ -1084,8 +1096,15 @@ function migrateSinglestat(panel: PanelModel) { return panel; } -export function migrateDatasourceNameToRef(nameOrRef?: string | DataSourceRef | null): DataSourceRef | null { - if (nameOrRef == null || nameOrRef === 'default') { +interface MigrateDatasourceNameOptions { + returnDefaultAsNull: boolean; +} + +export function migrateDatasourceNameToRef( + nameOrRef: string | DataSourceRef | null | undefined, + options: MigrateDatasourceNameOptions +): DataSourceRef | null { + if (options.returnDefaultAsNull && (nameOrRef == null || nameOrRef === 'default')) { return null; } diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 1365a13f879..17106080bba 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -206,7 +206,7 @@ export class DashboardModel implements TimeModel { } this.annotations.list.unshift({ - datasource: '-- Grafana --', + datasource: { uid: '-- Grafana --', type: 'grafana' }, name: 'Annotations & Alerts', type: 'dashboard', iconColor: DEFAULT_ANNOTATION_COLOR, @@ -1071,7 +1071,6 @@ export class DashboardModel implements TimeModel { private updateSchema(old: any) { const migrator = new DashboardMigrator(this); migrator.updateSchema(old); - migrator.syncQueryDataSources(); } resetOriginalTime() { diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 338d76227a0..1af302c5538 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -196,11 +196,6 @@ describe('PanelModel', () => { expect(saveModel.gridPos).toBe(undefined); }); - it('getSaveModel should not remove datasource default', () => { - const saveModel = model.getSaveModel(); - expect(saveModel.datasource).toBe(null); - }); - it('getSaveModel should remove nonPersistedProperties', () => { const saveModel = model.getSaveModel(); expect(saveModel.events).toBe(undefined); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 8e9acd31793..115c7ed8502 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -121,7 +121,6 @@ const defaults: any = { defaults: {}, overrides: [], }, - datasource: null, title: '', }; @@ -284,12 +283,6 @@ export class PanelModel implements DataConfigSource, IPanelModel { model[property] = cloneDeep(this[property]); } - if (model.datasource === undefined) { - // This is part of defaults as defaults are removed in save model and - // this should not be removed in save model as exporter needs to templatize it - model.datasource = null; - } - return model; } @@ -443,12 +436,10 @@ export class PanelModel implements DataConfigSource, IPanelModel { updateQueries(options: QueryGroupOptions) { const { dataSource } = options; - this.datasource = dataSource.default - ? null - : { - uid: dataSource.uid, - type: dataSource.type, - }; + this.datasource = { + uid: dataSource.uid, + type: dataSource.type, + }; this.cacheTimeout = options.cacheTimeout; this.timeFrom = options.timeRange?.from; this.timeShift = options.timeRange?.shift; diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 16ffa1c0d36..1e83411147e 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -308,7 +308,7 @@ export class DatasourceSrv implements DataSourceService { return this.getList({ annotations: true, variables: true }).map((x) => { return { name: x.name, - value: x.isDefault ? null : x.name, + value: x.name, meta: x.meta, }; }); @@ -321,7 +321,7 @@ export class DatasourceSrv implements DataSourceService { return this.getList({ metrics: true, variables: !options?.skipVariables }).map((x) => { return { name: x.name, - value: x.isDefault ? null : x.name, + value: x.name, meta: x.meta, }; }); @@ -345,5 +345,3 @@ export function variableInterpolation(value: any[]) { export const getDatasourceSrv = (): DatasourceSrv => { return getDataSourceService() as DatasourceSrv; }; - -export default DatasourceSrv; diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 83e4c259728..a1f024e4daa 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -103,7 +103,7 @@ export class TemplateSrv implements BaseTemplateSrv { for (const variable of this.getAdHocVariables()) { const variableUid = variable.datasource?.uid; - if (variableUid === ds.uid || (variable.datasource == null && ds?.isDefault)) { + if (variableUid === ds.uid) { filters = filters.concat(variable.filters); } else if (variableUid?.indexOf('$') === 0) { if (this.replace(variableUid) === datasourceName) { diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx index 25e4ec6355e..afef996aff0 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx @@ -6,6 +6,8 @@ import { SHARED_DASHBOARD_QUERY } from './types'; import { DashboardQueryEditor } from './DashboardQueryEditor'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardModel } from 'app/features/dashboard/state'; +import { setDataSourceSrv } from '@grafana/runtime'; +import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; jest.mock('app/core/config', () => ({ ...(jest.requireActual('app/core/config') as unknown as object), @@ -20,12 +22,11 @@ jest.mock('app/core/config', () => ({ }, })); -jest.mock('app/features/plugins/datasource_srv', () => ({ - getDatasourceSrv: () => ({ - get: () => Promise.resolve({}), - getInstanceSettings: () => ({}), - }), -})); +setDataSourceSrv( + new MockDataSourceSrv({ + test: mockDataSource({ isDefault: true }), + }) +); describe('DashboardQueryEditor', () => { const mockOnChange = jest.fn(); diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 103c92d097d..56c505af516 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -48,7 +48,7 @@ export class GrafanaDatasource extends DataSourceWithBackend { prepareQuery(anno: AnnotationQuery): GrafanaQuery { let datasource: DataSourceRef | undefined | null = undefined; if (isString(anno.datasource)) { - const ref = migrateDatasourceNameToRef(anno.datasource); + const ref = migrateDatasourceNameToRef(anno.datasource, { returnDefaultAsNull: false }); if (ref) { datasource = ref; } diff --git a/public/app/plugins/datasource/postgres/config_ctrl.ts b/public/app/plugins/datasource/postgres/config_ctrl.ts index f13a0ab0ade..d3759cc6971 100644 --- a/public/app/plugins/datasource/postgres/config_ctrl.ts +++ b/public/app/plugins/datasource/postgres/config_ctrl.ts @@ -4,7 +4,7 @@ import { createResetHandler, PasswordFieldEnum, } from '../../../features/datasources/utils/passwordHandlers'; -import DatasourceSrv from 'app/features/plugins/datasource_srv'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; export class PostgresConfigCtrl { static templateUrl = 'partials/config.html'; diff --git a/public/app/types/query.ts b/public/app/types/query.ts index bfa859ae135..7be2c2cd77d 100644 --- a/public/app/types/query.ts +++ b/public/app/types/query.ts @@ -1,8 +1,7 @@ import { DataQuery, DataSourceRef } from '@grafana/data'; -import { ExpressionQuery } from '../features/expressions/types'; export interface QueryGroupOptions { - queries: Array; + queries: DataQuery[]; dataSource: QueryGroupDataSource; maxDataPoints?: number | null; minInterval?: string | null;