LibraryPanels: Improves export and import of library panels between orgs (#39214)

* Chore: adds tests to reducer

* Refactor: rewrite state

* Refactor: adds library panels to export

* wip

* Refactor: adds import library panels

* Refactor: changes UI

* Chore: pushing drone

* Update public/app/features/manage-dashboards/components/ImportDashboardForm.tsx

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update public/app/features/manage-dashboards/components/ImportDashboardForm.tsx

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Chore: reverted unknown merge changes

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
Hugo Häggmark
2021-09-20 10:58:24 +02:00
committed by GitHub
parent d5b885f958
commit 2696be49b9
18 changed files with 897 additions and 97 deletions

View File

@@ -1,12 +1,13 @@
import { find } from 'lodash';
import config from 'app/core/config';
import { DashboardExporter } from './DashboardExporter';
import { DashboardExporter, LibraryElementExport } from './DashboardExporter';
import { DashboardModel } from '../../state/DashboardModel';
import { PanelPluginMeta } from '@grafana/data';
import { variableAdapters } from '../../../variables/adapters';
import { createConstantVariableAdapter } from '../../../variables/constant/adapter';
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
import { createDataSourceVariableAdapter } from '../../../variables/datasource/adapter';
import { LibraryElementKind } from '../../../library-panels/types';
function getStub(arg: string) {
return Promise.resolve(stubs[arg || 'gfdb']);
@@ -84,6 +85,15 @@ describe('given dashboard with repeated panels', () => {
targets: [{ datasource: 'other' }],
},
{ id: 9, datasource: '$ds' },
{
id: 17,
datasource: '$ds',
type: 'graph',
libraryPanel: {
name: 'Library Panel 2',
uid: 'ah8NqyDPs',
},
},
{
id: 2,
repeat: 'apps',
@@ -110,6 +120,15 @@ describe('given dashboard with repeated panels', () => {
type: 'heatmap',
},
{ id: 15, repeat: null, repeatPanelId: 14 },
{
id: 16,
datasource: 'gfdb',
type: 'graph',
libraryPanel: {
name: 'Library Panel',
uid: 'jL6MrxCMz',
},
},
],
},
],
@@ -149,7 +168,7 @@ describe('given dashboard with repeated panels', () => {
});
it('should replace datasource refs in collapsed row', () => {
const panel = exported.panels[5].panels[0];
const panel = exported.panels[6].panels[0];
expect(panel.datasource).toBe('${DS_GFDB}');
});
@@ -236,6 +255,36 @@ describe('given dashboard with repeated panels', () => {
const require: any = find(exported.__requires, { name: 'OtherDB_2' });
expect(require.id).toBe('other2');
});
it('should add library panels as elements', () => {
const element: LibraryElementExport = exported.__elements.find(
(element: LibraryElementExport) => element.uid === 'ah8NqyDPs'
);
expect(element.name).toBe('Library Panel 2');
expect(element.kind).toBe(LibraryElementKind.Panel);
expect(element.model).toEqual({
id: 17,
datasource: '$ds',
type: 'graph',
fieldConfig: {
defaults: {},
overrides: [],
},
});
});
it('should add library panels in collapsed rows as elements', () => {
const element: LibraryElementExport = exported.__elements.find(
(element: LibraryElementExport) => element.uid === 'jL6MrxCMz'
);
expect(element.name).toBe('Library Panel');
expect(element.kind).toBe(LibraryElementKind.Panel);
expect(element.model).toEqual({
id: 16,
datasource: '${DS_GFDB}',
type: 'graph',
});
});
});
// Stub responses

View File

@@ -7,6 +7,8 @@ import { PanelPluginMeta } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { VariableOption, VariableRefresh } from '../../../variables/types';
import { isConstant, isQuery } from '../../../variables/guard';
import { LibraryElementKind } from '../../../library-panels/types';
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
interface Input {
name: string;
@@ -36,6 +38,13 @@ interface DataSources {
};
}
export interface LibraryElementExport {
name: string;
uid: string;
model: any;
kind: LibraryElementKind;
}
export class DashboardExporter {
makeExportable(dashboard: DashboardModel) {
// clean up repeated rows and panels,
@@ -55,6 +64,7 @@ export class DashboardExporter {
const datasources: DataSources = {};
const promises: Array<Promise<void>> = [];
const variableLookup: { [key: string]: any } = {};
const libraryPanels: Map<string, LibraryElementExport> = new Map<string, LibraryElementExport>();
for (const variable of saveModel.getVariables()) {
variableLookup[variable.name] = variable;
@@ -132,6 +142,16 @@ export class DashboardExporter {
}
};
const processLibraryPanels = (panel: any) => {
if (isPanelModelLibraryPanel(panel)) {
const { libraryPanel, ...model } = panel;
const { name, uid } = libraryPanel;
if (!libraryPanels.has(uid)) {
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model });
}
}
};
// check up panel data sources
for (const panel of saveModel.panels) {
processPanel(panel);
@@ -174,6 +194,17 @@ export class DashboardExporter {
inputs.push(value);
});
// we need to process all panels again after all the promises are resolved
// so all data sources, variables and targets have been templateized when we process library panels
for (const panel of saveModel.panels) {
processLibraryPanels(panel);
if (panel.collapsed !== undefined && panel.collapsed === true && panel.panels) {
for (const rowPanel of panel.panels) {
processLibraryPanels(rowPanel);
}
}
}
// templatize constants
for (const variable of saveModel.getVariables()) {
if (isConstant(variable)) {
@@ -199,6 +230,7 @@ export class DashboardExporter {
// make inputs and requires a top thing
const newObj: { [key: string]: {} } = {};
newObj['__inputs'] = inputs;
newObj['__elements'] = [...libraryPanels.values()];
newObj['__requires'] = sortBy(requires, ['id']);
defaults(newObj, saveModel);