mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Export: Fix export/import of dash with library panels (#49319)
This commit is contained in:
@@ -337,13 +337,10 @@ describe('given dashboard with repeated panels', () => {
|
||||
});
|
||||
|
||||
it('should add library panels as elements', () => {
|
||||
const element: LibraryElementExport = exported.__elements.find(
|
||||
(element: LibraryElementExport) => element.uid === 'ah8NqyDPs'
|
||||
);
|
||||
const element: LibraryElementExport = exported.__elements['ah8NqyDPs'];
|
||||
expect(element.name).toBe('Library Panel 2');
|
||||
expect(element.kind).toBe(LibraryElementKind.Panel);
|
||||
expect(element.model).toEqual({
|
||||
id: 17,
|
||||
datasource: { type: 'other2', uid: '$ds' },
|
||||
targets: [{ refId: 'A', datasource: { type: 'other2', uid: '$ds' } }],
|
||||
type: 'graph',
|
||||
@@ -351,13 +348,10 @@ describe('given dashboard with repeated panels', () => {
|
||||
});
|
||||
|
||||
it('should add library panels in collapsed rows as elements', () => {
|
||||
const element: LibraryElementExport = exported.__elements.find(
|
||||
(element: LibraryElementExport) => element.uid === 'jL6MrxCMz'
|
||||
);
|
||||
const element: LibraryElementExport = exported.__elements['jL6MrxCMz'];
|
||||
expect(element.name).toBe('Library Panel');
|
||||
expect(element.kind).toBe(LibraryElementKind.Panel);
|
||||
expect(element.model).toEqual({
|
||||
id: 16,
|
||||
type: 'graph',
|
||||
datasource: {
|
||||
type: 'testdb',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { LibraryElementKind } from '../../../library-panels/types';
|
||||
import { isConstant, isQuery } from '../../../variables/guard';
|
||||
import { VariableOption, VariableRefresh } from '../../../variables/types';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { GridPos } from '../../state/PanelModel';
|
||||
|
||||
interface Input {
|
||||
name: string;
|
||||
@@ -28,6 +29,26 @@ interface Requires {
|
||||
};
|
||||
}
|
||||
|
||||
interface ExternalDashboard {
|
||||
__inputs: Input[];
|
||||
__elements: Record<string, LibraryElementExport>;
|
||||
__requires: Array<Requires[string]>;
|
||||
panels: Array<PanelModel | PanelWithExportableLibraryPanel>;
|
||||
}
|
||||
|
||||
interface PanelWithExportableLibraryPanel {
|
||||
gridPos: GridPos;
|
||||
id: number;
|
||||
libraryPanel: {
|
||||
name: string;
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
function isExportableLibraryPanel(p: any): p is PanelWithExportableLibraryPanel {
|
||||
return p.libraryPanel && typeof p.libraryPanel.name === 'string' && typeof p.libraryPanel.uid === 'string';
|
||||
}
|
||||
|
||||
interface DataSources {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
@@ -79,11 +100,11 @@ export class DashboardExporter {
|
||||
let datasource: string = obj.datasource;
|
||||
let datasourceVariable: any = null;
|
||||
|
||||
const datasourceUid: string = (datasource as any)?.uid;
|
||||
// ignore data source properties that contain a variable
|
||||
if (datasource && (datasource as any).uid) {
|
||||
const uid = (datasource as any).uid as string;
|
||||
if (uid.indexOf('$') === 0) {
|
||||
datasourceVariable = variableLookup[uid.substring(1)];
|
||||
if (datasourceUid) {
|
||||
if (datasourceUid.indexOf('$') === 0) {
|
||||
datasourceVariable = variableLookup[datasourceUid.substring(1)];
|
||||
if (datasourceVariable && datasourceVariable.current) {
|
||||
datasource = datasourceVariable.current.value;
|
||||
}
|
||||
@@ -150,8 +171,9 @@ export class DashboardExporter {
|
||||
if (isPanelModelLibraryPanel(panel)) {
|
||||
const { libraryPanel, ...model } = panel;
|
||||
const { name, uid } = libraryPanel;
|
||||
const { gridPos, id, ...rest } = model;
|
||||
if (!libraryPanels.has(uid)) {
|
||||
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model });
|
||||
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model: rest });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -230,13 +252,36 @@ 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']);
|
||||
const __elements = [...libraryPanels.entries()].reduce<Record<string, LibraryElementExport>>(
|
||||
(prev, [curKey, curLibPanel]) => {
|
||||
prev[curKey] = curLibPanel;
|
||||
return prev;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// make inputs and requires a top thing
|
||||
const newObj: ExternalDashboard = defaults(
|
||||
{
|
||||
__inputs: inputs,
|
||||
__elements,
|
||||
__requires: sortBy(requires, ['id']),
|
||||
},
|
||||
saveModel
|
||||
);
|
||||
|
||||
// Remove extraneous props from library panels
|
||||
for (let i = 0; i < newObj.panels.length; i++) {
|
||||
const libPanel = newObj.panels[i];
|
||||
if (isExportableLibraryPanel(libPanel)) {
|
||||
newObj.panels[i] = {
|
||||
gridPos: libPanel.gridPos,
|
||||
id: libPanel.id,
|
||||
libraryPanel: { uid: libPanel.libraryPanel.uid, name: libPanel.libraryPanel.name },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
defaults(newObj, saveModel);
|
||||
return newObj;
|
||||
} catch (err) {
|
||||
console.error('Export failed:', err);
|
||||
|
||||
@@ -64,7 +64,7 @@ export const ImportDashboardForm: FC<Props> = ({
|
||||
}
|
||||
}, [errors, getValues, isSubmitted, onSubmit]);
|
||||
const newLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.New) ?? [];
|
||||
const existingLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.Exits) ?? [];
|
||||
const existingLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.Exists) ?? [];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -77,7 +77,7 @@ function processInputs(dashboardJson: any): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }): ThunkResult<void> {
|
||||
function processElements(dashboardJson?: { __elements?: Record<string, LibraryElementExport> }): ThunkResult<void> {
|
||||
return async function (dispatch) {
|
||||
if (!dashboardJson || !dashboardJson.__elements) {
|
||||
return;
|
||||
@@ -85,7 +85,7 @@ function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }
|
||||
|
||||
const libraryPanelInputs: LibraryPanelInput[] = [];
|
||||
|
||||
for (const element of dashboardJson.__elements) {
|
||||
for (const element of Object.values(dashboardJson.__elements)) {
|
||||
if (element.kind !== LibraryElementKind.Panel) {
|
||||
continue;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }
|
||||
|
||||
try {
|
||||
const panelInDb = await getLibraryPanel(uid, true);
|
||||
input.state = LibraryPanelInputState.Exits;
|
||||
input.state = LibraryPanelInputState.Exists;
|
||||
input.model = panelInDb;
|
||||
} catch (e: any) {
|
||||
if (e.status !== 404) {
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('importDashboardReducer', () => {
|
||||
setLibraryPanelInputs([
|
||||
{
|
||||
model: { uid: 'sadjahsdk', name: 'A name', type: 'text' } as LibraryElementDTO,
|
||||
state: LibraryPanelInputState.Exits,
|
||||
state: LibraryPanelInputState.Exists,
|
||||
},
|
||||
])
|
||||
)
|
||||
@@ -127,7 +127,7 @@ describe('importDashboardReducer', () => {
|
||||
libraryPanels: [
|
||||
{
|
||||
model: { uid: 'sadjahsdk', name: 'A name', type: 'text' } as LibraryElementDTO,
|
||||
state: LibraryPanelInputState.Exits,
|
||||
state: LibraryPanelInputState.Exists,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@ export enum InputType {
|
||||
|
||||
export enum LibraryPanelInputState {
|
||||
New = 'new',
|
||||
Exits = 'exists',
|
||||
Exists = 'exists',
|
||||
Different = 'different',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user