From c332bf885c244cf4de6fa3e5ff8954fdea036dae Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Fri, 26 Aug 2022 09:42:46 +0200 Subject: [PATCH 01/17] Migrate to UID: Stop using search result ID (#54099) * DashList: Stop using IDs * DashLinks: Stop using IDs * BackendSrv: Do not use ID for search endpoint * DashboardDataDTO: Remove ID * Remove unused properties from DashboardSearchItem --- .../components/FolderFilter/FolderFilter.tsx | 5 ++- .../OptionsUI/DashboardPickerByID.tsx | 7 ++- .../components/Select/DashboardPicker.tsx | 6 +-- .../Select/ReadonlyFolderPicker/api.test.ts | 4 +- .../SharedPreferences/SharedPreferences.tsx | 7 +-- public/app/core/services/backend_srv.ts | 4 +- public/app/core/services/search_srv.ts | 3 +- public/app/core/specs/search_srv.test.ts | 12 ++--- .../components/SubMenu/DashboardLinks.tsx | 2 +- .../SubMenu/DashboardLinksDashboard.test.tsx | 44 +++++++++++++------ .../SubMenu/DashboardLinksDashboard.tsx | 28 ++++++------ .../explore/AddToDashboard/index.test.tsx | 3 -- public/app/features/search/types.ts | 12 +++-- .../app/plugins/panel/dashlist/DashList.tsx | 15 ++++--- public/app/types/dashboard.ts | 3 -- 15 files changed, 90 insertions(+), 65 deletions(-) diff --git a/public/app/core/components/FolderFilter/FolderFilter.tsx b/public/app/core/components/FolderFilter/FolderFilter.tsx index c346fbda1ae..698311084eb 100644 --- a/public/app/core/components/FolderFilter/FolderFilter.tsx +++ b/public/app/core/components/FolderFilter/FolderFilter.tsx @@ -5,6 +5,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { AsyncMultiSelect, Icon, Button, useStyles2 } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { DashboardSearchHit } from 'app/features/search/types'; import { FolderInfo, PermissionLevelString } from 'app/types'; export interface FolderFilterProps { @@ -75,7 +76,9 @@ async function getFoldersAsOptions(searchString: string, setLoading: (loading: b permission: PermissionLevelString.View, }; - const searchHits = await getBackendSrv().search(params); + // FIXME: stop using id from search and use UID instead + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const searchHits = (await getBackendSrv().search(params)) as DashboardSearchHit[]; const options = searchHits.map((d) => ({ label: d.title, value: { id: d.id, title: d.title } })); if (!searchString || 'general'.includes(searchString.toLowerCase())) { options.unshift({ label: 'General', value: { id: 0, title: 'General' } }); diff --git a/public/app/core/components/OptionsUI/DashboardPickerByID.tsx b/public/app/core/components/OptionsUI/DashboardPickerByID.tsx index 85d90167699..9d3c942b8d4 100644 --- a/public/app/core/components/OptionsUI/DashboardPickerByID.tsx +++ b/public/app/core/components/OptionsUI/DashboardPickerByID.tsx @@ -4,6 +4,7 @@ import React, { FC } from 'react'; import { SelectableValue } from '@grafana/data'; import { AsyncSelect } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; +import { DashboardSearchHit } from 'app/features/search/types'; /** * @deprecated prefer using dashboard uid rather than id @@ -70,10 +71,12 @@ async function getDashboards( label: string, excludedDashboards?: string[] ): Promise>> { - const result = await backendSrv.search({ type: 'dash-db', query, limit: 100 }); + // FIXME: stop using id from search and use UID instead + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const result = (await backendSrv.search({ type: 'dash-db', query, limit: 100 })) as DashboardSearchHit[]; const dashboards = result.map(({ id, uid = '', title, folderTitle }) => { const value: DashboardPickerItem = { - id, + id: id!, uid, [label]: `${folderTitle ?? 'General'}/${title}`, }; diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index c46980f63e9..4426cc4a99e 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { AsyncSelectProps, AsyncSelect } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; -import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSearchItem } from 'app/features/search/types'; import { DashboardDTO } from 'app/types'; interface Props extends Omit, 'value' | 'onChange' | 'loadOptions' | ''> { @@ -18,8 +18,8 @@ export type DashboardPickerDTO = Pick `${folderTitle}/${dashboardTitle}`; const getDashboards = debounce((query = ''): Promise>> => { - return backendSrv.search({ type: 'dash-db', query, limit: 100 }).then((result: DashboardSearchHit[]) => { - return result.map((item: DashboardSearchHit) => ({ + return backendSrv.search({ type: 'dash-db', query, limit: 100 }).then((result: DashboardSearchItem[]) => { + return result.map((item: DashboardSearchItem) => ({ value: { // dashboards uid here is always defined as this endpoint does not return the default home dashboard uid: item.uid!, diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts b/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts index 46ab7d3adb8..d0cab2a2861 100644 --- a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts +++ b/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts @@ -1,13 +1,13 @@ import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as api from '../../../../features/manage-dashboards/state/actions'; -import { DashboardSearchHit } from '../../../../features/search/types'; +import { DashboardSearchItem } from '../../../../features/search/types'; import { PermissionLevelString } from '../../../../types'; import { ALL_FOLDER, GENERAL_FOLDER } from './ReadonlyFolderPicker'; import { getFolderAsOption, getFoldersAsOptions } from './api'; function getTestContext( - searchHits: DashboardSearchHit[] = [], + searchHits: DashboardSearchItem[] = [], folderById: { id: number; title: string } = { id: 1, title: 'Folder 1' } ) { jest.clearAllMocks(); diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index f850ca9a7eb..d3ed914bbd1 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -32,8 +32,10 @@ export interface Props { disabled?: boolean; } +type DefaultDashboardSearchItem = Omit & { uid?: string }; + export type State = UserPreferencesDTO & { - dashboards: DashboardSearchItem[]; + dashboards: DashboardSearchItem[] | DefaultDashboardSearchItem[]; }; const themes: SelectableValue[] = [ @@ -75,14 +77,13 @@ const languages: Array> = [ const i18nFlag = Boolean(config.featureToggles.internationalization); -const DEFAULT_DASHBOARD_HOME: DashboardSearchItem = { +const DEFAULT_DASHBOARD_HOME: DefaultDashboardSearchItem = { title: 'Default', tags: [], type: '' as DashboardSearchItemType, uid: undefined, uri: '', url: '', - folderId: 0, folderTitle: '', folderUid: '', folderUrl: '', diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 061bb83ebb7..99cf0a1da70 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -18,7 +18,7 @@ import { BackendSrv as BackendService, BackendSrvRequest, config, FetchError, Fe import appEvents from 'app/core/app_events'; import { getConfig } from 'app/core/config'; import { loadUrlToken } from 'app/core/utils/urlToken'; -import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSearchItem } from 'app/features/search/types'; import { getGrafanaStorage } from 'app/features/storage/storage'; import { TokenRevokedModal } from 'app/features/users/TokenRevokedModal'; import { DashboardDTO, FolderDTO } from 'app/types'; @@ -439,7 +439,7 @@ export class BackendSrv implements BackendService { } /** @deprecated */ - search(query: any): Promise { + search(query: any): Promise { return this.get('/api/search', query); } diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index 14d0d4460cb..3b4b7251b39 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -113,7 +113,8 @@ export class SearchSrv { // create folder index for (const hit of results) { if (hit.type === 'dash-folder') { - sections[hit.id] = { + // FIXME: Use hit.uid instead + sections[hit.id!] = { id: hit.id, uid: hit.uid, title: hit.title, diff --git a/public/app/core/specs/search_srv.test.ts b/public/app/core/specs/search_srv.test.ts index b9ceda26d5e..e4a516d8a12 100644 --- a/public/app/core/specs/search_srv.test.ts +++ b/public/app/core/specs/search_srv.test.ts @@ -1,7 +1,7 @@ import { contextSrv } from 'app/core/services/context_srv'; import impressionSrv from 'app/core/services/impression_srv'; import { SearchSrv } from 'app/core/services/search_srv'; -import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSearchItem } from 'app/features/search/types'; import { backendSrv } from '../services/backend_srv'; @@ -40,7 +40,7 @@ describe('SearchSrv', () => { return Promise.resolve([ { uid: 'DSNdW0gVk', title: 'second but first' }, { uid: 'srx16xR4z', title: 'first but second' }, - ] as DashboardSearchHit[]); + ] as DashboardSearchItem[]); } return Promise.resolve([]); }); @@ -70,7 +70,7 @@ describe('SearchSrv', () => { return Promise.resolve([ { uid: 'DSNdW0gVk', title: 'two' }, { uid: 'srx16xR4z', title: 'one' }, - ] as DashboardSearchHit[]); + ] as DashboardSearchItem[]); } return Promise.resolve([]); }); @@ -98,7 +98,7 @@ describe('SearchSrv', () => { beforeEach(() => { searchMock.mockImplementation((options) => { if (options.starred) { - return Promise.resolve([{ id: 1, title: 'starred' }] as DashboardSearchHit[]); + return Promise.resolve([{ uid: '1', title: 'starred' }] as DashboardSearchItem[]); } return Promise.resolve([]); }); @@ -123,9 +123,9 @@ describe('SearchSrv', () => { return Promise.resolve([ { uid: 'srx16xR4z', title: 'starred and recent', isStarred: true }, { uid: 'DSNdW0gVk', title: 'recent' }, - ] as DashboardSearchHit[]); + ] as DashboardSearchItem[]); } - return Promise.resolve([{ uid: 'srx16xR4z', title: 'starred and recent' }] as DashboardSearchHit[]); + return Promise.resolve([{ uid: 'srx16xR4z', title: 'starred and recent' }] as DashboardSearchItem[]); }); impressionSrv.getDashboardOpened = jest.fn().mockResolvedValue(['srx16xR4z', 'DSNdW0gVk']); diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx index b86b66651c8..2e3186aeb37 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx @@ -37,7 +37,7 @@ export const DashboardLinks: FC = ({ dashboard, links }) => { const key = `${link.title}-$${index}`; if (link.type === 'dashboards') { - return ; + return ; } const linkElement = ( diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx index 84fe77ebe90..8f2263dc68a 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx @@ -1,4 +1,4 @@ -import { DashboardSearchHit, DashboardSearchItemType } from '../../../search/types'; +import { DashboardSearchItem, DashboardSearchItemType } from '../../../search/types'; import { DashboardLink } from '../../state/DashboardModel'; import { resolveLinks, searchForTags } from './DashboardLinksDashboard'; @@ -39,7 +39,7 @@ describe('searchForTags', () => { }); describe('resolveLinks', () => { - const setupTestContext = (dashboardId: number, searchHitId: number) => { + const setupTestContext = (dashboardUID: string, searchHitId: string) => { const link: DashboardLink = { targetBlank: false, keepTime: false, @@ -52,9 +52,9 @@ describe('resolveLinks', () => { type: 'dashboards', url: '/d/6ieouugGk/DashLinks', }; - const searchHits: DashboardSearchHit[] = [ + const searchHits: DashboardSearchItem[] = [ { - id: searchHitId, + uid: searchHitId, title: 'DashLinks', url: '/d/6ieouugGk/DashLinks', isStarred: false, @@ -70,14 +70,18 @@ describe('resolveLinks', () => { const sanitize = jest.fn((args) => args); const sanitizeUrl = jest.fn((args) => args); - return { dashboardId, link, searchHits, linkSrv, sanitize, sanitizeUrl }; + return { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl }; }; describe('when called', () => { - it('should filter out the calling dashboardId', () => { - const { dashboardId, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext(1, 1); + it('should filter out the calling dashboardUID', () => { + const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '1'); - const results = resolveLinks(dashboardId, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl }); + const results = resolveLinks(dashboardUID, link, searchHits, { + getLinkSrv: () => linkSrv, + sanitize, + sanitizeUrl, + }); expect(results.length).toEqual(0); expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(0); @@ -86,9 +90,13 @@ describe('resolveLinks', () => { }); it('should resolve link url', () => { - const { dashboardId, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext(1, 2); + const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); - const results = resolveLinks(dashboardId, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl }); + const results = resolveLinks(dashboardUID, link, searchHits, { + getLinkSrv: () => linkSrv, + sanitize, + sanitizeUrl, + }); expect(results.length).toEqual(1); expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(1); @@ -96,9 +104,13 @@ describe('resolveLinks', () => { }); it('should sanitize title', () => { - const { dashboardId, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext(1, 2); + const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); - const results = resolveLinks(dashboardId, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl }); + const results = resolveLinks(dashboardUID, link, searchHits, { + getLinkSrv: () => linkSrv, + sanitize, + sanitizeUrl, + }); expect(results.length).toEqual(1); expect(sanitize).toHaveBeenCalledTimes(1); @@ -106,9 +118,13 @@ describe('resolveLinks', () => { }); it('should sanitize url', () => { - const { dashboardId, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext(1, 2); + const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); - const results = resolveLinks(dashboardId, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl }); + const results = resolveLinks(dashboardUID, link, searchHits, { + getLinkSrv: () => linkSrv, + sanitize, + sanitizeUrl, + }); expect(results.length).toEqual(1); expect(sanitizeUrl).toHaveBeenCalledTimes(1); diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index 66bd546dc76..de85c07b995 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -7,7 +7,7 @@ import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize'; import { selectors } from '@grafana/e2e-selectors'; import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSearchItem } from 'app/features/search/types'; import { getLinkSrv } from '../../../panel/panellinks/link_srv'; import { DashboardLink } from '../../state/DashboardModel'; @@ -15,7 +15,7 @@ import { DashboardLink } from '../../state/DashboardModel'; interface Props { link: DashboardLink; linkInfo: { title: string; href: string }; - dashboardId: number; + dashboardUID: string; } export const DashboardLinksDashboard = (props: Props) => { @@ -55,7 +55,7 @@ export const DashboardLinksDashboard = (props: Props) => { {resolvedLinks.length > 0 && resolvedLinks.map((resolvedLink, index) => { return ( -
  • +
  • { return ( = (props) => { ); }; -const useResolvedLinks = ({ link, dashboardId }: Props, opened: number): ResolvedLinkDTO[] => { +const useResolvedLinks = ({ link, dashboardUID }: Props, opened: number): ResolvedLinkDTO[] => { const { tags } = link; const result = useAsync(() => searchForTags(tags), [tags, opened]); if (!result.value) { return []; } - return resolveLinks(dashboardId, link, result.value); + return resolveLinks(dashboardUID, link, result.value); }; interface ResolvedLinkDTO { - id: number; + uid: string; url: string; title: string; } @@ -138,17 +138,17 @@ interface ResolvedLinkDTO { export async function searchForTags( tags: string[], dependencies: { getBackendSrv: typeof getBackendSrv } = { getBackendSrv } -): Promise { +): Promise { const limit = 100; - const searchHits: DashboardSearchHit[] = await dependencies.getBackendSrv().search({ tag: tags, limit }); + const searchHits: DashboardSearchItem[] = await dependencies.getBackendSrv().search({ tag: tags, limit }); return searchHits; } export function resolveLinks( - dashboardId: number, + dashboardUID: string, link: DashboardLink, - searchHits: DashboardSearchHit[], + searchHits: DashboardSearchItem[], dependencies: { getLinkSrv: typeof getLinkSrv; sanitize: typeof sanitize; sanitizeUrl: typeof sanitizeUrl } = { getLinkSrv, sanitize, @@ -156,14 +156,14 @@ export function resolveLinks( } ): ResolvedLinkDTO[] { return searchHits - .filter((searchHit) => searchHit.id !== dashboardId) + .filter((searchHit) => searchHit.uid !== dashboardUID) .map((searchHit) => { - const id = searchHit.id; + const uid = searchHit.uid; const title = dependencies.sanitize(searchHit.title); const resolvedLink = dependencies.getLinkSrv().getLinkUrl({ ...link, url: searchHit.url }); const url = dependencies.sanitizeUrl(resolvedLink); - return { id, title, url }; + return { uid, title, url }; }); } diff --git a/public/app/features/explore/AddToDashboard/index.test.tsx b/public/app/features/explore/AddToDashboard/index.test.tsx index e11ca1eae9a..424950aea74 100644 --- a/public/app/features/explore/AddToDashboard/index.test.tsx +++ b/public/app/features/explore/AddToDashboard/index.test.tsx @@ -199,7 +199,6 @@ describe('AddToDashboardButton', () => { }); jest.spyOn(backendSrv, 'search').mockResolvedValue([ { - id: 1, uid: 'someUid', isStarred: false, items: [], @@ -242,7 +241,6 @@ describe('AddToDashboardButton', () => { }); jest.spyOn(backendSrv, 'search').mockResolvedValue([ { - id: 1, uid: 'someUid', isStarred: false, items: [], @@ -359,7 +357,6 @@ describe('AddToDashboardButton', () => { jest.spyOn(backendSrv, 'getDashboardByUid').mockRejectedValue('SOME ERROR'); jest.spyOn(backendSrv, 'search').mockResolvedValue([ { - id: 1, uid: 'someUid', isStarred: false, items: [], diff --git a/public/app/features/search/types.ts b/public/app/features/search/types.ts index a54d55c2da3..2fbba252ad7 100644 --- a/public/app/features/search/types.ts +++ b/public/app/features/search/types.ts @@ -12,7 +12,7 @@ export enum DashboardSearchItemType { * @deprecated */ export interface DashboardSection { - id: number; + id?: number; uid?: string; title: string; expanded?: boolean; @@ -37,7 +37,7 @@ export interface DashboardSectionItem { folderTitle?: string; folderUid?: string; folderUrl?: string; - id: number; + id?: number; isStarred: boolean; selected?: boolean; tags: string[]; @@ -56,7 +56,13 @@ export interface DashboardSectionItem { */ export interface DashboardSearchHit extends DashboardSectionItem, DashboardSection, WithAccessControlMetadata {} -export interface DashboardSearchItem extends Omit {} +export interface DashboardSearchItem + extends Omit< + DashboardSearchHit, + 'id' | 'uid' | 'expanded' | 'selected' | 'checked' | 'folderId' | 'icon' | 'sortMeta' | 'sortMetaName' + > { + uid: string; +} export interface SearchAction extends Action { payload?: any; diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index fb51ca69a18..09b741bcd1b 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -12,12 +12,12 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { SearchCard } from 'app/features/search/components/SearchCard'; -import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSearchItem } from 'app/features/search/types'; import { PanelLayout, PanelOptions } from './models.gen'; import { getStyles } from './styles'; -type Dashboard = DashboardSearchHit & { isSearchResult?: boolean; isRecent?: boolean }; +type Dashboard = DashboardSearchItem & { id?: number; isSearchResult?: boolean; isRecent?: boolean }; interface DashboardGroup { show: boolean; @@ -26,13 +26,13 @@ interface DashboardGroup { } async function fetchDashboards(options: PanelOptions, replaceVars: InterpolateFunction) { - let starredDashboards: Promise = Promise.resolve([]); + let starredDashboards: Promise = Promise.resolve([]); if (options.showStarred) { const params = { limit: options.maxItems, starred: 'true' }; starredDashboards = getBackendSrv().search(params); } - let recentDashboards: Promise = Promise.resolve([]); + let recentDashboards: Promise = Promise.resolve([]); let dashUIDs: string[] = []; if (options.showRecentlyViewed) { let uids = await impressionSrv.getDashboardOpened(); @@ -40,7 +40,7 @@ async function fetchDashboards(options: PanelOptions, replaceVars: InterpolateFu recentDashboards = getBackendSrv().search({ dashboardUIDs: dashUIDs, limit: options.maxItems }); } - let searchedDashboards: Promise = Promise.resolve([]); + let searchedDashboards: Promise = Promise.resolve([]); if (options.showSearch) { const params = { limit: options.maxItems, @@ -103,7 +103,8 @@ export function DashList(props: PanelProps) { e.preventDefault(); e.stopPropagation(); - const isStarred = await getDashboardSrv().starDashboard(dash.id.toString(), dash.isStarred); + // FIXME: Do not use dash ID. Use UID to star a dashboard once the backend allows it + const isStarred = await getDashboardSrv().starDashboard(dash.id!.toString(), dash.isStarred); const updatedDashboards = new Map(dashboards); updatedDashboards.set(dash?.uid ?? '', { ...dash, isStarred }); setDashboards(updatedDashboards); @@ -144,7 +145,7 @@ export function DashList(props: PanelProps) { const renderList = (dashboards: Dashboard[]) => (