Chore: remove frontend code that retrieves by folder id (#97066)

This commit is contained in:
Stephanie Hingtgen
2024-11-28 22:12:52 -06:00
committed by GitHub
parent c7a5f96ce7
commit 63a09c27f7
6 changed files with 0 additions and 421 deletions
@@ -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<ReadonlyFolderPickerProps> = {},
folders: Array<SelectableValue<FolderInfo>> = [],
folder: SelectableValue<FolderInfo> | 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(<ReadonlyFolderPicker {...props} />);
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);
});
});
});
});
@@ -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<SelectableValue<FolderInfo> | undefined>(undefined);
const [options, setOptions] = useState<Array<SelectableValue<FolderInfo>> | undefined>(undefined);
const initialize = useCallback(
async (options: Array<SelectableValue<FolderInfo>>) => {
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<FolderInfo>) => {
const option = findOptionWithId(options, value?.id);
setOption(option);
propsOnChange(value);
},
[options, propsOnChange]
);
return (
<div data-testid={selectors.components.ReadonlyFolderPicker.container}>
<AsyncSelect
loadingMessage="Loading folders..."
defaultOptions
defaultValue={option}
value={option}
loadOptions={debouncedLoadOptions}
onChange={onChange}
/>
</div>
);
}
@@ -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();
});
});
});
@@ -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<FolderInfo[]> {
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<FolderInfo[]> {
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<FolderInfo> = { value, label: value.title };
return option;
});
}
export function findOptionWithId(
options?: Array<SelectableValue<FolderInfo>>,
id?: number
): SelectableValue<FolderInfo> | undefined {
return options?.find((o) => o.value?.id === id);
}
export async function getFolderAsOption(folderId?: number): Promise<SelectableValue<FolderInfo> | 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;
}
@@ -1,3 +0,0 @@
import { PermissionLevelString } from '../../../../types';
export type PermissionLevel = Exclude<PermissionLevelString, PermissionLevelString.Admin>;
@@ -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}`);
}