mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 14:12:26 +08:00
Dashlist: Use new nested folder picker (#74011)
* Add new folderUID property * Add nested folder picker + migration to UID * fix folderUID * comment
This commit is contained in:
@ -4496,7 +4496,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/panel/dashlist/module.tsx:5381": [
|
"public/app/plugins/panel/dashlist/migrations.test.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/panel/debug/CursorView.tsx:5381": [
|
"public/app/plugins/panel/debug/CursorView.tsx:5381": [
|
||||||
|
@ -24,17 +24,18 @@ title: DashboardListPanelCfg kind
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Property | Type | Required | Default | Description |
|
| Property | Type | Required | Default | Description |
|
||||||
|----------------------|----------|----------|---------|-------------|
|
|----------------------|----------|----------|---------|-----------------------------------------------------------------|
|
||||||
| `includeVars` | boolean | **Yes** | `false` | |
|
| `includeVars` | boolean | **Yes** | `false` | |
|
||||||
| `keepTime` | boolean | **Yes** | `false` | |
|
| `keepTime` | boolean | **Yes** | `false` | |
|
||||||
| `maxItems` | integer | **Yes** | `10` | |
|
| `maxItems` | integer | **Yes** | `10` | |
|
||||||
| `query` | string | **Yes** | `` | |
|
| `query` | string | **Yes** | `` | |
|
||||||
| `showHeadings` | boolean | **Yes** | `true` | |
|
| `showHeadings` | boolean | **Yes** | `true` | |
|
||||||
| `showRecentlyViewed` | boolean | **Yes** | `false` | |
|
| `showRecentlyViewed` | boolean | **Yes** | `false` | |
|
||||||
| `showSearch` | boolean | **Yes** | `false` | |
|
| `showSearch` | boolean | **Yes** | `false` | |
|
||||||
| `showStarred` | boolean | **Yes** | `true` | |
|
| `showStarred` | boolean | **Yes** | `true` | |
|
||||||
| `tags` | string[] | **Yes** | | |
|
| `tags` | string[] | **Yes** | | |
|
||||||
| `folderId` | integer | No | | |
|
| `folderId` | integer | No | | folderId is deprecated, and migrated to folderUid on panel init |
|
||||||
|
| `folderUID` | string | No | | |
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,11 @@
|
|||||||
export const pluginVersion = "10.2.0-pre";
|
export const pluginVersion = "10.2.0-pre";
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
|
/**
|
||||||
|
* folderId is deprecated, and migrated to folderUid on panel init
|
||||||
|
*/
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
|
folderUID?: string;
|
||||||
includeVars: boolean;
|
includeVars: boolean;
|
||||||
keepTime: boolean;
|
keepTime: boolean;
|
||||||
maxItems: number;
|
maxItems: number;
|
||||||
|
@ -41,10 +41,11 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio
|
|||||||
|
|
||||||
let searchedDashboards: Promise<DashboardSearchItem[]> = Promise.resolve([]);
|
let searchedDashboards: Promise<DashboardSearchItem[]> = Promise.resolve([]);
|
||||||
if (options.showSearch) {
|
if (options.showSearch) {
|
||||||
|
const uid = options.folderUID === '' ? 'general' : options.folderUID;
|
||||||
const params = {
|
const params = {
|
||||||
limit: options.maxItems,
|
limit: options.maxItems,
|
||||||
query: replaceVars(options.query, {}, 'text'),
|
query: replaceVars(options.query, {}, 'text'),
|
||||||
folderIds: options.folderId,
|
folderUIDs: uid,
|
||||||
tag: options.tags.map((tag: string) => replaceVars(tag, {}, 'text')),
|
tag: options.tags.map((tag: string) => replaceVars(tag, {}, 'text')),
|
||||||
type: 'dash-db',
|
type: 'dash-db',
|
||||||
};
|
};
|
||||||
|
77
public/app/plugins/panel/dashlist/migrations.test.ts
Normal file
77
public/app/plugins/panel/dashlist/migrations.test.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { wellFormedPanelModel } from 'test/fixtures/panelModel.fixture';
|
||||||
|
|
||||||
|
import { PanelModel } from '@grafana/data';
|
||||||
|
import { mockFolderDTO } from 'app/features/browse-dashboards/fixtures/folder.fixture';
|
||||||
|
|
||||||
|
import { dashlistMigrationHandler, AngularModel } from './migrations';
|
||||||
|
|
||||||
|
const getMock = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
getBackendSrv: () => ({
|
||||||
|
get: getMock,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('dashlist migrations', () => {
|
||||||
|
it('migrates angular panel model to react model', async () => {
|
||||||
|
const basePanelModel = wellFormedPanelModel({});
|
||||||
|
basePanelModel.pluginVersion = '5.1';
|
||||||
|
|
||||||
|
const angularPanel: PanelModel<any> & AngularModel = {
|
||||||
|
...basePanelModel,
|
||||||
|
// pluginVersion: '5.1',
|
||||||
|
starred: true,
|
||||||
|
recent: true,
|
||||||
|
search: true,
|
||||||
|
headings: true,
|
||||||
|
limit: 7,
|
||||||
|
query: 'hello, query',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newOptions = await dashlistMigrationHandler(angularPanel);
|
||||||
|
expect(newOptions).toEqual({
|
||||||
|
showStarred: true,
|
||||||
|
showRecentlyViewed: true,
|
||||||
|
showSearch: true,
|
||||||
|
showHeadings: true,
|
||||||
|
maxItems: 7,
|
||||||
|
query: 'hello, query',
|
||||||
|
includeVars: undefined,
|
||||||
|
keepTime: undefined,
|
||||||
|
});
|
||||||
|
expect(angularPanel).toStrictEqual(basePanelModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('migrates folder id to folder UID', async () => {
|
||||||
|
const folderDTO = mockFolderDTO(1, {
|
||||||
|
id: 77,
|
||||||
|
uid: 'abc-124',
|
||||||
|
});
|
||||||
|
getMock.mockResolvedValue(folderDTO);
|
||||||
|
|
||||||
|
const basePanelOptions = {
|
||||||
|
showStarred: true,
|
||||||
|
showRecentlyViewed: true,
|
||||||
|
showSearch: true,
|
||||||
|
showHeadings: true,
|
||||||
|
maxItems: 7,
|
||||||
|
query: 'hello, query',
|
||||||
|
includeVars: false,
|
||||||
|
keepTime: false,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
const panelModel = wellFormedPanelModel({
|
||||||
|
...basePanelOptions,
|
||||||
|
folderId: 77,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newOptions = await dashlistMigrationHandler(panelModel);
|
||||||
|
|
||||||
|
expect(newOptions).toStrictEqual({
|
||||||
|
...basePanelOptions,
|
||||||
|
folderUID: 'abc-124',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
60
public/app/plugins/panel/dashlist/migrations.ts
Normal file
60
public/app/plugins/panel/dashlist/migrations.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { PanelModel } from '@grafana/data';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { FolderDTO } from 'app/types';
|
||||||
|
|
||||||
|
import { Options } from './panelcfg.gen';
|
||||||
|
|
||||||
|
function getFolderByID(folderID: number) {
|
||||||
|
return getBackendSrv().get<FolderDTO>(`/api/folders/id/${folderID}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AngularModel {
|
||||||
|
/** @deprecated */
|
||||||
|
starred?: boolean;
|
||||||
|
/** @deprecated */
|
||||||
|
recent?: boolean;
|
||||||
|
/** @deprecated */
|
||||||
|
search?: boolean;
|
||||||
|
/** @deprecated */
|
||||||
|
headings?: boolean;
|
||||||
|
/** @deprecated */
|
||||||
|
limit?: number;
|
||||||
|
/** @deprecated */
|
||||||
|
query?: string;
|
||||||
|
/** @deprecated */
|
||||||
|
folderId?: number;
|
||||||
|
/** @deprecated */
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function dashlistMigrationHandler(panel: PanelModel<Options> & AngularModel) {
|
||||||
|
// Convert old angular model to new react model
|
||||||
|
const newOptions: Options = {
|
||||||
|
...panel.options,
|
||||||
|
showStarred: panel.options.showStarred ?? panel.starred,
|
||||||
|
showRecentlyViewed: panel.options.showRecentlyViewed ?? panel.recent,
|
||||||
|
showSearch: panel.options.showSearch ?? panel.search,
|
||||||
|
showHeadings: panel.options.showHeadings ?? panel.headings,
|
||||||
|
maxItems: panel.options.maxItems ?? panel.limit,
|
||||||
|
query: panel.options.query ?? panel.query,
|
||||||
|
folderId: panel.options.folderId ?? panel.folderId,
|
||||||
|
tags: panel.options.tags ?? panel.tags,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete old angular properties
|
||||||
|
const previousVersion = parseFloat(panel.pluginVersion || '6.1');
|
||||||
|
if (previousVersion < 6.3) {
|
||||||
|
const oldProps = ['starred', 'recent', 'search', 'headings', 'limit', 'query', 'folderId'] as const;
|
||||||
|
oldProps.forEach((prop) => delete panel[prop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the folderId to folderUID. Uses the API to do the conversion.
|
||||||
|
if (newOptions.folderId !== undefined) {
|
||||||
|
const folderId = newOptions.folderId;
|
||||||
|
const folderResp = await getFolderByID(folderId);
|
||||||
|
newOptions.folderUID = folderResp.uid;
|
||||||
|
delete newOptions.folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOptions;
|
||||||
|
}
|
@ -1,15 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { PanelModel, PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { TagsInput } from '@grafana/ui';
|
import { TagsInput } from '@grafana/ui';
|
||||||
|
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||||
import {
|
|
||||||
ALL_FOLDER,
|
|
||||||
GENERAL_FOLDER,
|
|
||||||
ReadonlyFolderPicker,
|
|
||||||
} from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker';
|
|
||||||
|
|
||||||
import { DashList } from './DashList';
|
import { DashList } from './DashList';
|
||||||
|
import { dashlistMigrationHandler } from './migrations';
|
||||||
import { defaultOptions, Options } from './panelcfg.gen';
|
import { defaultOptions, Options } from './panelcfg.gen';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<Options>(DashList)
|
export const plugin = new PanelPlugin<Options>(DashList)
|
||||||
@ -56,18 +52,12 @@ export const plugin = new PanelPlugin<Options>(DashList)
|
|||||||
defaultValue: defaultOptions.query,
|
defaultValue: defaultOptions.query,
|
||||||
})
|
})
|
||||||
.addCustomEditor({
|
.addCustomEditor({
|
||||||
path: 'folderId',
|
path: 'folderUid',
|
||||||
name: 'Folder',
|
name: 'Folder',
|
||||||
id: 'folderId',
|
id: 'folderUid',
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
editor: function RenderFolderPicker({ value, onChange }) {
|
editor: function RenderFolderPicker({ value, onChange }) {
|
||||||
return (
|
return <FolderPicker value={value} onChange={(folderUID) => onChange(folderUID)} />;
|
||||||
<ReadonlyFolderPicker
|
|
||||||
initialFolderId={value}
|
|
||||||
onChange={(folder) => onChange(folder?.id)}
|
|
||||||
extraFolders={[ALL_FOLDER, GENERAL_FOLDER]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addCustomEditor({
|
.addCustomEditor({
|
||||||
@ -81,23 +71,4 @@ export const plugin = new PanelPlugin<Options>(DashList)
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.setMigrationHandler((panel: PanelModel<Options> & Record<string, any>) => {
|
.setMigrationHandler(dashlistMigrationHandler);
|
||||||
const newOptions = {
|
|
||||||
showStarred: panel.options.showStarred ?? panel.starred,
|
|
||||||
showRecentlyViewed: panel.options.showRecentlyViewed ?? panel.recent,
|
|
||||||
showSearch: panel.options.showSearch ?? panel.search,
|
|
||||||
showHeadings: panel.options.showHeadings ?? panel.headings,
|
|
||||||
maxItems: panel.options.maxItems ?? panel.limit,
|
|
||||||
query: panel.options.query ?? panel.query,
|
|
||||||
folderId: panel.options.folderId ?? panel.folderId,
|
|
||||||
tags: panel.options.tags ?? panel.tags,
|
|
||||||
};
|
|
||||||
|
|
||||||
const previousVersion = parseFloat(panel.pluginVersion || '6.1');
|
|
||||||
if (previousVersion < 6.3) {
|
|
||||||
const oldProps = ['starred', 'recent', 'search', 'headings', 'limit', 'query', 'folderId'];
|
|
||||||
oldProps.forEach((prop) => delete panel[prop]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newOptions;
|
|
||||||
});
|
|
||||||
|
@ -30,8 +30,10 @@ composableKinds: PanelCfg: {
|
|||||||
showHeadings: bool | *true
|
showHeadings: bool | *true
|
||||||
maxItems: int | *10
|
maxItems: int | *10
|
||||||
query: string | *""
|
query: string | *""
|
||||||
folderId?: int
|
|
||||||
tags: [...string] | *[]
|
tags: [...string] | *[]
|
||||||
|
// folderId is deprecated, and migrated to folderUid on panel init
|
||||||
|
folderId?: int
|
||||||
|
folderUID?: string
|
||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -9,7 +9,11 @@
|
|||||||
// Run 'make gen-cue' from repository root to regenerate.
|
// Run 'make gen-cue' from repository root to regenerate.
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
|
/**
|
||||||
|
* folderId is deprecated, and migrated to folderUid on panel init
|
||||||
|
*/
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
|
folderUID?: string;
|
||||||
includeVars: boolean;
|
includeVars: boolean;
|
||||||
keepTime: boolean;
|
keepTime: boolean;
|
||||||
maxItems: number;
|
maxItems: number;
|
||||||
|
21
public/test/fixtures/panelModel.fixture.ts
vendored
Normal file
21
public/test/fixtures/panelModel.fixture.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Chance } from 'chance';
|
||||||
|
|
||||||
|
import { PanelModel } from '@grafana/data';
|
||||||
|
|
||||||
|
export function wellFormedPanelModel<T extends object>(panelOptions: T, seed = 1): PanelModel<T> {
|
||||||
|
const random = Chance(seed);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: random.integer(),
|
||||||
|
type: random.word(),
|
||||||
|
title: random.sentence({ words: 3 }),
|
||||||
|
description: random.sentence({ words: 10 }),
|
||||||
|
options: panelOptions,
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
pluginVersion: '9.5.0',
|
||||||
|
targets: [],
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user