From 44e51ffe8ba09d66e3852f11a157715fc3db001a Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 7 Sep 2023 10:57:31 +0000 Subject: [PATCH] Dashlist: Use new nested folder picker (#74011) * Add new folderUID property * Add nested folder picker + migration to UID * fix folderUID * comment --- .betterer.results | 2 +- .../panelcfg/schema-reference.md | 25 +++--- .../x/DashboardListPanelCfg_types.gen.ts | 4 + .../app/plugins/panel/dashlist/DashList.tsx | 3 +- .../plugins/panel/dashlist/migrations.test.ts | 77 +++++++++++++++++++ .../app/plugins/panel/dashlist/migrations.ts | 60 +++++++++++++++ public/app/plugins/panel/dashlist/module.tsx | 43 ++--------- .../app/plugins/panel/dashlist/panelcfg.cue | 4 +- .../plugins/panel/dashlist/panelcfg.gen.ts | 4 + public/test/fixtures/panelModel.fixture.ts | 21 +++++ 10 files changed, 192 insertions(+), 51 deletions(-) create mode 100644 public/app/plugins/panel/dashlist/migrations.test.ts create mode 100644 public/app/plugins/panel/dashlist/migrations.ts create mode 100644 public/test/fixtures/panelModel.fixture.ts diff --git a/.betterer.results b/.betterer.results index 8b57e0fa9f2..f84dedb5989 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4496,7 +4496,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "4"], [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"] ], "public/app/plugins/panel/debug/CursorView.tsx:5381": [ diff --git a/docs/sources/developers/kinds/composable/dashboardlist/panelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/dashboardlist/panelcfg/schema-reference.md index 6b41918c182..732b4ea5837 100644 --- a/docs/sources/developers/kinds/composable/dashboardlist/panelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/dashboardlist/panelcfg/schema-reference.md @@ -24,17 +24,18 @@ title: DashboardListPanelCfg kind ### Options -| Property | Type | Required | Default | Description | -|----------------------|----------|----------|---------|-------------| -| `includeVars` | boolean | **Yes** | `false` | | -| `keepTime` | boolean | **Yes** | `false` | | -| `maxItems` | integer | **Yes** | `10` | | -| `query` | string | **Yes** | `` | | -| `showHeadings` | boolean | **Yes** | `true` | | -| `showRecentlyViewed` | boolean | **Yes** | `false` | | -| `showSearch` | boolean | **Yes** | `false` | | -| `showStarred` | boolean | **Yes** | `true` | | -| `tags` | string[] | **Yes** | | | -| `folderId` | integer | No | | | +| Property | Type | Required | Default | Description | +|----------------------|----------|----------|---------|-----------------------------------------------------------------| +| `includeVars` | boolean | **Yes** | `false` | | +| `keepTime` | boolean | **Yes** | `false` | | +| `maxItems` | integer | **Yes** | `10` | | +| `query` | string | **Yes** | `` | | +| `showHeadings` | boolean | **Yes** | `true` | | +| `showRecentlyViewed` | boolean | **Yes** | `false` | | +| `showSearch` | boolean | **Yes** | `false` | | +| `showStarred` | boolean | **Yes** | `true` | | +| `tags` | string[] | **Yes** | | | +| `folderId` | integer | No | | folderId is deprecated, and migrated to folderUid on panel init | +| `folderUID` | string | No | | | diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index dccc3f0feb3..946075837d4 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -12,7 +12,11 @@ export const pluginVersion = "10.2.0-pre"; export interface Options { + /** + * folderId is deprecated, and migrated to folderUid on panel init + */ folderId?: number; + folderUID?: string; includeVars: boolean; keepTime: boolean; maxItems: number; diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index 84dcd3d8cd8..9f773eb0099 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -41,10 +41,11 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio let searchedDashboards: Promise = Promise.resolve([]); if (options.showSearch) { + const uid = options.folderUID === '' ? 'general' : options.folderUID; const params = { limit: options.maxItems, query: replaceVars(options.query, {}, 'text'), - folderIds: options.folderId, + folderUIDs: uid, tag: options.tags.map((tag: string) => replaceVars(tag, {}, 'text')), type: 'dash-db', }; diff --git a/public/app/plugins/panel/dashlist/migrations.test.ts b/public/app/plugins/panel/dashlist/migrations.test.ts new file mode 100644 index 00000000000..2f708ef576c --- /dev/null +++ b/public/app/plugins/panel/dashlist/migrations.test.ts @@ -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 & 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', + }); + }); +}); diff --git a/public/app/plugins/panel/dashlist/migrations.ts b/public/app/plugins/panel/dashlist/migrations.ts new file mode 100644 index 00000000000..ce03112190f --- /dev/null +++ b/public/app/plugins/panel/dashlist/migrations.ts @@ -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(`/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 & 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; +} diff --git a/public/app/plugins/panel/dashlist/module.tsx b/public/app/plugins/panel/dashlist/module.tsx index 35dfb485817..13a438e14dc 100644 --- a/public/app/plugins/panel/dashlist/module.tsx +++ b/public/app/plugins/panel/dashlist/module.tsx @@ -1,15 +1,11 @@ import React from 'react'; -import { PanelModel, PanelPlugin } from '@grafana/data'; +import { PanelPlugin } from '@grafana/data'; import { TagsInput } from '@grafana/ui'; - -import { - ALL_FOLDER, - GENERAL_FOLDER, - ReadonlyFolderPicker, -} from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker'; +import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { DashList } from './DashList'; +import { dashlistMigrationHandler } from './migrations'; import { defaultOptions, Options } from './panelcfg.gen'; export const plugin = new PanelPlugin(DashList) @@ -56,18 +52,12 @@ export const plugin = new PanelPlugin(DashList) defaultValue: defaultOptions.query, }) .addCustomEditor({ - path: 'folderId', + path: 'folderUid', name: 'Folder', - id: 'folderId', + id: 'folderUid', defaultValue: undefined, editor: function RenderFolderPicker({ value, onChange }) { - return ( - onChange(folder?.id)} - extraFolders={[ALL_FOLDER, GENERAL_FOLDER]} - /> - ); + return onChange(folderUID)} />; }, }) .addCustomEditor({ @@ -81,23 +71,4 @@ export const plugin = new PanelPlugin(DashList) }, }); }) - .setMigrationHandler((panel: PanelModel & Record) => { - 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; - }); + .setMigrationHandler(dashlistMigrationHandler); diff --git a/public/app/plugins/panel/dashlist/panelcfg.cue b/public/app/plugins/panel/dashlist/panelcfg.cue index a96580d431b..3ef8be5679c 100644 --- a/public/app/plugins/panel/dashlist/panelcfg.cue +++ b/public/app/plugins/panel/dashlist/panelcfg.cue @@ -30,8 +30,10 @@ composableKinds: PanelCfg: { showHeadings: bool | *true maxItems: int | *10 query: string | *"" - folderId?: int tags: [...string] | *[] + // folderId is deprecated, and migrated to folderUid on panel init + folderId?: int + folderUID?: string } @cuetsy(kind="interface") } }] diff --git a/public/app/plugins/panel/dashlist/panelcfg.gen.ts b/public/app/plugins/panel/dashlist/panelcfg.gen.ts index 7b0b071a719..4c12168d0a5 100644 --- a/public/app/plugins/panel/dashlist/panelcfg.gen.ts +++ b/public/app/plugins/panel/dashlist/panelcfg.gen.ts @@ -9,7 +9,11 @@ // Run 'make gen-cue' from repository root to regenerate. export interface Options { + /** + * folderId is deprecated, and migrated to folderUid on panel init + */ folderId?: number; + folderUID?: string; includeVars: boolean; keepTime: boolean; maxItems: number; diff --git a/public/test/fixtures/panelModel.fixture.ts b/public/test/fixtures/panelModel.fixture.ts new file mode 100644 index 00000000000..40bbdb2e813 --- /dev/null +++ b/public/test/fixtures/panelModel.fixture.ts @@ -0,0 +1,21 @@ +import { Chance } from 'chance'; + +import { PanelModel } from '@grafana/data'; + +export function wellFormedPanelModel(panelOptions: T, seed = 1): PanelModel { + 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: [], + }; +}