diff --git a/.betterer.results b/.betterer.results index 9b47eddaeb4..b09b00781de 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3227,12 +3227,11 @@ exports[`better eslint`] = { ], "public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Do not use any type assertions.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], "public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -3266,8 +3265,7 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"] + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -4622,8 +4620,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/search/page/components/MoveToFolderModal.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/search/page/components/SearchResultsCards.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/public/app/core/components/Select/FolderPicker.test.tsx b/public/app/core/components/Select/FolderPicker.test.tsx index 6fa19a5a935..3c6b18fc135 100644 --- a/public/app/core/components/Select/FolderPicker.test.tsx +++ b/public/app/core/components/Select/FolderPicker.test.tsx @@ -16,8 +16,8 @@ describe('FolderPicker', () => { jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, ]); render(); @@ -28,12 +28,12 @@ describe('FolderPicker', () => { jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, - { title: 'Dash 3', id: 3 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, + { title: 'Dash 3', uid: '7MeksYbmk' } as DashboardSearchHit, ]); - render( hits.filter((h) => h.id !== 2)} />); + render( hits.filter((h) => h.uid !== 'wfTJJL5Wz')} />); const pickerContainer = screen.getByLabelText(selectors.components.FolderPicker.input); selectEvent.openMenu(pickerContainer); @@ -46,13 +46,13 @@ describe('FolderPicker', () => { }); it('should allow creating a new option', async () => { - const newFolder = { title: 'New Folder', id: 3 } as DashboardSearchHit; + const newFolder = { title: 'New Folder', uid: '7MeksYbmk' } as DashboardSearchHit; jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, ]); const onChangeFn = jest.fn(); @@ -70,7 +70,7 @@ describe('FolderPicker', () => { expect(create).toHaveBeenCalledWith({ title: newFolder.title }); }); - expect(onChangeFn).toHaveBeenCalledWith({ title: newFolder.title, id: newFolder.id }); + expect(onChangeFn).toHaveBeenCalledWith({ title: newFolder.title, uid: newFolder.uid }); await waitFor(() => { expect(screen.getByText(newFolder.title)).toBeInTheDocument(); }); @@ -80,8 +80,8 @@ describe('FolderPicker', () => { jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, ]); jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true); @@ -101,8 +101,8 @@ describe('FolderPicker', () => { jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, ]); jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true); @@ -122,8 +122,8 @@ describe('FolderPicker', () => { jest .spyOn(api, 'searchFolders') .mockResolvedValue([ - { title: 'Dash 1', id: 1 } as DashboardSearchHit, - { title: 'Dash 2', id: 2 } as DashboardSearchHit, + { title: 'Dash 1', uid: 'xMsQdBfWz' } as DashboardSearchHit, + { title: 'Dash 2', uid: 'wfTJJL5Wz' } as DashboardSearchHit, ]); jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false); @@ -141,28 +141,28 @@ describe('FolderPicker', () => { }); describe('getInitialValues', () => { - describe('when called with folderId and title', () => { - it('then it should return folderId and title', async () => { + describe('when called with folderUid and title', () => { + it('then it should return folderUid and title', async () => { const getFolder = jest.fn().mockResolvedValue({}); - const folder = await getInitialValues({ folderId: 0, folderName: 'Some title', getFolder }); + const folder = await getInitialValues({ folderUid: '', folderName: 'Some title', getFolder }); - expect(folder).toEqual({ label: 'Some title', value: 0 }); + expect(folder).toEqual({ label: 'Some title', value: '' }); expect(getFolder).not.toHaveBeenCalled(); }); }); - describe('when called with just a folderId', () => { + describe('when called with just a folderUid', () => { it('then it should call api to retrieve title', async () => { - const getFolder = jest.fn().mockResolvedValue({ id: 0, title: 'Title from api' }); - const folder = await getInitialValues({ folderId: 0, getFolder }); + const getFolder = jest.fn().mockResolvedValue({ uid: '', title: 'Title from api' }); + const folder = await getInitialValues({ folderUid: '', getFolder }); - expect(folder).toEqual({ label: 'Title from api', value: 0 }); + expect(folder).toEqual({ label: 'Title from api', value: '' }); expect(getFolder).toHaveBeenCalledTimes(1); - expect(getFolder).toHaveBeenCalledWith(0); + expect(getFolder).toHaveBeenCalledWith(''); }); }); - describe('when called without folderId', () => { + describe('when called without folderUid', () => { it('then it should throw an error', async () => { const getFolder = jest.fn().mockResolvedValue({}); await expect(getInitialValues({ getFolder })).rejects.toThrow(); diff --git a/public/app/core/components/Select/FolderPicker.tsx b/public/app/core/components/Select/FolderPicker.tsx index 9e7a8976314..8f2d2764b62 100644 --- a/public/app/core/components/Select/FolderPicker.tsx +++ b/public/app/core/components/Select/FolderPicker.tsx @@ -9,7 +9,7 @@ import { useStyles2, ActionMeta, AsyncSelect, Input, InputActionMeta } from '@gr import appEvents from 'app/core/app_events'; import { t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; -import { createFolder, getFolderById, searchFolders } from 'app/features/manage-dashboards/state/actions'; +import { createFolder, getFolderByUid, searchFolders } from 'app/features/manage-dashboards/state/actions'; import { DashboardSearchHit } from 'app/features/search/types'; import { AccessControlAction, PermissionLevelString } from 'app/types'; @@ -28,13 +28,13 @@ export interface CustomAdd { } export interface Props { - onChange: ($folder: { title: string; id: number }) => void; + onChange: ($folder: { title: string; uid: string }) => void; enableCreateNew?: boolean; rootName?: string; enableReset?: boolean; dashboardId?: number | string; initialTitle?: string; - initialFolderId?: number; + initialFolderUid?: string; permissionLevel?: Exclude; filter?: FolderPickerFilter; allowEmpty?: boolean; @@ -47,15 +47,15 @@ export interface Props { /** * Skips loading all folders in order to find the folder matching * the folder where the dashboard is stored. - * Instead initialFolderId and initialTitle will be used to display the correct folder. - * initialFolderId needs to have an value > -1 or an error will be thrown. + * Instead initialFolderUid and initialTitle will be used to display the correct folder. + * initialFolderUid needs to be a string or an error will be thrown. */ skipInitialLoad?: boolean; /** The id of the search input. Use this to set a matching label with htmlFor */ inputId?: string; } -export type SelectedFolder = SelectableValue; -const VALUE_FOR_ADD = -10; +export type SelectedFolder = SelectableValue; +const VALUE_FOR_ADD = '-10'; export function FolderPicker(props: Props) { const { @@ -67,7 +67,7 @@ export function FolderPicker(props: Props) { inputId, onClear, enableReset, - initialFolderId, + initialFolderUid, initialTitle = '', permissionLevel = PermissionLevelString.Edit, rootName = 'General', @@ -90,14 +90,14 @@ export function FolderPicker(props: Props) { const getOptions = useCallback( async (query: string) => { const searchHits = await searchFolders(query, permissionLevel, accessControlMetadata); - const options: Array> = mapSearchHitsToOptions(searchHits, filter); + const options: Array> = mapSearchHitsToOptions(searchHits, filter); const hasAccess = contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor) || contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor); if (hasAccess && rootName?.toLowerCase().startsWith(query.toLowerCase()) && showRoot) { - options.unshift({ label: rootName, value: 0 }); + options.unshift({ label: rootName, value: '' }); } if ( @@ -106,7 +106,7 @@ export function FolderPicker(props: Props) { initialTitle !== '' && !options.find((option) => option.label === initialTitle) ) { - options.unshift({ label: initialTitle, value: initialFolderId }); + options.unshift({ label: initialTitle, value: initialFolderUid }); } if (enableCreateNew && Boolean(customAdd)) { return [...options, { value: VALUE_FOR_ADD, label: ADD_NEW_FOLER_OPTION, title: query }]; @@ -116,7 +116,7 @@ export function FolderPicker(props: Props) { }, [ enableReset, - initialFolderId, + initialFolderUid, initialTitle, permissionLevel, rootName, @@ -133,19 +133,19 @@ export function FolderPicker(props: Props) { }, [getOptions]); const loadInitialValue = async () => { - const resetFolder: SelectableValue = { label: initialTitle, value: undefined }; - const rootFolder: SelectableValue = { label: rootName, value: 0 }; + const resetFolder: SelectableValue = { label: initialTitle, value: undefined }; + const rootFolder: SelectableValue = { label: rootName, value: '' }; const options = await getOptions(''); - let folder: SelectableValue | null = null; + let folder: SelectableValue | null = null; - if (initialFolderId !== undefined && initialFolderId !== null && initialFolderId > -1) { - folder = options.find((option) => option.value === initialFolderId) || null; + if (initialFolderUid !== undefined && initialFolderUid !== null) { + folder = options.find((option) => option.value === initialFolderUid) || null; } else if (enableReset && initialTitle) { folder = resetFolder; - } else if (initialFolderId) { - folder = options.find((option) => option.id === initialFolderId) || null; + } else if (initialFolderUid) { + folder = options.find((option) => option.id === initialFolderUid) || null; } if (!folder && !allowEmpty) { @@ -166,25 +166,25 @@ export function FolderPicker(props: Props) { useEffect(() => { // if this is not the same as our initial value notify parent - if (folder && folder.value !== initialFolderId) { - !isCreatingNew && folder.value && folder.label && onChange({ id: folder.value, title: folder.label }); + if (folder && folder.value !== initialFolderUid) { + !isCreatingNew && folder.value && folder.label && onChange({ uid: folder.value, title: folder.label }); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [folder, initialFolderId]); + }, [folder, initialFolderUid]); // initial values for dropdown useAsync(async () => { if (skipInitialLoad) { const folder = await getInitialValues({ - getFolder: getFolderById, - folderId: initialFolderId, + getFolder: getFolderByUid, + folderUid: initialFolderUid, folderName: initialTitle, }); setFolder(folder); } await loadInitialValue(); - }, [skipInitialLoad, initialFolderId, initialTitle]); + }, [skipInitialLoad, initialFolderUid, initialTitle]); useEffect(() => { if (folder && folder.id === VALUE_FOR_ADD) { @@ -193,7 +193,7 @@ export function FolderPicker(props: Props) { }, [folder]); const onFolderChange = useCallback( - (newFolder: SelectableValue | null | undefined, actionMeta: ActionMeta) => { + (newFolder: SelectableValue | null | undefined, actionMeta: ActionMeta) => { if (newFolder?.value === VALUE_FOR_ADD) { setFolder({ id: VALUE_FOR_ADD, @@ -202,7 +202,7 @@ export function FolderPicker(props: Props) { setNewFolderValue(inputValue); } else { if (!newFolder) { - newFolder = { value: 0, label: rootName }; + newFolder = { value: '', label: rootName }; } if (actionMeta.action === 'clear' && onClear) { @@ -211,7 +211,7 @@ export function FolderPicker(props: Props) { } setFolder(newFolder); - onChange({ id: newFolder.value!, title: newFolder.label! }); + onChange({ uid: newFolder.value!, title: newFolder.label! }); } }, [onChange, onClear, rootName, inputValue] @@ -223,11 +223,11 @@ export function FolderPicker(props: Props) { return false; } const newFolder = await createFolder({ title: folderName }); - let folder: SelectableValue = { value: -1, label: 'Not created' }; + let folder: SelectableValue = { value: '', label: 'Not created' }; - if (newFolder.id > -1) { + if (newFolder.uid) { appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']); - folder = { value: newFolder.id, label: newFolder.title }; + folder = { value: newFolder.uid, label: newFolder.title }; setFolder(newFolder); onFolderChange(folder, { action: 'create-option', option: folder }); @@ -255,7 +255,7 @@ export function FolderPicker(props: Props) { break; } case 'Escape': { - setFolder({ value: 0, label: rootName }); + setFolder({ value: '', label: rootName }); setIsCreatingNew(false); } } @@ -266,11 +266,11 @@ export function FolderPicker(props: Props) { const onNewFolderChange = (e: FormEvent) => { const value = e.currentTarget.value; setNewFolderValue(value); - setFolder({ id: -1, title: value }); + setFolder({ id: undefined, title: value }); }; const onBlur = () => { - setFolder({ value: 0, label: rootName }); + setFolder({ value: '', label: rootName }); setIsCreatingNew(false); }; @@ -344,25 +344,25 @@ export function FolderPicker(props: Props) { function mapSearchHitsToOptions(hits: DashboardSearchHit[], filter?: FolderPickerFilter) { const filteredHits = filter ? filter(hits) : hits; - return filteredHits.map((hit) => ({ label: hit.title, value: hit.id })); + return filteredHits.map((hit) => ({ label: hit.title, value: hit.uid })); } interface Args { - getFolder: typeof getFolderById; - folderId?: number; + getFolder: typeof getFolderByUid; + folderUid?: string; folderName?: string; } -export async function getInitialValues({ folderName, folderId, getFolder }: Args): Promise> { - if (folderId === null || folderId === undefined || folderId < 0) { - throw new Error('folderId should to be greater or equal to zero.'); +export async function getInitialValues({ folderName, folderUid, getFolder }: Args): Promise> { + if (folderUid === null || folderUid === undefined) { + throw new Error('folderUid is not found.'); } if (folderName) { - return { label: folderName, value: folderId }; + return { label: folderName, value: folderUid }; } - const folderDto = await getFolder(folderId); - return { label: folderDto.title, value: folderId }; + const folderDto = await getFolder(folderUid); + return { label: folderDto.title, value: folderUid }; } const getStyles = (theme: GrafanaTheme2) => ({ diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx index db6f02bd74c..f1e973a5d81 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx @@ -11,7 +11,7 @@ import { FolderWarning, CustomAdd } from '../../../../../core/components/Select/ export interface Folder { title: string; - id: number; + uid: string; } export interface RuleFolderPickerProps extends Omit { @@ -53,7 +53,7 @@ export function RuleFolderPicker(props: RuleFolderPickerProps) { showRoot={false} allowEmpty={true} initialTitle={value?.title} - initialFolderId={value?.id} + initialFolderUid={value?.uid} accessControlMetadata {...props} permissionLevel={PermissionLevelString.View} diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx index ab14b47bc27..0ae88841c4e 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx @@ -76,7 +76,7 @@ export const DashboardPrompt = React.memo(({ dashboard }: Props) => { showModal(SaveLibraryPanelModal, { isUnsavedPrompt: true, panel: dashboard.panelInEdit as PanelModelWithLibraryPanel, - folderId: dashboard.meta.folderId as number, + folderUid: dashboard.meta.folderUid ?? '', onConfirm: () => { hideModal(); moveToBlockedLocationAfterReactStateUpdate(location); diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index 789b60b2ffc..c84d5c8a6f4 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -30,8 +30,8 @@ export function GeneralSettingsUnconnected({ }: Props): JSX.Element { const [renderCounter, setRenderCounter] = useState(0); - const onFolderChange = (folder: { id: number; title: string }) => { - dashboard.meta.folderId = folder.id; + const onFolderChange = (folder: { uid: string; title: string }) => { + dashboard.meta.folderUid = folder.uid; dashboard.meta.folderTitle = folder.title; dashboard.meta.hasUnsavedFolderChange = true; }; @@ -109,7 +109,7 @@ export function GeneralSettingsUnconnected({ { {this.state.showSaveLibraryPanelModal && ( = ({ const defaultValues: SaveDashboardAsFormDTO = { title: isNew ? dashboard.title : `${dashboard.title} Copy`, $folder: { - id: dashboard.meta.folderId, + uid: dashboard.meta.folderUid, title: dashboard.meta.folderTitle, }, copyTags: false, @@ -60,7 +60,7 @@ export const SaveDashboardAsForm: React.FC = ({ return 'Dashboard name cannot be the same as folder name'; } try { - await validationSrv.validateNewDashboardName(getFormValues().$folder.id, dashboardName); + await validationSrv.validateNewDashboardName(getFormValues().$folder.uid, dashboardName); return true; } catch (e) { return e instanceof Error ? e.message : 'Dashboard name is invalid'; @@ -84,7 +84,7 @@ export const SaveDashboardAsForm: React.FC = ({ const result = await onSubmit( clone, { - folderId: data.$folder.id, + folderUid: data.$folder.uid, }, dashboard ); @@ -111,7 +111,7 @@ export const SaveDashboardAsForm: React.FC = ({ diff --git a/public/app/features/dashboard/components/SaveDashboard/types.ts b/public/app/features/dashboard/components/SaveDashboard/types.ts index 0a5646aa095..084ff68b517 100644 --- a/public/app/features/dashboard/components/SaveDashboard/types.ts +++ b/public/app/features/dashboard/components/SaveDashboard/types.ts @@ -11,7 +11,7 @@ export interface SaveDashboardData { } export interface SaveDashboardOptions extends CloneOptions { - folderId?: number; + folderUid?: string; overwrite?: boolean; message?: string; makeEditable?: boolean; @@ -20,7 +20,7 @@ export interface SaveDashboardOptions extends CloneOptions { export interface SaveDashboardCommand { dashboard: DashboardDataDTO; message?: string; - folderId?: number; + folderUid?: string; overwrite?: boolean; } diff --git a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx index 22de9223b8e..42306bcd8c8 100644 --- a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx @@ -15,12 +15,12 @@ import { DashboardSavedEvent } from 'app/types/events'; import { SaveDashboardOptions } from './types'; const saveDashboard = async (saveModel: any, options: SaveDashboardOptions, dashboard: DashboardModel) => { - let folderId = options.folderId; - if (folderId === undefined) { - folderId = dashboard.meta.folderId ?? saveModel.folderId; + let folderUid = options.folderUid; + if (folderUid === undefined) { + folderUid = dashboard.meta.folderUid ?? saveModel.folderUid; } - const result = await saveDashboardApiCall({ ...options, folderId, dashboard: saveModel }); + const result = await saveDashboardApiCall({ ...options, folderUid, dashboard: saveModel }); // fetch updated access control permissions await contextSrv.fetchUserPermissions(); return result; diff --git a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx index 28f98cec05a..e3b65aab155 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx @@ -7,10 +7,10 @@ import { AddLibraryPanelContents } from 'app/features/library-panels/components/ import { ShareModalTabProps } from './types'; interface Props extends ShareModalTabProps { - initialFolderId?: number; + initialFolderUid?: string; } -export const ShareLibraryPanel = ({ panel, initialFolderId, onDismiss }: Props) => { +export const ShareLibraryPanel = ({ panel, initialFolderUid, onDismiss }: Props) => { useEffect(() => { reportInteraction('grafana_dashboards_library_panel_share_viewed'); }, []); @@ -24,7 +24,7 @@ export const ShareLibraryPanel = ({ panel, initialFolderId, onDismiss }: Props)

Create library panel.

- + ); }; diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 40c6b6100d8..1783d7d34bd 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -45,7 +45,7 @@ export interface DashboardPageRouteParams { export type DashboardPageRouteSearchParams = { tab?: string; - folderId?: string; + folderUid?: string; editPanel?: string; viewPanel?: string; editview?: string; @@ -139,7 +139,7 @@ export class UnthemedDashboardPage extends PureComponent { urlSlug: match.params.slug, urlUid: match.params.uid, urlType: match.params.type, - urlFolderId: queryParams.folderId, + urlFolderUid: queryParams.folderUid, panelType: queryParams.panelType, routeName: this.props.route.routeName, fixUrl: !isPublic, diff --git a/public/app/features/dashboard/services/DashboardSrv.ts b/public/app/features/dashboard/services/DashboardSrv.ts index 0b4eb8d0f07..11e32a26eeb 100644 --- a/public/app/features/dashboard/services/DashboardSrv.ts +++ b/public/app/features/dashboard/services/DashboardSrv.ts @@ -69,7 +69,7 @@ export class DashboardSrv { const parsedJson = JSON.parse(json); return saveDashboard({ dashboard: parsedJson, - folderId: this.dashboard?.meta.folderId || parsedJson.folderId, + folderUid: this.dashboard?.meta.folderUid || parsedJson.folderUid, }); } diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index dc6d007d023..705dd42141c 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -12,7 +12,15 @@ import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { toStateKey } from 'app/features/variables/utils'; -import { DashboardDTO, DashboardInitPhase, DashboardRoutes, StoreState, ThunkDispatch, ThunkResult } from 'app/types'; +import { + DashboardDTO, + DashboardInitPhase, + DashboardMeta, + DashboardRoutes, + StoreState, + ThunkDispatch, + ThunkResult, +} from 'app/types'; import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; import { initVariablesTransaction } from '../../variables/state/actions'; @@ -27,7 +35,7 @@ export interface InitDashboardArgs { urlUid?: string; urlSlug?: string; urlType?: string; - urlFolderId?: string; + urlFolderUid?: string; panelType?: string; accessToken?: string; routeName?: string; @@ -89,7 +97,7 @@ async function fetchDashboard( return dashDTO; } case DashboardRoutes.New: { - return getNewDashboardModelData(args.urlFolderId, args.panelType); + return getNewDashboardModelData(args.urlFolderUid, args.panelType); } case DashboardRoutes.Path: { const path = args.urlSlug ?? ''; @@ -255,14 +263,17 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { }; } -export function getNewDashboardModelData(urlFolderId?: string, panelType?: string): any { +export function getNewDashboardModelData( + urlFolderUid?: string, + panelType?: string +): { dashboard: any; meta: DashboardMeta } { const data = { meta: { canStar: false, canShare: false, canDelete: false, isNew: true, - folderId: 0, + folderUid: '', }, dashboard: { title: 'New dashboard', @@ -276,8 +287,8 @@ export function getNewDashboardModelData(urlFolderId?: string, panelType?: strin }, }; - if (urlFolderId) { - data.meta.folderId = parseInt(urlFolderId, 10); + if (urlFolderUid) { + data.meta.folderUid = urlFolderUid; } return data; diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index 6950dadc9d4..1a870ee6b03 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -75,7 +75,7 @@ export const addLibraryPanel = (dashboard: DashboardModel, panel: PanelModel) => component: AddLibraryPanelModal, props: { panel, - initialFolderId: dashboard.meta.folderId, + initialFolderUid: dashboard.meta.folderUid, isOpen: true, }, }) diff --git a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx index 36d64f498da..9f2f5253f2b 100644 --- a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx @@ -13,11 +13,11 @@ import { usePanelSave } from '../../utils/usePanelSave'; interface AddLibraryPanelContentsProps { onDismiss: () => void; panel: PanelModel; - initialFolderId?: number; + initialFolderUid?: string; } -export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: AddLibraryPanelContentsProps) => { - const [folderId, setFolderId] = useState(initialFolderId); +export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }: AddLibraryPanelContentsProps) => { + const [folderUid, setFolderUid] = useState(initialFolderUid); const [panelName, setPanelName] = useState(panel.title); const [debouncedPanelName, setDebouncedPanelName] = useState(panel.title); const [waiting, setWaiting] = useState(false); @@ -28,15 +28,15 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A const { saveLibraryPanel } = usePanelSave(); const onCreate = useCallback(() => { panel.libraryPanel = { uid: '', name: panelName }; - saveLibraryPanel(panel, folderId!).then((res) => { + saveLibraryPanel(panel, folderUid!).then((res) => { if (!(res instanceof Error)) { onDismiss(); } }); - }, [panel, panelName, folderId, onDismiss, saveLibraryPanel]); + }, [panel, panelName, folderUid, onDismiss, saveLibraryPanel]); const isValidName = useAsync(async () => { try { - return !(await getLibraryPanelByName(panelName)).some((lp) => lp.folderId === folderId); + return !(await getLibraryPanelByName(panelName)).some((lp) => lp.folderUid === folderUid); } catch (err) { if (isFetchError(err)) { err.isHandled = true; @@ -45,7 +45,7 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A } finally { setWaiting(false); } - }, [debouncedPanelName, folderId]); + }, [debouncedPanelName, folderUid]); const invalidInput = !isValidName?.value && isValidName.value !== undefined && panelName === debouncedPanelName && !waiting; @@ -72,8 +72,8 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A )} > setFolderId(id)} - initialFolderId={initialFolderId} + onChange={({ uid }) => setFolderUid(uid)} + initialFolderUid={initialFolderUid} inputId="share-panel-library-panel-folder-picker" /> @@ -94,10 +94,10 @@ interface Props extends AddLibraryPanelContentsProps { isOpen?: boolean; } -export const AddLibraryPanelModal = ({ isOpen = false, panel, initialFolderId, ...props }: Props) => { +export const AddLibraryPanelModal = ({ isOpen = false, panel, initialFolderUid, ...props }: Props) => { return ( - + ); }; diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index e3a84f0a57e..726957acb8c 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -28,9 +28,9 @@ jest.mock('debounce-promise', () => { const debounce = (fn: any) => { const debounced = () => Promise.resolve([ - { label: 'General', value: { id: 0, title: 'General' } }, - { label: 'Folder1', value: { id: 1, title: 'Folder1' } }, - { label: 'Folder2', value: { id: 2, title: 'Folder2' } }, + { label: 'General', value: { uid: '', title: 'General' } }, + { label: 'Folder1', value: { id: 'xMsQdBfWz', title: 'Folder1' } }, + { label: 'Folder2', value: { id: 'wfTJJL5Wz', title: 'Folder2' } }, ]); return debounced; }; @@ -187,7 +187,7 @@ describe('LibraryPanelsSearch', () => { kind: LibraryElementKind.Panel, uid: 'uid', description: 'Library Panel Description', - folderId: 0, + folderUid: '', model: { type: 'timeseries', title: 'A title' }, type: 'timeseries', orgId: 1, @@ -242,7 +242,7 @@ describe('LibraryPanelsSearch', () => { kind: LibraryElementKind.Panel, uid: 'uid', description: 'Library Panel Description', - folderId: 0, + folderUid: '', model: { type: 'timeseries', title: 'A title' }, type: 'timeseries', orgId: 1, @@ -286,7 +286,7 @@ describe('LibraryPanelsSearch', () => { kind: LibraryElementKind.Panel, uid: 'uid', description: 'Library Panel Description', - folderId: 0, + folderUid: '', model: { type: 'timeseries', title: 'A title' }, type: 'timeseries', orgId: 1, diff --git a/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts b/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts index be4cbad2922..527d345534b 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts +++ b/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts @@ -106,7 +106,7 @@ function mockLibraryPanel({ uid = '1', id = 1, orgId = 1, - folderId = 0, + folderUid = '', name = 'Test Panel', model = { type: 'text', title: 'Test Panel' }, meta = { @@ -126,7 +126,7 @@ function mockLibraryPanel({ uid, id, orgId, - folderId, + folderUid, name, kind: LibraryElementKind.Panel, model, diff --git a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx index 3fb0c327952..94d86bab9a7 100644 --- a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx +++ b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx @@ -70,7 +70,7 @@ export const PanelLibraryOptionsGroup: FC = ({ panel, searchQuery }) => { setShowingAddPanelModal(false)} - initialFolderId={dashboard?.meta.folderId} + initialFolderUid={dashboard?.meta.folderUid} isOpen={showingAddPanelModal} /> )} diff --git a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx index 442cce84952..e3b551d302a 100644 --- a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx @@ -10,14 +10,21 @@ import { usePanelSave } from '../../utils/usePanelSave'; interface Props { panel: PanelModelWithLibraryPanel; - folderId: number; + folderUid: string; isUnsavedPrompt?: boolean; onConfirm: () => void; onDismiss: () => void; onDiscard: () => void; } -export const SaveLibraryPanelModal = ({ panel, folderId, isUnsavedPrompt, onDismiss, onConfirm, onDiscard }: Props) => { +export const SaveLibraryPanelModal = ({ + panel, + folderUid, + isUnsavedPrompt, + onDismiss, + onConfirm, + onDiscard, +}: Props) => { const [searchString, setSearchString] = useState(''); const dashState = useAsync(async () => { const searchHits = await getConnectedDashboards(panel.libraryPanel.uid); @@ -98,7 +105,7 @@ export const SaveLibraryPanelModal = ({ panel, folderId, isUnsavedPrompt, onDism )}