mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Export: Remove DS input when dashboard is imported with a lib panel that already exists (#69412)
This commit is contained in:
parent
649cd08a19
commit
427714f8d0
@ -2731,10 +2731,10 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "5"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
|
[0, 0, 0, "Do not use any type assertions.", "6"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||||
@ -2746,8 +2746,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "17"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "17"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "18"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "18"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "19"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "19"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "20"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/manage-dashboards/state/reducers.ts:5381": [
|
"public/app/features/manage-dashboards/state/reducers.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
|
@ -56,7 +56,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon
|
|||||||
cmd.FolderID = folder.ID
|
cmd.FolderID = folder.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd)
|
element, err := l.CreateElement(c.Req.Context(), c.SignedInUser, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toLibraryElementError(err, "Failed to create library element")
|
return toLibraryElementError(err, "Failed to create library element")
|
||||||
}
|
}
|
||||||
|
@ -120,12 +120,22 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
|
|||||||
return model.LibraryElementDTO{}, model.ErrLibraryElementUIDTooLong
|
return model.LibraryElementDTO{}, model.ErrLibraryElementUIDTooLong
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedModel := cmd.Model
|
||||||
|
var err error
|
||||||
|
if cmd.Kind == int64(model.PanelElement) {
|
||||||
|
updatedModel, err = l.addUidToLibraryPanel(cmd.Model, createUID)
|
||||||
|
if err != nil {
|
||||||
|
return model.LibraryElementDTO{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
element := model.LibraryElement{
|
element := model.LibraryElement{
|
||||||
OrgID: signedInUser.OrgID,
|
OrgID: signedInUser.OrgID,
|
||||||
FolderID: cmd.FolderID,
|
FolderID: cmd.FolderID,
|
||||||
UID: createUID,
|
UID: createUID,
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
Model: cmd.Model,
|
Model: updatedModel,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Kind: cmd.Kind,
|
Kind: cmd.Kind,
|
||||||
|
|
||||||
@ -140,7 +150,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
|
|||||||
return model.LibraryElementDTO{}, err
|
return model.LibraryElementDTO{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
|
||||||
if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil {
|
if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -228,7 +238,7 @@ func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getLibraryElements gets a Library Element where param == value
|
// getLibraryElements gets a Library Element where param == value
|
||||||
func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair, features featuremgmt.FeatureToggles, cmd model.GetLibraryElementCommand) ([]model.LibraryElementDTO, error) {
|
func (l *LibraryElementService) getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair, features featuremgmt.FeatureToggles, cmd model.GetLibraryElementCommand) ([]model.LibraryElementDTO, error) {
|
||||||
libraryElements := make([]model.LibraryElementWithMeta, 0)
|
libraryElements := make([]model.LibraryElementWithMeta, 0)
|
||||||
|
|
||||||
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
||||||
@ -267,6 +277,14 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed
|
|||||||
|
|
||||||
leDtos := make([]model.LibraryElementDTO, len(libraryElements))
|
leDtos := make([]model.LibraryElementDTO, len(libraryElements))
|
||||||
for i, libraryElement := range libraryElements {
|
for i, libraryElement := range libraryElements {
|
||||||
|
var updatedModel json.RawMessage
|
||||||
|
if libraryElement.Kind == int64(model.PanelElement) {
|
||||||
|
updatedModel, err = l.addUidToLibraryPanel(libraryElement.Model, libraryElement.UID)
|
||||||
|
if err != nil {
|
||||||
|
return []model.LibraryElementDTO{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
leDtos[i] = model.LibraryElementDTO{
|
leDtos[i] = model.LibraryElementDTO{
|
||||||
ID: libraryElement.ID,
|
ID: libraryElement.ID,
|
||||||
OrgID: libraryElement.OrgID,
|
OrgID: libraryElement.OrgID,
|
||||||
@ -277,7 +295,7 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed
|
|||||||
Kind: libraryElement.Kind,
|
Kind: libraryElement.Kind,
|
||||||
Type: libraryElement.Type,
|
Type: libraryElement.Type,
|
||||||
Description: libraryElement.Description,
|
Description: libraryElement.Description,
|
||||||
Model: libraryElement.Model,
|
Model: updatedModel,
|
||||||
Version: libraryElement.Version,
|
Version: libraryElement.Version,
|
||||||
Meta: model.LibraryElementDTOMeta{
|
Meta: model.LibraryElementDTOMeta{
|
||||||
FolderName: libraryElement.FolderName,
|
FolderName: libraryElement.FolderName,
|
||||||
@ -304,7 +322,7 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed
|
|||||||
|
|
||||||
// getLibraryElementByUid gets a Library Element by uid.
|
// getLibraryElementByUid gets a Library Element by uid.
|
||||||
func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) {
|
func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) {
|
||||||
libraryElements, err := getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: cmd.UID}}, l.features, cmd)
|
libraryElements, err := l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: cmd.UID}}, l.features, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.LibraryElementDTO{}, err
|
return model.LibraryElementDTO{}, err
|
||||||
}
|
}
|
||||||
@ -317,7 +335,7 @@ func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signed
|
|||||||
|
|
||||||
// getLibraryElementByName gets a Library Element by name.
|
// getLibraryElementByName gets a Library Element by name.
|
||||||
func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]model.LibraryElementDTO, error) {
|
func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]model.LibraryElementDTO, error) {
|
||||||
return getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}, l.features,
|
return l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}, l.features,
|
||||||
model.GetLibraryElementCommand{
|
model.GetLibraryElementCommand{
|
||||||
FolderName: dashboards.RootFolderName,
|
FolderName: dashboards.RootFolderName,
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ package libraryelements
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
@ -75,3 +76,24 @@ func (l *LibraryElementService) DisconnectElementsFromDashboard(c context.Contex
|
|||||||
func (l *LibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser *user.SignedInUser, folderUID string) error {
|
func (l *LibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser *user.SignedInUser, folderUID string) error {
|
||||||
return l.deleteLibraryElementsInFolderUID(c, signedInUser, folderUID)
|
return l.deleteLibraryElementsInFolderUID(c, signedInUser, folderUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LibraryElementService) addUidToLibraryPanel(model []byte, newUid string) (json.RawMessage, error) {
|
||||||
|
var modelMap map[string]interface{}
|
||||||
|
err := json.Unmarshal(model, &modelMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if libraryPanel, ok := modelMap["libraryPanel"].(map[string]interface{}); ok {
|
||||||
|
if uid, ok := libraryPanel["uid"]; ok && uid == "" {
|
||||||
|
libraryPanel["uid"] = newUid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedModel, err := json.Marshal(modelMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedModel, nil
|
||||||
|
}
|
||||||
|
@ -164,8 +164,8 @@ it('replaces datasource ref in library panel', async () => {
|
|||||||
if ('error' in exported) {
|
if ('error' in exported) {
|
||||||
throw new Error('error should not be returned when making exportable json');
|
throw new Error('error should not be returned when making exportable json');
|
||||||
}
|
}
|
||||||
expect(exported.__elements['c46a6b49-de40-43b3-982c-1b5e1ec084a4'].model.datasource.uid).toBe('${DS_GFDB}');
|
expect(exported.__elements!['c46a6b49-de40-43b3-982c-1b5e1ec084a4'].model.datasource.uid).toBe('${DS_GFDB}');
|
||||||
expect(exported.__inputs[0].name).toBe('DS_GFDB');
|
expect(exported.__inputs![0].name).toBe('DS_GFDB');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('If a panel queries has no datasource prop ignore it', async () => {
|
it('If a panel queries has no datasource prop ignore it', async () => {
|
||||||
|
@ -13,12 +13,21 @@ import { VariableOption, VariableRefresh } from '../../../variables/types';
|
|||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { GridPos } from '../../state/PanelModel';
|
import { GridPos } from '../../state/PanelModel';
|
||||||
|
|
||||||
interface Input {
|
export interface InputUsage {
|
||||||
|
libraryPanels?: LibraryPanel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LibraryPanel {
|
||||||
|
name: string;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
export interface Input {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: any;
|
value: any;
|
||||||
description: string;
|
description: string;
|
||||||
|
usage?: InputUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Requires {
|
interface Requires {
|
||||||
@ -30,20 +39,17 @@ interface Requires {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalDashboard {
|
export interface ExternalDashboard {
|
||||||
__inputs: Input[];
|
__inputs?: Input[];
|
||||||
__elements: Record<string, LibraryElementExport>;
|
__elements?: Record<string, LibraryElementExport>;
|
||||||
__requires: Array<Requires[string]>;
|
__requires?: Array<Requires[string]>;
|
||||||
panels: Array<PanelModel | PanelWithExportableLibraryPanel>;
|
panels: Array<PanelModel | PanelWithExportableLibraryPanel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PanelWithExportableLibraryPanel {
|
interface PanelWithExportableLibraryPanel {
|
||||||
gridPos: GridPos;
|
gridPos: GridPos;
|
||||||
id: number;
|
id: number;
|
||||||
libraryPanel: {
|
libraryPanel: LibraryPanel;
|
||||||
name: string;
|
|
||||||
uid: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExportableLibraryPanel(p: any): p is PanelWithExportableLibraryPanel {
|
function isExportableLibraryPanel(p: any): p is PanelWithExportableLibraryPanel {
|
||||||
@ -58,6 +64,7 @@ interface DataSources {
|
|||||||
type: string;
|
type: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
pluginName: string;
|
pluginName: string;
|
||||||
|
usage?: InputUsage;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +139,10 @@ export class DashboardExporter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refName = 'DS_' + ds.name.replace(' ', '_').toUpperCase();
|
const libraryPanel = obj.libraryPanel;
|
||||||
|
const libraryPanelSuffix = !!libraryPanel ? '-for-library-panel' : '';
|
||||||
|
let refName = 'DS_' + ds.name.replace(' ', '_').toUpperCase() + libraryPanelSuffix.toUpperCase();
|
||||||
|
|
||||||
datasources[refName] = {
|
datasources[refName] = {
|
||||||
name: refName,
|
name: refName,
|
||||||
label: ds.name,
|
label: ds.name,
|
||||||
@ -140,8 +150,18 @@ export class DashboardExporter {
|
|||||||
type: 'datasource',
|
type: 'datasource',
|
||||||
pluginId: ds.meta?.id,
|
pluginId: ds.meta?.id,
|
||||||
pluginName: ds.meta?.name,
|
pluginName: ds.meta?.name,
|
||||||
|
usage: datasources[refName]?.usage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!!libraryPanel) {
|
||||||
|
const libPanels = datasources[refName]?.usage?.libraryPanels || [];
|
||||||
|
libPanels.push({ name: libraryPanel.name, uid: libraryPanel.uid });
|
||||||
|
|
||||||
|
datasources[refName].usage = {
|
||||||
|
libraryPanels: libPanels,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
obj.datasource = { type: ds.meta.id, uid: '${' + refName + '}' };
|
obj.datasource = { type: ds.meta.id, uid: '${' + refName + '}' };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -118,6 +118,7 @@ export const ImportDashboardForm = ({
|
|||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
label={input.label}
|
label={input.label}
|
||||||
|
description={input.description}
|
||||||
key={dataSourceOption}
|
key={dataSourceOption}
|
||||||
invalid={errors.dataSources && !!errors.dataSources[index]}
|
invalid={errors.dataSources && !!errors.dataSources[index]}
|
||||||
error={errors.dataSources && errors.dataSources[index] && 'A data source is required'}
|
error={errors.dataSources && errors.dataSources[index] && 'A data source is required'}
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||||
|
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings, ThresholdsMode } from '@grafana/data';
|
||||||
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
|
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
|
||||||
|
import { defaultDashboard, FieldColorModeId } from '@grafana/schema';
|
||||||
|
import { getLibraryPanel } from 'app/features/library-panels/state/api';
|
||||||
|
|
||||||
|
import { PanelModel } from '../../dashboard/state';
|
||||||
|
import { LibraryElementDTO } from '../../library-panels/types';
|
||||||
|
import { DashboardJson } from '../types';
|
||||||
import { validateDashboardJson } from '../utils/validation';
|
import { validateDashboardJson } from '../utils/validation';
|
||||||
|
|
||||||
import { importDashboard } from './actions';
|
import { getLibraryPanelInputs, importDashboard, processDashboard } from './actions';
|
||||||
import { DataSourceInput, ImportDashboardDTO, initialImportDashboardState, InputType } from './reducers';
|
import { DataSourceInput, ImportDashboardDTO, initialImportDashboardState, InputType } from './reducers';
|
||||||
|
|
||||||
|
jest.mock('app/features/library-panels/state/api');
|
||||||
|
const mocks = {
|
||||||
|
getLibraryPanel: jest.mocked(getLibraryPanel),
|
||||||
|
};
|
||||||
|
|
||||||
describe('importDashboard', () => {
|
describe('importDashboard', () => {
|
||||||
it('Should send data source uid', async () => {
|
it('Should send data source uid', async () => {
|
||||||
const form: ImportDashboardDTO = {
|
const form: ImportDashboardDTO = {
|
||||||
@ -107,3 +117,643 @@ describe('validateDashboardJson', () => {
|
|||||||
expect(validateDashboardJsonNotValid).toBe('Not valid JSON');
|
expect(validateDashboardJsonNotValid).toBe('Not valid JSON');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('processDashboard', () => {
|
||||||
|
const panel = new PanelModel({
|
||||||
|
datasource: {
|
||||||
|
type: 'mysql',
|
||||||
|
uid: '${DS_GDEV-MYSQL}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const panelWithLibPanel = {
|
||||||
|
gridPos: {
|
||||||
|
h: 8,
|
||||||
|
w: 12,
|
||||||
|
x: 0,
|
||||||
|
y: 8,
|
||||||
|
},
|
||||||
|
id: 3,
|
||||||
|
libraryPanel: {
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const libPanel = {
|
||||||
|
'a0379b21-fa20-4313-bf12-d7fd7ceb6f90': {
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
kind: 1,
|
||||||
|
model: {
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: '${DS_GDEV-PROMETHEUS-FOR-LIBRARY-PANEL}',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'palette-classic',
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
axisCenteredZero: false,
|
||||||
|
axisColorMode: 'text',
|
||||||
|
axisLabel: '',
|
||||||
|
axisPlacement: 'auto',
|
||||||
|
barAlignment: 0,
|
||||||
|
drawStyle: 'line',
|
||||||
|
fillOpacity: 0,
|
||||||
|
gradientMode: 'none',
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
lineInterpolation: 'linear',
|
||||||
|
lineWidth: 1,
|
||||||
|
pointSize: 5,
|
||||||
|
scaleDistribution: {
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
showPoints: 'auto',
|
||||||
|
spanNulls: false,
|
||||||
|
stacking: {
|
||||||
|
group: 'A',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
thresholdsStyle: {
|
||||||
|
mode: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mappings: [],
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
libraryPanel: {
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'single',
|
||||||
|
sort: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
editorMode: 'builder',
|
||||||
|
expr: 'access_evaluation_duration_bucket',
|
||||||
|
instant: false,
|
||||||
|
range: true,
|
||||||
|
refId: 'A',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Panel Title',
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const panelWithSecondLibPanel = {
|
||||||
|
gridPos: {
|
||||||
|
h: 8,
|
||||||
|
w: 12,
|
||||||
|
x: 0,
|
||||||
|
y: 16,
|
||||||
|
},
|
||||||
|
id: 1,
|
||||||
|
libraryPanel: {
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const secondLibPanel = {
|
||||||
|
'c46a6b49-de40-43b3-982c-1b5e1ec084a4': {
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
kind: 1,
|
||||||
|
model: {
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: '${DS_GDEV-PROMETHEUS-FOR-LIBRARY-PANEL}',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'palette-classic',
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
axisCenteredZero: false,
|
||||||
|
axisColorMode: 'text',
|
||||||
|
axisLabel: '',
|
||||||
|
axisPlacement: 'auto',
|
||||||
|
barAlignment: 0,
|
||||||
|
drawStyle: 'line',
|
||||||
|
fillOpacity: 0,
|
||||||
|
gradientMode: 'none',
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
lineInterpolation: 'linear',
|
||||||
|
lineWidth: 1,
|
||||||
|
pointSize: 5,
|
||||||
|
scaleDistribution: {
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
showPoints: 'auto',
|
||||||
|
spanNulls: false,
|
||||||
|
stacking: {
|
||||||
|
group: 'A',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
thresholdsStyle: {
|
||||||
|
mode: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mappings: [],
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
libraryPanel: {
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'single',
|
||||||
|
sort: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
editorMode: 'builder',
|
||||||
|
expr: 'access_evaluation_duration_count',
|
||||||
|
instant: false,
|
||||||
|
range: true,
|
||||||
|
refId: 'A',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Panel Title',
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const importedJson: DashboardJson = {
|
||||||
|
...defaultDashboard,
|
||||||
|
__inputs: [
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-MYSQL',
|
||||||
|
label: 'gdev-mysql',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-PROMETHEUS-FOR-LIBRARY-PANEL',
|
||||||
|
label: 'gdev-prometheus',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
usage: {
|
||||||
|
libraryPanels: [
|
||||||
|
{
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
__elements: {
|
||||||
|
...libPanel,
|
||||||
|
},
|
||||||
|
__requires: [
|
||||||
|
{
|
||||||
|
type: 'grafana',
|
||||||
|
id: 'grafana',
|
||||||
|
name: 'Grafana',
|
||||||
|
version: '10.1.0-pre',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'datasource',
|
||||||
|
id: 'mysql',
|
||||||
|
name: 'MySQL',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'datasource',
|
||||||
|
id: 'prometheus',
|
||||||
|
name: 'Prometheus',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'panel',
|
||||||
|
id: 'table',
|
||||||
|
name: 'Table',
|
||||||
|
version: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
panels: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("Should return 2 inputs, 1 for library panel because it's used for 2 panels", async () => {
|
||||||
|
mocks.getLibraryPanel.mockImplementation(() => {
|
||||||
|
throw { status: 404 };
|
||||||
|
});
|
||||||
|
const importDashboardState = initialImportDashboardState;
|
||||||
|
const dashboardJson: DashboardJson = {
|
||||||
|
...importedJson,
|
||||||
|
panels: [panel, panelWithLibPanel, panelWithLibPanel],
|
||||||
|
};
|
||||||
|
const libPanelInputs = await getLibraryPanelInputs(dashboardJson);
|
||||||
|
const newDashboardState = {
|
||||||
|
...importDashboardState,
|
||||||
|
inputs: {
|
||||||
|
...importDashboardState.inputs,
|
||||||
|
libraryPanels: libPanelInputs!,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedDashboard = processDashboard(dashboardJson, newDashboardState);
|
||||||
|
const dsInputsForLibPanels = processedDashboard.__inputs!.filter((input) => !!input.usage?.libraryPanels);
|
||||||
|
expect(processedDashboard.__inputs).toHaveLength(2);
|
||||||
|
expect(dsInputsForLibPanels).toHaveLength(1);
|
||||||
|
});
|
||||||
|
it('Should return 3 inputs, 2 for library panels', async () => {
|
||||||
|
mocks.getLibraryPanel.mockImplementation(() => {
|
||||||
|
throw { status: 404 };
|
||||||
|
});
|
||||||
|
const importDashboardState = initialImportDashboardState;
|
||||||
|
const dashboardJson: DashboardJson = {
|
||||||
|
...importedJson,
|
||||||
|
__inputs: [
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-MYSQL',
|
||||||
|
label: 'gdev-mysql',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-PROMETHEUS-FOR-LIBRARY-PANEL',
|
||||||
|
label: 'gdev-prometheus',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
usage: {
|
||||||
|
libraryPanels: [
|
||||||
|
{
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-MYSQL-FOR-LIBRARY-PANEL',
|
||||||
|
label: 'gdev-mysql-2',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
usage: {
|
||||||
|
libraryPanels: [
|
||||||
|
{
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
__elements: {
|
||||||
|
...libPanel,
|
||||||
|
...secondLibPanel,
|
||||||
|
},
|
||||||
|
panels: [panel, panelWithLibPanel, panelWithSecondLibPanel],
|
||||||
|
};
|
||||||
|
const libPanelInputs = await getLibraryPanelInputs(dashboardJson);
|
||||||
|
const newDashboardState = {
|
||||||
|
...importDashboardState,
|
||||||
|
inputs: {
|
||||||
|
...importDashboardState.inputs,
|
||||||
|
libraryPanels: libPanelInputs!,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedDashboard = processDashboard(dashboardJson, newDashboardState);
|
||||||
|
const dsInputsForLibPanels = processedDashboard.__inputs!.filter((input) => !!input.usage?.libraryPanels);
|
||||||
|
expect(processedDashboard.__inputs).toHaveLength(3);
|
||||||
|
expect(dsInputsForLibPanels).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return 1 input, since library panels already exist in the instance', async () => {
|
||||||
|
const getLibPanelFirstRS: LibraryElementDTO = {
|
||||||
|
folderUid: '',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
type: 'timeseries',
|
||||||
|
description: '',
|
||||||
|
model: {
|
||||||
|
transparent: false,
|
||||||
|
transformations: [],
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: FieldColorModeId.PaletteClassic,
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
axisCenteredZero: false,
|
||||||
|
axisColorMode: 'text',
|
||||||
|
axisLabel: '',
|
||||||
|
axisPlacement: 'auto',
|
||||||
|
barAlignment: 0,
|
||||||
|
drawStyle: 'line',
|
||||||
|
fillOpacity: 0,
|
||||||
|
gradientMode: 'none',
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
lineInterpolation: 'linear',
|
||||||
|
lineWidth: 1,
|
||||||
|
pointSize: 5,
|
||||||
|
scaleDistribution: {
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
showPoints: 'auto',
|
||||||
|
spanNulls: false,
|
||||||
|
stacking: {
|
||||||
|
group: 'A',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
thresholdsStyle: {
|
||||||
|
mode: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mappings: [],
|
||||||
|
thresholds: {
|
||||||
|
mode: ThresholdsMode.Absolute,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'single',
|
||||||
|
sort: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
editorMode: 'builder',
|
||||||
|
expr: 'access_evaluation_duration_bucket',
|
||||||
|
instant: false,
|
||||||
|
range: true,
|
||||||
|
refId: 'A',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Panel Title',
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLibPanelSecondRS: LibraryElementDTO = {
|
||||||
|
folderUid: '',
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
type: 'timeseries',
|
||||||
|
description: '',
|
||||||
|
model: {
|
||||||
|
transparent: false,
|
||||||
|
transformations: [],
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: FieldColorModeId.PaletteClassic,
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
axisCenteredZero: false,
|
||||||
|
axisColorMode: 'text',
|
||||||
|
axisLabel: '',
|
||||||
|
axisPlacement: 'auto',
|
||||||
|
barAlignment: 0,
|
||||||
|
drawStyle: 'line',
|
||||||
|
fillOpacity: 0,
|
||||||
|
gradientMode: 'none',
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
lineInterpolation: 'linear',
|
||||||
|
lineWidth: 1,
|
||||||
|
pointSize: 5,
|
||||||
|
scaleDistribution: {
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
showPoints: 'auto',
|
||||||
|
spanNulls: false,
|
||||||
|
stacking: {
|
||||||
|
group: 'A',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
thresholdsStyle: {
|
||||||
|
mode: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mappings: [],
|
||||||
|
thresholds: {
|
||||||
|
mode: ThresholdsMode.Absolute,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'single',
|
||||||
|
sort: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
datasource: {
|
||||||
|
type: 'prometheus',
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
},
|
||||||
|
editorMode: 'builder',
|
||||||
|
expr: 'access_evaluation_duration_count',
|
||||||
|
instant: false,
|
||||||
|
range: true,
|
||||||
|
refId: 'A',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Panel Title',
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
mocks.getLibraryPanel
|
||||||
|
.mockReturnValueOnce(Promise.resolve(getLibPanelFirstRS))
|
||||||
|
.mockReturnValueOnce(Promise.resolve(getLibPanelSecondRS));
|
||||||
|
|
||||||
|
const importDashboardState = initialImportDashboardState;
|
||||||
|
const dashboardJson: DashboardJson = {
|
||||||
|
...importedJson,
|
||||||
|
__inputs: [
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-MYSQL',
|
||||||
|
label: 'gdev-mysql',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-PROMETHEUS-FOR-LIBRARY-PANEL',
|
||||||
|
label: 'gdev-prometheus',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
usage: {
|
||||||
|
libraryPanels: [
|
||||||
|
{
|
||||||
|
name: 'another prom lib panel',
|
||||||
|
uid: 'a0379b21-fa20-4313-bf12-d7fd7ceb6f90',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DS_GDEV-MYSQL-FOR-LIBRARY-PANEL',
|
||||||
|
label: 'gdev-mysql-2',
|
||||||
|
description: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
usage: {
|
||||||
|
libraryPanels: [
|
||||||
|
{
|
||||||
|
uid: 'c46a6b49-de40-43b3-982c-1b5e1ec084a4',
|
||||||
|
name: 'Testing lib panel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
__elements: {
|
||||||
|
...libPanel,
|
||||||
|
...secondLibPanel,
|
||||||
|
},
|
||||||
|
panels: [panel, panelWithLibPanel, panelWithSecondLibPanel],
|
||||||
|
};
|
||||||
|
const libPanelInputs = await getLibraryPanelInputs(dashboardJson);
|
||||||
|
const newDashboardState = {
|
||||||
|
...importDashboardState,
|
||||||
|
inputs: {
|
||||||
|
...importDashboardState.inputs,
|
||||||
|
libraryPanels: libPanelInputs!,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedDashboard = processDashboard(dashboardJson, newDashboardState);
|
||||||
|
const dsInputsForLibPanels = processedDashboard.__inputs!.filter((input) => !!input.usage?.libraryPanels);
|
||||||
|
expect(processedDashboard.__inputs).toHaveLength(1);
|
||||||
|
expect(dsInputsForLibPanels).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
import { DataSourceInstanceSettings, locationUtil } from '@grafana/data';
|
import { DataSourceInstanceSettings, locationUtil } from '@grafana/data';
|
||||||
import { getDataSourceSrv, locationService, getBackendSrv, isFetchError } from '@grafana/runtime';
|
import { getBackendSrv, getDataSourceSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
||||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||||
import { DashboardDTO, FolderInfo, PermissionLevelString, SearchQueryType, ThunkResult } from 'app/types';
|
import { DashboardDTO, FolderInfo, PermissionLevelString, SearchQueryType, ThunkResult } from 'app/types';
|
||||||
|
|
||||||
import { LibraryElementExport } from '../../dashboard/components/DashExportModal/DashboardExporter';
|
import {
|
||||||
|
Input,
|
||||||
|
InputUsage,
|
||||||
|
LibraryElementExport,
|
||||||
|
LibraryPanel,
|
||||||
|
} from '../../dashboard/components/DashExportModal/DashboardExporter';
|
||||||
import { getLibraryPanel } from '../../library-panels/state/api';
|
import { getLibraryPanel } from '../../library-panels/state/api';
|
||||||
import { LibraryElementDTO, LibraryElementKind } from '../../library-panels/types';
|
import { LibraryElementDTO, LibraryElementKind } from '../../library-panels/types';
|
||||||
import { DashboardSearchHit } from '../../search/types';
|
import { DashboardSearchHit } from '../../search/types';
|
||||||
import { DeleteDashboardResponse } from '../types';
|
import { DashboardJson, DeleteDashboardResponse } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearDashboard,
|
clearDashboard,
|
||||||
fetchDashboard,
|
fetchDashboard,
|
||||||
fetchFailed,
|
fetchFailed,
|
||||||
ImportDashboardDTO,
|
ImportDashboardDTO,
|
||||||
|
ImportDashboardState,
|
||||||
InputType,
|
InputType,
|
||||||
LibraryPanelInput,
|
LibraryPanelInput,
|
||||||
LibraryPanelInputState,
|
LibraryPanelInputState,
|
||||||
@ -31,9 +37,9 @@ export function fetchGcomDashboard(id: string): ThunkResult<void> {
|
|||||||
try {
|
try {
|
||||||
dispatch(fetchDashboard());
|
dispatch(fetchDashboard());
|
||||||
const dashboard = await getBackendSrv().get(`/api/gnet/dashboards/${id}`);
|
const dashboard = await getBackendSrv().get(`/api/gnet/dashboards/${id}`);
|
||||||
dispatch(setGcomDashboard(dashboard));
|
await dispatch(processElements(dashboard.json));
|
||||||
dispatch(processInputs(dashboard.json));
|
await dispatch(processGcomDashboard(dashboard));
|
||||||
dispatch(processElements(dashboard.json));
|
dispatch(processInputs());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(fetchFailed());
|
dispatch(fetchFailed());
|
||||||
if (isFetchError(error)) {
|
if (isFetchError(error)) {
|
||||||
@ -45,17 +51,66 @@ export function fetchGcomDashboard(id: string): ThunkResult<void> {
|
|||||||
|
|
||||||
export function importDashboardJson(dashboard: any): ThunkResult<void> {
|
export function importDashboardJson(dashboard: any): ThunkResult<void> {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(setJsonDashboard(dashboard));
|
await dispatch(processElements(dashboard));
|
||||||
dispatch(processInputs(dashboard));
|
await dispatch(processJsonDashboard(dashboard));
|
||||||
dispatch(processElements(dashboard));
|
dispatch(processInputs());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function processInputs(dashboardJson: any): ThunkResult<void> {
|
const getNewLibraryPanelsByInput = (input: Input, state: ImportDashboardState): LibraryPanel[] | undefined => {
|
||||||
return (dispatch) => {
|
return input?.usage?.libraryPanels?.filter((usageLibPanel) =>
|
||||||
if (dashboardJson && dashboardJson.__inputs) {
|
state.inputs.libraryPanels.some(
|
||||||
|
(libPanel) => libPanel.state !== LibraryPanelInputState.Exists && libPanel.model.uid === usageLibPanel.uid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function processDashboard(dashboardJson: DashboardJson, state: ImportDashboardState): DashboardJson {
|
||||||
|
let inputs = dashboardJson.__inputs;
|
||||||
|
if (!!state.inputs.libraryPanels?.length) {
|
||||||
|
const filteredUsedInputs: Input[] = [];
|
||||||
|
dashboardJson.__inputs?.forEach((input: Input) => {
|
||||||
|
if (!input?.usage?.libraryPanels) {
|
||||||
|
filteredUsedInputs.push(input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLibraryPanels = getNewLibraryPanelsByInput(input, state);
|
||||||
|
input.usage = { libraryPanels: newLibraryPanels };
|
||||||
|
|
||||||
|
const isInputBeingUsedByANewLibraryPanel = !!newLibraryPanels?.length;
|
||||||
|
if (isInputBeingUsedByANewLibraryPanel) {
|
||||||
|
filteredUsedInputs.push(input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
inputs = filteredUsedInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...dashboardJson, __inputs: inputs };
|
||||||
|
}
|
||||||
|
|
||||||
|
function processGcomDashboard(dashboard: { json: DashboardJson }): ThunkResult<void> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState().importDashboard;
|
||||||
|
const dashboardJson = processDashboard(dashboard.json, state);
|
||||||
|
dispatch(setGcomDashboard({ ...dashboard, json: dashboardJson }));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function processJsonDashboard(dashboardJson: DashboardJson): ThunkResult<void> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState().importDashboard;
|
||||||
|
const dashboard = processDashboard(dashboardJson, state);
|
||||||
|
dispatch(setJsonDashboard(dashboard));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function processInputs(): ThunkResult<void> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const dashboard = getState().importDashboard.dashboard;
|
||||||
|
if (dashboard && dashboard.__inputs) {
|
||||||
const inputs: any[] = [];
|
const inputs: any[] = [];
|
||||||
dashboardJson.__inputs.forEach((input: any) => {
|
dashboard.__inputs.forEach((input: any) => {
|
||||||
const inputModel: any = {
|
const inputModel: any = {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
label: input.label,
|
label: input.label,
|
||||||
@ -66,6 +121,8 @@ function processInputs(dashboardJson: any): ThunkResult<void> {
|
|||||||
options: [],
|
options: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inputModel.description = getDataSourceDescription(input);
|
||||||
|
|
||||||
if (input.type === InputType.DataSource) {
|
if (input.type === InputType.DataSource) {
|
||||||
getDataSourceOptions(input, inputModel);
|
getDataSourceOptions(input, inputModel);
|
||||||
} else if (!inputModel.info) {
|
} else if (!inputModel.info) {
|
||||||
@ -81,8 +138,16 @@ function processInputs(dashboardJson: any): ThunkResult<void> {
|
|||||||
|
|
||||||
function processElements(dashboardJson?: { __elements?: Record<string, LibraryElementExport> }): ThunkResult<void> {
|
function processElements(dashboardJson?: { __elements?: Record<string, LibraryElementExport> }): ThunkResult<void> {
|
||||||
return async function (dispatch) {
|
return async function (dispatch) {
|
||||||
|
const libraryPanelInputs = await getLibraryPanelInputs(dashboardJson);
|
||||||
|
dispatch(setLibraryPanelInputs(libraryPanelInputs));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLibraryPanelInputs(dashboardJson?: {
|
||||||
|
__elements?: Record<string, LibraryElementExport>;
|
||||||
|
}): Promise<LibraryPanelInput[]> {
|
||||||
if (!dashboardJson || !dashboardJson.__elements) {
|
if (!dashboardJson || !dashboardJson.__elements) {
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const libraryPanelInputs: LibraryPanelInput[] = [];
|
const libraryPanelInputs: LibraryPanelInput[] = [];
|
||||||
@ -121,8 +186,7 @@ function processElements(dashboardJson?: { __elements?: Record<string, LibraryEl
|
|||||||
libraryPanelInputs.push(input);
|
libraryPanelInputs.push(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setLibraryPanelInputs(libraryPanelInputs));
|
return libraryPanelInputs;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearLoadedDashboard(): ThunkResult<void> {
|
export function clearLoadedDashboard(): ThunkResult<void> {
|
||||||
@ -182,6 +246,22 @@ const getDataSourceOptions = (input: { pluginId: string; pluginName: string }, i
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDataSourceDescription = (input: { usage?: InputUsage }): string | undefined => {
|
||||||
|
if (!input.usage) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.usage.libraryPanels) {
|
||||||
|
const libPanelNames = input.usage.libraryPanels.reduce(
|
||||||
|
(acc: string, libPanel, index) => (index === 0 ? libPanel.name : `${acc}, ${libPanel.name}`),
|
||||||
|
''
|
||||||
|
);
|
||||||
|
return `List of affected library panels: ${libPanelNames}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export async function moveFolders(folderUIDs: string[], toFolder: FolderInfo) {
|
export async function moveFolders(folderUIDs: string[], toFolder: FolderInfo) {
|
||||||
const result = {
|
const result = {
|
||||||
totalCount: folderUIDs.length,
|
totalCount: folderUIDs.length,
|
||||||
|
@ -34,6 +34,7 @@ export enum LibraryPanelInputState {
|
|||||||
export interface DashboardInput {
|
export interface DashboardInput {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
description?: string;
|
||||||
info: string;
|
info: string;
|
||||||
value: string;
|
value: string;
|
||||||
type: InputType;
|
type: InputType;
|
||||||
@ -100,7 +101,7 @@ const importDashboardSlice = createSlice({
|
|||||||
state.inputs = {
|
state.inputs = {
|
||||||
dataSources: action.payload.filter((p) => p.type === InputType.DataSource),
|
dataSources: action.payload.filter((p) => p.type === InputType.DataSource),
|
||||||
constants: action.payload.filter((p) => p.type === InputType.Constant),
|
constants: action.payload.filter((p) => p.type === InputType.Constant),
|
||||||
libraryPanels: [],
|
libraryPanels: state.inputs.libraryPanels || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setLibraryPanelInputs: (state: Draft<ImportDashboardState>, action: PayloadAction<LibraryPanelInput[]>) => {
|
setLibraryPanelInputs: (state: Draft<ImportDashboardState>, action: PayloadAction<LibraryPanelInput[]>) => {
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { Dashboard } from '@grafana/schema/src/veneer/dashboard.types';
|
||||||
|
|
||||||
|
import { ExternalDashboard } from '../dashboard/components/DashExportModal/DashboardExporter';
|
||||||
|
|
||||||
export interface Snapshot {
|
export interface Snapshot {
|
||||||
created: string;
|
created: string;
|
||||||
expires: string;
|
expires: string;
|
||||||
@ -36,3 +40,5 @@ export interface PublicDashboardListResponse {
|
|||||||
export interface PublicDashboardListWithPagination extends PublicDashboardListWithPaginationResponse {
|
export interface PublicDashboardListWithPagination extends PublicDashboardListWithPaginationResponse {
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DashboardJson = ExternalDashboard & Omit<Dashboard, 'panels'>;
|
||||||
|
Loading…
Reference in New Issue
Block a user