From 63a09c27f761f55a126082a2cf45064bb8dc9df4 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 28 Nov 2024 21:12:52 -0700 Subject: [PATCH] Chore: remove frontend code that retrieves by folder id (#97066) --- .../ReadonlyFolderPicker.test.tsx | 138 ------------------ .../ReadonlyFolderPicker.tsx | 86 ----------- .../Select/ReadonlyFolderPicker/api.test.ts | 116 --------------- .../Select/ReadonlyFolderPicker/api.ts | 75 ---------- .../Select/ReadonlyFolderPicker/types.ts | 3 - .../manage-dashboards/state/actions.ts | 3 - 6 files changed, 421 deletions(-) delete mode 100644 public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx delete mode 100644 public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx delete mode 100644 public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts delete mode 100644 public/app/core/components/Select/ReadonlyFolderPicker/api.ts delete mode 100644 public/app/core/components/Select/ReadonlyFolderPicker/types.ts diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx deleted file mode 100644 index 6269aea909f..00000000000 --- a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { byTestId } from 'testing-library-selector'; - -import { SelectableValue } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; - -import { FolderInfo, PermissionLevelString } from '../../../../types'; - -import { ALL_FOLDER, GENERAL_FOLDER, ReadonlyFolderPicker, ReadonlyFolderPickerProps } from './ReadonlyFolderPicker'; -import * as api from './api'; - -const FOLDERS = [ - { value: GENERAL_FOLDER, label: GENERAL_FOLDER.title }, - { value: { id: 1, title: 'Test' }, label: 'Test' }, -]; - -async function getTestContext( - propOverrides: Partial = {}, - folders: Array> = [], - folder: SelectableValue | undefined = undefined -) { - jest.clearAllMocks(); - const selectors = { - container: byTestId(e2eSelectors.components.ReadonlyFolderPicker.container), - }; - const getFoldersAsOptionsSpy = jest.spyOn(api, 'getFoldersAsOptions').mockResolvedValue(folders); - const getFolderAsOptionSpy = jest.spyOn(api, 'getFolderAsOption').mockResolvedValue(folder); - const props: ReadonlyFolderPickerProps = { - onChange: jest.fn(), - }; - - Object.assign(props, propOverrides); - - render(); - await waitFor(() => expect(screen.queryByText(/Loading/)).not.toBeInTheDocument()); - - return { getFoldersAsOptionsSpy, getFolderAsOptionSpy, selectors }; -} - -describe('ReadonlyFolderPicker', () => { - describe('when there are no folders', () => { - it('then the no folder should be selected and Choose should appear', async () => { - const { selectors } = await getTestContext(); - - expect(within(selectors.container.get()).getByText('Choose')).toBeInTheDocument(); - }); - }); - - describe('when permissionLevel is set', () => { - it('then permissionLevel is passed correctly to getFoldersAsOptions', async () => { - const { getFoldersAsOptionsSpy } = await getTestContext({ permissionLevel: PermissionLevelString.Edit }); - - expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({ - query: '', - permissionLevel: 'Edit', - extraFolders: [], - }); - }); - }); - - describe('when extraFolders is set', () => { - it('then extraFolders is passed correctly to getFoldersAsOptions', async () => { - const { getFoldersAsOptionsSpy } = await getTestContext({ extraFolders: [ALL_FOLDER] }); - - expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({ - query: '', - permissionLevel: 'View', - extraFolders: [{ id: undefined, title: 'All' }], - }); - }); - }); - - describe('when entering a query in the input', () => { - it('then query is passed correctly to getFoldersAsOptions', async () => { - const { getFoldersAsOptionsSpy, selectors } = await getTestContext(); - - expect(within(selectors.container.get()).getByRole('combobox')).toBeInTheDocument(); - getFoldersAsOptionsSpy.mockClear(); - await userEvent.type(within(selectors.container.get()).getByRole('combobox'), 'A'); - await waitFor(() => expect(getFoldersAsOptionsSpy).toHaveBeenCalledTimes(1)); - - expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({ - query: 'A', - permissionLevel: 'View', - extraFolders: [], - }); - }); - }); - - describe('when there are folders', () => { - it('then the first folder in all folders should be selected', async () => { - const { selectors } = await getTestContext({}, FOLDERS); - - expect(await within(selectors.container.get()).findByText('Dashboards')).toBeInTheDocument(); - }); - - describe('and initialFolderId is passed in props and it matches an existing folder', () => { - it('then the folder with an id equal to initialFolderId should be selected', async () => { - const { selectors } = await getTestContext({ initialFolderId: 1 }, FOLDERS); - - expect(await within(selectors.container.get()).findByText('Test')).toBeInTheDocument(); - }); - }); - - describe('and initialFolderId is passed in props and it does not match an existing folder from search api', () => { - it('then getFolderAsOption should be called and correct folder should be selected', async () => { - const folderById = { - value: { id: 50000, title: 'Outside api search' }, - label: 'Outside api search', - }; - const { selectors, getFolderAsOptionSpy } = await getTestContext( - { initialFolderId: 50000 }, - FOLDERS, - folderById - ); - - expect(await within(selectors.container.get()).findByText('Outside api search')).toBeInTheDocument(); - expect(getFolderAsOptionSpy).toHaveBeenCalledTimes(1); - expect(getFolderAsOptionSpy).toHaveBeenCalledWith(50000); - }); - }); - - describe('and initialFolderId is passed in props and folder does not exist', () => { - it('then getFolderAsOption should be called and the first folder should be selected instead', async () => { - const { selectors, getFolderAsOptionSpy } = await getTestContext( - { initialFolderId: 50000 }, - FOLDERS, - undefined - ); - - expect(await within(selectors.container.get()).findByText('Dashboards')).toBeInTheDocument(); - expect(getFolderAsOptionSpy).toHaveBeenCalledTimes(1); - expect(getFolderAsOptionSpy).toHaveBeenCalledWith(50000); - }); - }); - }); -}); diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx deleted file mode 100644 index 170d6312031..00000000000 --- a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import debouncePromise from 'debounce-promise'; -import { ReactElement, useCallback, useState } from 'react'; - -import { SelectableValue } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { AsyncSelect } from '@grafana/ui'; - -import { GENERAL_FOLDER_ID, GENERAL_FOLDER_TITLE } from '../../../../features/search/constants'; -import { FolderInfo, PermissionLevelString } from '../../../../types'; - -import { findOptionWithId, getFolderAsOption, getFoldersAsOptions } from './api'; -import { PermissionLevel } from './types'; - -export const ALL_FOLDER: FolderInfo = { id: undefined, title: 'All' }; -export const GENERAL_FOLDER: FolderInfo = { id: GENERAL_FOLDER_ID, title: GENERAL_FOLDER_TITLE }; - -export interface ReadonlyFolderPickerProps { - onChange: (folder?: FolderInfo) => void; - initialFolderId?: number; - /** - * By default the folders API doesn't include the General folder because it doesn't exist - * Add any extra folders you need to appear in the folder picker with the extraFolders property - */ - extraFolders?: FolderInfo[]; - permissionLevel?: PermissionLevel; -} - -export function ReadonlyFolderPicker({ - onChange: propsOnChange, - extraFolders = [], - initialFolderId, - permissionLevel = PermissionLevelString.View, -}: ReadonlyFolderPickerProps): ReactElement { - const [initialized, setInitialized] = useState(false); - const [option, setOption] = useState | undefined>(undefined); - const [options, setOptions] = useState> | undefined>(undefined); - const initialize = useCallback( - async (options: Array>) => { - let option = findOptionWithId(options, initialFolderId); - if (!option) { - // we didn't find the option with the initialFolderId - // might be because the folder doesn't exist any longer - // might be because the folder is outside of the search limit of the api - option = (await getFolderAsOption(initialFolderId)) ?? options[0]; // get folder by id or select the first item in the options and call propsOnChange - propsOnChange(option.value); - } - - setInitialized(true); - setOptions(options); - setOption(option); - }, - [initialFolderId, propsOnChange] - ); - const loadOptions = useCallback( - async (query: string) => { - const options = await getFoldersAsOptions({ query, permissionLevel, extraFolders }); - if (!initialized) { - await initialize(options); - } - return options; - }, - [permissionLevel, extraFolders, initialized, initialize] - ); - const debouncedLoadOptions = debouncePromise(loadOptions, 300, { leading: true }); - const onChange = useCallback( - ({ value }: SelectableValue) => { - const option = findOptionWithId(options, value?.id); - setOption(option); - propsOnChange(value); - }, - [options, propsOnChange] - ); - - return ( -
- -
- ); -} diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts b/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts deleted file mode 100644 index 57259113c62..00000000000 --- a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; -import * as api from '../../../../features/manage-dashboards/state/actions'; -import { DashboardSearchHit } from '../../../../features/search/types'; -import { PermissionLevelString } from '../../../../types'; - -import { ALL_FOLDER, GENERAL_FOLDER } from './ReadonlyFolderPicker'; -import { getFolderAsOption, getFoldersAsOptions } from './api'; - -function getTestContext( - searchHits: DashboardSearchHit[] = [], - folderById: { id: number; title: string } = { id: 1, title: 'Folder 1' } -) { - jest.clearAllMocks(); - const searchFoldersSpy = jest.spyOn(api, 'searchFolders').mockResolvedValue(searchHits); - const getFolderByIdSpy = jest.spyOn(api, 'getFolderById').mockResolvedValue(folderById); - - return { searchFoldersSpy, getFolderByIdSpy }; -} - -describe('getFoldersAsOptions', () => { - describe('when called without permissionLevel and query', () => { - it('then the correct defaults are passed to the api', async () => { - const { searchFoldersSpy } = getTestContext(); - - await getFoldersAsOptions({ query: '' }); - - expect(searchFoldersSpy).toHaveBeenCalledTimes(1); - expect(searchFoldersSpy).toHaveBeenCalledWith('', 'View'); - }); - - describe('and extra folders are passed', () => { - it('then extra folders should all appear first in the result', async () => { - const args = { query: '', extraFolders: [ALL_FOLDER, GENERAL_FOLDER] }; - const searchHits = [{ id: 1, title: 'Folder 1' }] as DashboardSearchHit[]; - getTestContext(searchHits); - - const result = await getFoldersAsOptions(args); - expect(result).toEqual([ - { value: { id: undefined, title: 'All' }, label: 'All' }, - { value: { id: 0, title: 'Dashboards' }, label: 'Dashboards' }, - { value: { id: 1, title: 'Folder 1' }, label: 'Folder 1' }, - ]); - }); - }); - }); - - describe('when called with permissionLevel and query', () => { - it('then the correct values are passed to the api', async () => { - const { searchFoldersSpy } = getTestContext(); - - await getFoldersAsOptions({ query: 'Folder1', permissionLevel: PermissionLevelString.Edit }); - - expect(searchFoldersSpy).toHaveBeenCalledTimes(1); - expect(searchFoldersSpy).toHaveBeenCalledWith('Folder1', 'Edit'); - }); - - describe('and extra folders are passed and extra folders contain query', () => { - it('then correct extra folders should all appear first in the result', async () => { - const args = { query: 'o', extraFolders: [ALL_FOLDER, GENERAL_FOLDER] }; - const searchHits = [{ id: 1, title: 'Folder 1' }] as DashboardSearchHit[]; - getTestContext(searchHits); - - const result = await getFoldersAsOptions(args); - expect(result).toEqual([ - { value: { id: 0, title: 'Dashboards' }, label: 'Dashboards' }, - { value: { id: 1, title: 'Folder 1' }, label: 'Folder 1' }, - ]); - }); - }); - - describe('and extra folders are passed and extra folders do not contain query', () => { - it('then no extra folders should appear first in the result', async () => { - const args = { query: '1', extraFolders: [ALL_FOLDER, GENERAL_FOLDER] }; - const searchHits = [{ id: 1, title: 'Folder 1' }] as DashboardSearchHit[]; - getTestContext(searchHits); - - const result = await getFoldersAsOptions(args); - expect(result).toEqual([{ value: { id: 1, title: 'Folder 1' }, label: 'Folder 1' }]); - }); - }); - }); -}); - -describe('getFolderAsOption', () => { - describe('when called with undefined', () => { - it('then it should return undefined', async () => { - const { getFolderByIdSpy } = getTestContext(); - - const result = await getFolderAsOption(undefined); - expect(result).toBeUndefined(); - expect(getFolderByIdSpy).not.toHaveBeenCalled(); - }); - }); - - describe('when called with a folder id that does not exist', () => { - silenceConsoleOutput(); - it('then it should return undefined', async () => { - const { getFolderByIdSpy } = getTestContext(); - getFolderByIdSpy.mockRejectedValue('Not found'); - - const result = await getFolderAsOption(-1); - expect(result).toBeUndefined(); - expect(getFolderByIdSpy).toHaveBeenCalled(); - }); - }); - - describe('when called with a folder id that exist', () => { - it('then it should return a SelectableValue of FolderInfo', async () => { - const { getFolderByIdSpy } = getTestContext(); - - const result = await getFolderAsOption(1); - expect(result).toEqual({ value: { id: 1, title: 'Folder 1' }, label: 'Folder 1' }); - expect(getFolderByIdSpy).toHaveBeenCalled(); - }); - }); -}); diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/api.ts b/public/app/core/components/Select/ReadonlyFolderPicker/api.ts deleted file mode 100644 index 8641fd9a6fd..00000000000 --- a/public/app/core/components/Select/ReadonlyFolderPicker/api.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { SelectableValue } from '@grafana/data'; - -import { getFolderById, searchFolders } from '../../../../features/manage-dashboards/state/actions'; -import { FolderInfo, PermissionLevelString } from '../../../../types'; - -import { PermissionLevel } from './types'; - -interface GetFoldersArgs { - query: string; - permissionLevel?: PermissionLevel; -} - -async function getFolders({ query, permissionLevel }: GetFoldersArgs): Promise { - const searchHits = await searchFolders(query, permissionLevel); - const folders: FolderInfo[] = searchHits.map((searchHit) => ({ - id: searchHit.id, - title: searchHit.title, - url: searchHit.url, - })); - - return folders; -} - -export interface GetFoldersWithEntriesArgs extends GetFoldersArgs { - extraFolders?: FolderInfo[]; -} - -async function getFoldersWithEntries({ - query, - permissionLevel, - extraFolders, -}: GetFoldersWithEntriesArgs): Promise { - const folders = await getFolders({ query, permissionLevel }); - const extra: FolderInfo[] = extraFolders ?? []; - const filteredExtra = query ? extra.filter((f) => f.title?.toLowerCase().includes(query.toLowerCase())) : extra; - if (folders) { - return filteredExtra.concat(folders); - } - - return filteredExtra; -} - -export async function getFoldersAsOptions({ - query, - permissionLevel = PermissionLevelString.View, - extraFolders = [], -}: GetFoldersWithEntriesArgs) { - const folders = await getFoldersWithEntries({ query, permissionLevel, extraFolders }); - return folders.map((value) => { - const option: SelectableValue = { value, label: value.title }; - return option; - }); -} - -export function findOptionWithId( - options?: Array>, - id?: number -): SelectableValue | undefined { - return options?.find((o) => o.value?.id === id); -} - -export async function getFolderAsOption(folderId?: number): Promise | undefined> { - if (folderId === undefined || folderId === null) { - return; - } - - try { - const { id, title } = await getFolderById(folderId); - return { value: { id, title }, label: title }; - } catch (err) { - console.error(`Could not find folder with id:${folderId}`); - } - - return; -} diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/types.ts b/public/app/core/components/Select/ReadonlyFolderPicker/types.ts deleted file mode 100644 index f01bb8a8a58..00000000000 --- a/public/app/core/components/Select/ReadonlyFolderPicker/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PermissionLevelString } from '../../../../types'; - -export type PermissionLevel = Exclude; diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 4a2b0e02d3e..60fb84442bb 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -283,6 +283,3 @@ export function searchFolders( export function getFolderByUid(uid: string): Promise<{ uid: string; title: string }> { return getBackendSrv().get(`/api/folders/${uid}`); } -export function getFolderById(id: number): Promise<{ id: number; title: string }> { - return getBackendSrv().get(`/api/folders/id/${id}`); -}