mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataSource: Default data source is no longer a persisted state but just the default data source for new panels (#45132)
* PanelEdit: Change the meaning of default data source to be just that the default for new panels * Added migration, and also migration for annotation datasource prop to data source refs * fix * Fixing tests * Fixes to annotation * Fixing unit test
This commit is contained in:
parent
a9ee446de4
commit
79e5e5c024
@ -9,7 +9,7 @@ import { DataQuery, DataSourceRef } from './query';
|
||||
* This JSON object is stored in the dashboard json model.
|
||||
*/
|
||||
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||
datasource?: DataSourceRef | string | null;
|
||||
datasource?: DataSourceRef | null;
|
||||
|
||||
enable: boolean;
|
||||
name: string;
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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<Props> = ({ 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<Props> = ({ editIdx, dashboard })
|
||||
const onDataSourceChange = (ds: DataSourceInstanceSettings) => {
|
||||
onUpdate({
|
||||
...annotation,
|
||||
datasource: ds.name,
|
||||
datasource: getDataSourceRef(ds),
|
||||
});
|
||||
};
|
||||
|
||||
@ -63,7 +58,7 @@ export const AnnotationSettingsEdit: React.FC<Props> = ({ editIdx, dashboard })
|
||||
});
|
||||
};
|
||||
|
||||
const isNewAnnotation = annotation.name === newAnnotation.name;
|
||||
const isNewAnnotation = annotation.name === newAnnotationName;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -50,7 +50,7 @@ export const AnnotationSettingsList: React.FC<Props> = ({ dashboard, onNew, onEd
|
||||
</td>
|
||||
)}
|
||||
<td className="pointer" onClick={() => onEdit(idx)}>
|
||||
{annotation.datasource || 'Default'}
|
||||
{annotation.datasource?.uid}
|
||||
</td>
|
||||
<td style={{ width: '1%' }}>
|
||||
{idx !== 0 && (
|
||||
|
@ -1,2 +1,2 @@
|
||||
export { AnnotationSettingsEdit } from './AnnotationSettingsEdit';
|
||||
export { AnnotationSettingsEdit, newAnnotationName } from './AnnotationSettingsEdit';
|
||||
export { AnnotationSettingsList } from './AnnotationSettingsList';
|
||||
|
@ -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', () => {
|
||||
|
@ -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<string, any> = {
|
||||
Grafana: {
|
||||
|
||||
const dataSources = {
|
||||
grafana: mockDataSource(
|
||||
{
|
||||
name: 'Grafana',
|
||||
meta: {
|
||||
type: 'datasource',
|
||||
name: 'Grafana',
|
||||
id: 'grafana',
|
||||
info: {
|
||||
logos: {
|
||||
small: 'public/img/icn-datasource.svg',
|
||||
uid: 'Grafana',
|
||||
type: 'grafana',
|
||||
isDefault: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Testdata: {
|
||||
{ annotations: true }
|
||||
),
|
||||
Testdata: mockDataSource(
|
||||
{
|
||||
name: 'Testdata',
|
||||
id: 4,
|
||||
meta: {
|
||||
type: 'datasource',
|
||||
name: 'TestData',
|
||||
id: 'testdata',
|
||||
info: {
|
||||
logos: {
|
||||
small: 'public/app/plugins/datasource/testdata/img/testdata.svg',
|
||||
uid: 'Testdata',
|
||||
type: 'testdata',
|
||||
isDefault: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Prometheus: {
|
||||
{ annotations: true }
|
||||
),
|
||||
Prometheus: mockDataSource(
|
||||
{
|
||||
name: 'Prometheus',
|
||||
id: 33,
|
||||
meta: {
|
||||
type: 'datasource',
|
||||
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(<AnnotationsSettings dashboard={dashboard} />);
|
||||
|
||||
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));
|
||||
|
@ -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<Props> = ({ 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);
|
||||
};
|
||||
|
@ -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<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
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<Props> {
|
||||
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<Props> {
|
||||
|
||||
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 (
|
||||
|
@ -127,7 +127,7 @@ export class ShareSnapshot extends PureComponent<Props, State> {
|
||||
|
||||
// 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,
|
||||
|
@ -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[]) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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,9 +436,7 @@ export class PanelModel implements DataConfigSource, IPanelModel {
|
||||
|
||||
updateQueries(options: QueryGroupOptions) {
|
||||
const { dataSource } = options;
|
||||
this.datasource = dataSource.default
|
||||
? null
|
||||
: {
|
||||
this.datasource = {
|
||||
uid: dataSource.uid,
|
||||
type: dataSource.type,
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -48,7 +48,7 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
prepareQuery(anno: AnnotationQuery<GrafanaAnnotationQuery>): 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;
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { DataQuery, DataSourceRef } from '@grafana/data';
|
||||
import { ExpressionQuery } from '../features/expressions/types';
|
||||
|
||||
export interface QueryGroupOptions {
|
||||
queries: Array<DataQuery | ExpressionQuery>;
|
||||
queries: DataQuery[];
|
||||
dataSource: QueryGroupDataSource;
|
||||
maxDataPoints?: number | null;
|
||||
minInterval?: string | null;
|
||||
|
Loading…
Reference in New Issue
Block a user