mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user