diff --git a/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts b/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts index 4d5b1982434..747eb499cdf 100644 --- a/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts @@ -11,6 +11,7 @@ const collator = new Intl.Collator(); const mockAccessControl = { 'dashboards.permissions:write': true, 'dashboards:create': true, + 'folders:write': true, }; const additionalProperties = { canAdmin: true, @@ -108,6 +109,19 @@ const createFolderHandler = () => }); }); -const handlers = [listFoldersHandler(), getFolderHandler(), createFolderHandler()]; +const saveFolderHandler = () => + http.put<{ uid: string }, { title: string; version: number }>('/api/folders/:uid', async ({ params, request }) => { + const { uid } = params; + const body = await request.json(); + const folder = mockTree.find((v) => v.item.uid === uid); + + if (!folder) { + return HttpResponse.json({ message: 'folder not found' }, { status: 404 }); + } + + return HttpResponse.json({ ...folder.item, title: body.title }); + }); + +const handlers = [listFoldersHandler(), getFolderHandler(), createFolderHandler(), saveFolderHandler()]; export default handlers; diff --git a/packages/grafana-test-utils/src/handlers/api/search/constants.ts b/packages/grafana-test-utils/src/handlers/api/search/constants.ts new file mode 100644 index 00000000000..b4249dc4696 --- /dev/null +++ b/packages/grafana-test-utils/src/handlers/api/search/constants.ts @@ -0,0 +1,17 @@ +/** Expected constant response from `/api/search/sorting` */ +export const SORT_OPTIONS = { + sortOptions: [ + { + description: 'Sort results in an alphabetically ascending order', + displayName: 'Alphabetically (A–Z)', + meta: '', + name: 'alpha-asc', + }, + { + description: 'Sort results in an alphabetically descending order', + displayName: 'Alphabetically (Z–A)', + meta: '', + name: 'alpha-desc', + }, + ], +}; diff --git a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts index 195af7dac1d..f8f0a0d6b4e 100644 --- a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts @@ -3,6 +3,8 @@ import { HttpResponse, http } from 'msw'; import { wellFormedTree } from '../../../fixtures/folders'; +import { SORT_OPTIONS } from './constants'; + const [mockTree] = wellFormedTree(); type FilterArray = Array<(v: (typeof mockTree)[number]) => boolean>; @@ -70,4 +72,6 @@ const getLegacySearchHandler = () => return HttpResponse.json(response); }); -export default [getLegacySearchHandler()]; +const getSearchSortingHandler = () => http.get('/api/search/sorting', () => HttpResponse.json(SORT_OPTIONS)); + +export default [getLegacySearchHandler(), getSearchSortingHandler()]; diff --git a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts index 43813d6f4f8..469cc4316bd 100644 --- a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts @@ -7,32 +7,42 @@ const [mockTree] = wellFormedTree(); type FilterArray = Array<(v: (typeof mockTree)[number]) => boolean>; +const typeMap: Record = { + folder: 'folders', + dashboard: 'dashboards', +}; + const getSearchHandler = () => - http.get('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/search', ({ request }) => { + http.get('/apis/dashboard.grafana.app/v0alpha1/namespaces/:namespace/search', ({ request }) => { const folderFilter = new URL(request.url).searchParams.get('folder') || null; const typeFilter = new URL(request.url).searchParams.get('type') || null; const response = mockTree .filter((filterItem) => { const filters: FilterArray = []; - if (folderFilter && folderFilter !== 'general') { - filters.push(({ item }) => item.kind === 'folder' && item.parentUID === folderFilter); - } - - if (folderFilter === 'general') { - filters.push(({ item }) => item.kind === 'folder' && item.parentUID === undefined); - } if (typeFilter) { filters.push(({ item }) => item.kind === typeFilter); } + if (folderFilter && folderFilter !== 'general') { + filters.push( + ({ item }) => (item.kind === 'folder' || item.kind === 'dashboard') && item.parentUID === folderFilter + ); + } + + if (folderFilter === 'general') { + filters.push( + ({ item }) => (item.kind === 'folder' || item.kind === 'dashboard') && item.parentUID === undefined + ); + } + return filters.every((filterPredicate) => filterPredicate(filterItem)); }) .map(({ item }) => { const random = Chance(item.uid); return { - resource: 'folders', + resource: typeMap[item.kind], name: item.uid, title: item.title, field: { diff --git a/packages/grafana-test-utils/src/handlers/apis/folder.grafana.app/v1beta1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/folder.grafana.app/v1beta1/handlers.ts index f53261a9b97..732a57c24ea 100644 --- a/packages/grafana-test-utils/src/handlers/apis/folder.grafana.app/v1beta1/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/apis/folder.grafana.app/v1beta1/handlers.ts @@ -11,6 +11,32 @@ const baseResponse = { apiVersion: 'folder.grafana.app/v1beta1', }; +const folderToAppPlatform = (folder: (typeof mockTree)[number]['item'], id?: number, namespace?: string) => { + return { + ...baseResponse, + + metadata: { + name: folder.uid, + namespace: namespace ?? 'default', + uid: folder.uid, + creationTimestamp: '2023-01-01T00:00:00Z', + annotations: { + // TODO: Generalise annotations in fixture data + 'grafana.app/createdBy': 'user:1', + 'grafana.app/updatedBy': 'user:2', + 'grafana.app/managedBy': 'user', + 'grafana.app/updatedTimestamp': '2024-01-01T00:00:00Z', + 'grafana.app/folder': folder.kind === 'folder' ? folder.parentUID : undefined, + }, + labels: { + 'grafana.app/deprecatedInternalID': id ?? '123', + }, + }, + spec: { title: folder.title, description: '' }, + status: {}, + }; +}; + const folderNotFoundError = getErrorResponse('folder not found', 404); const getFolderHandler = () => @@ -26,28 +52,9 @@ const getFolderHandler = () => return HttpResponse.json(folderNotFoundError, { status: 404 }); } - return HttpResponse.json({ - ...baseResponse, - metadata: { - name: response.item.uid, - namespace, - uid: response.item.uid, - creationTimestamp: '2023-01-01T00:00:00Z', - annotations: { - // TODO: Generalise annotations in fixture data - 'grafana.app/createdBy': 'user:1', - 'grafana.app/updatedBy': 'user:2', - 'grafana.app/managedBy': 'user', - 'grafana.app/updatedTimestamp': '2024-01-01T00:00:00Z', - 'grafana.app/folder': response.item.kind === 'folder' ? response.item.parentUID : undefined, - }, - labels: { - 'grafana.app/deprecatedInternalID': '123', - }, - }, - spec: { title: response.item.title, description: '' }, - status: {}, - }); + const appPlatformFolder = folderToAppPlatform(response.item, undefined, namespace); + + return HttpResponse.json(appPlatformFolder); } ); @@ -121,35 +128,40 @@ const createFolderHandler = () => const parentUid = body?.metadata?.annotations?.['grafana.app/folder']; const random = Chance(title); - const name = random.string({ length: 10 }); const uid = random.string({ length: 45 }); const id = random.integer({ min: 1, max: 1000 }); - return HttpResponse.json({ - ...baseResponse, - metadata: { - name, - namespace, - uid, - resourceVersion: '1756207979831', - generation: 1, - creationTimestamp: '2025-08-26T11:32:59Z', - labels: { - 'grafana.app/deprecatedInternalID': id, - }, - annotations: { - 'grafana.app/createdBy': 'user:1', - 'grafana.app/folder': parentUid, - 'grafana.app/updatedBy': 'user:1', - 'grafana.app/updatedTimestamp': '2025-08-26T11:32:59Z', - }, - }, - spec: { - title, - description: '', - }, - status: {}, - }); + const appPlatformFolder = folderToAppPlatform( + { uid, title, parentUID: parentUid, kind: 'folder' }, + id, + namespace + ); + return HttpResponse.json(appPlatformFolder); } ); -export default [getFolderHandler(), getFolderParentsHandler(), createFolderHandler()]; + +const replaceFolderHandler = () => + http.put<{ folderUid: string; namespace: string }, PartialFolderPayload>( + '/apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:folderUid', + async ({ params, request }) => { + const body = await request.json(); + const { folderUid } = params; + const response = mockTree.find(({ item }) => { + return item.uid === folderUid; + }); + + if (!response) { + return HttpResponse.json(folderNotFoundError, { status: 404 }); + } + + const modifiedFolder = { + ...response.item, + title: body.spec.title, + }; + + const appPlatformFolder = folderToAppPlatform(modifiedFolder); + + return HttpResponse.json(appPlatformFolder); + } + ); +export default [getFolderHandler(), getFolderParentsHandler(), createFolderHandler(), replaceFolderHandler()]; diff --git a/public/app/api/clients/folder/v1beta1/hooks.test.ts b/public/app/api/clients/folder/v1beta1/hooks.test.ts index 1dd64562abe..0b8aa0eceed 100644 --- a/public/app/api/clients/folder/v1beta1/hooks.test.ts +++ b/public/app/api/clients/folder/v1beta1/hooks.test.ts @@ -17,7 +17,7 @@ import { useDeleteMultipleFoldersMutationFacade, useMoveMultipleFoldersMutationFacade, } from './hooks'; -import { setupCreateFolder } from './test-utils'; +import { setupCreateFolder, setupUpdateFolder } from './test-utils'; import { useDeleteFolderMutation, useUpdateFolderMutation } from './index'; @@ -68,7 +68,7 @@ const renderFolderHook = async () => { wrapper: getWrapper({}), }); await waitFor(() => { - expect(result.current.isLoading).toBe(false); + expect(result.current.data).toBeDefined(); }); return result; }; @@ -246,20 +246,20 @@ describe('useMoveMultipleFoldersMutationFacade', () => { }); }); -describe('useCreateFolder', () => { - describe.each([ - // app platform - true, - // legacy - false, - ])('folderAppPlatformAPI toggle set to: %s', (toggle) => { - beforeEach(() => { - config.featureToggles.foldersAppPlatformAPI = toggle; - }); - afterEach(() => { - config.featureToggles = originalToggles; - }); +describe.each([ + // app platform + true, + // legacy + false, +])('folderAppPlatformAPI toggle set to: %s', (toggle) => { + beforeEach(() => { + config.featureToggles.foldersAppPlatformAPI = toggle; + }); + afterEach(() => { + config.featureToggles = originalToggles; + }); + describe('useCreateFolder', () => { it('creates a folder', async () => { const { user } = setupCreateFolder(); @@ -268,4 +268,15 @@ describe('useCreateFolder', () => { expect(await screen.findByText('Folder created')).toBeInTheDocument(); }); }); + + describe('useUpdateFolder', () => { + it('updates a folder', async () => { + const { user } = await setupUpdateFolder(folderA_folderA.item.uid); + + await user.type(screen.getByLabelText('Folder Title'), 'Updated Folder'); + await user.click(screen.getByText('Update Folder')); + + expect(await screen.findByText('Folder updated')).toBeInTheDocument(); + }); + }); }); diff --git a/public/app/api/clients/folder/v1beta1/hooks.ts b/public/app/api/clients/folder/v1beta1/hooks.ts index 7db4e810260..9c7cc1aae0f 100644 --- a/public/app/api/clients/folder/v1beta1/hooks.ts +++ b/public/app/api/clients/folder/v1beta1/hooks.ts @@ -11,6 +11,7 @@ import { useDeleteFoldersMutation as useDeleteFoldersMutationLegacy, useNewFolderMutation as useLegacyNewFolderMutation, useMoveFoldersMutation as useMoveFoldersMutationLegacy, + useSaveFolderMutation as useLegacySaveFolderMutation, MoveFoldersArgs, DeleteFoldersArgs, } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; @@ -44,6 +45,8 @@ import { useUpdateFolderMutation, Folder, CreateFolderApiArg, + useReplaceFolderMutation, + ReplaceFolderApiArg, } from './index'; /** Trigger necessary actions to ensure legacy folder stores are updated */ @@ -320,6 +323,38 @@ export function useCreateFolder() { return [createFolderAppPlatform, result] as const; } +export function useUpdateFolder() { + const [updateFolder, result] = useReplaceFolderMutation(); + const legacyHook = useLegacySaveFolderMutation(); + + if (!config.featureToggles.foldersAppPlatformAPI) { + return legacyHook; + } + + const updateFolderAppPlatform = async (folder: Pick) => { + const payload: ReplaceFolderApiArg = { + name: folder.uid, + folder: { + spec: { title: folder.title }, + metadata: { + name: folder.uid, + }, + status: {}, + }, + }; + + const result = await updateFolder(payload); + dispatchRefetchChildren(folder.parentUid); + + return { + ...result, + data: result.data ? appPlatformFolderToLegacyFolder(result.data) : undefined, + }; + }; + + return [updateFolderAppPlatform, result] as const; +} + function combinedState( result: ReturnType, resultParents: ReturnType, diff --git a/public/app/api/clients/folder/v1beta1/index.ts b/public/app/api/clients/folder/v1beta1/index.ts index 78aafad3098..38477c6c852 100644 --- a/public/app/api/clients/folder/v1beta1/index.ts +++ b/public/app/api/clients/folder/v1beta1/index.ts @@ -45,7 +45,8 @@ export const { useDeleteFolderMutation, useCreateFolderMutation, useUpdateFolderMutation, + useReplaceFolderMutation, } = folderAPIv1beta1; // eslint-disable-next-line no-barrel-files/no-barrel-files -export { type Folder, type FolderList, type CreateFolderApiArg } from './endpoints.gen'; +export { type Folder, type FolderList, type CreateFolderApiArg, type ReplaceFolderApiArg } from './endpoints.gen'; diff --git a/public/app/api/clients/folder/v1beta1/test-utils.tsx b/public/app/api/clients/folder/v1beta1/test-utils.tsx index 34368bffa80..c120045fcf5 100644 --- a/public/app/api/clients/folder/v1beta1/test-utils.tsx +++ b/public/app/api/clients/folder/v1beta1/test-utils.tsx @@ -1,9 +1,10 @@ -import { render } from 'test/test-utils'; +import { useState } from 'react'; +import { render, screen } from 'test/test-utils'; import { getFolderFixtures } from '@grafana/test-utils/unstable'; import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList'; -import { useCreateFolder } from './hooks'; +import { useCreateFolder, useUpdateFolder } from './hooks'; const [_, { folderA }] = getFolderFixtures(); @@ -19,5 +20,27 @@ const TestCreationComponent = () => { ); }; +const TestUpdateComponent = ({ folderUID }: { folderUID: string }) => { + const [updateFolder, result] = useUpdateFolder(); + const [title, setTitle] = useState(''); + + return ( + <> + + + setTitle(e.target.value)} /> + +
{result.isSuccess ? 'Folder updated' : 'Error updating folder'}
+ + ); +}; + /** Renders test component with a button that will create a new folder */ export const setupCreateFolder = () => render(); + +/** Renders test component with a button that allows updating a folder */ +export const setupUpdateFolder = async (folderUID: string) => { + const view = render(); + await screen.findByText('Update Folder'); + return view; +}; diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx index b7775e5c249..d05b97d6960 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx @@ -1,14 +1,11 @@ -import { render as rtlRender, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { HttpResponse, http } from 'msw'; import { ComponentProps } from 'react'; -import * as React from 'react'; import { useParams } from 'react-router-dom-v5-compat'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { render as testRender, screen, waitFor } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; -import server, { setupMockServer } from '@grafana/test-utils/server'; +import { config, setBackendSrv } from '@grafana/runtime'; +import { setupMockServer } from '@grafana/test-utils/server'; import { getFolderFixtures } from '@grafana/test-utils/unstable'; import { contextSrv } from 'app/core/core'; import { backendSrv } from 'app/core/services/backend_srv'; @@ -16,17 +13,10 @@ import { backendSrv } from 'app/core/services/backend_srv'; import BrowseDashboardsPage from './BrowseDashboardsPage'; import * as permissions from './permissions'; +setBackendSrv(backendSrv); setupMockServer(); -const [_, { dashbdD, folderA, folderA_folderA }] = getFolderFixtures(); -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - getBackendSrv: () => backendSrv, - config: { - ...jest.requireActual('@grafana/runtime').config, - unifiedAlertingEnabled: true, - }, -})); +const [_, { dashbdD, folderA, folderA_folderA }] = getFolderFixtures(); jest.mock('react-virtualized-auto-sizer', () => { return { @@ -51,42 +41,10 @@ jest.mock('react-router-dom-v5-compat', () => ({ useParams: jest.fn().mockReturnValue({}), })); -function render(...[ui, options]: Parameters) { - const { rerender } = rtlRender( - - {ui} - , - options - ); - - const wrappedRerender = (ui: React.ReactElement) => { - rerender( - - {ui} - - ); - }; - return { - rerender: wrappedRerender, - }; +function render(ui: Parameters[0]) { + return testRender(ui, { + preloadedState: { navIndex: { 'dashboards/browse': { text: 'Dashboards', id: 'dashboards/browse' } } }, + }); } describe('browse-dashboards BrowseDashboardsPage', () => { @@ -102,16 +60,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }; beforeEach(() => { - server.use( - http.get('/api/search/sorting', () => { - return HttpResponse.json({ - sortOptions: [], - }); - }) - ); - }); - - beforeEach(() => { + config.unifiedAlertingEnabled = true; jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => mockPermissions); jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true); }); @@ -188,10 +137,10 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('selecting an item hides the filters and shows the actions instead', async () => { - render(); + const { user } = render(); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid)); - await userEvent.click(checkbox); + await user.click(checkbox); // Check the filters are now hidden expect(screen.queryByText('Filter by tag')).not.toBeInTheDocument(); @@ -203,10 +152,10 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('navigating into a child item resets the selected state', async () => { - const { rerender } = render(); + const { rerender, user } = render(); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid)); - await userEvent.click(checkbox); + await user.click(checkbox); // Check the actions are now visible expect(screen.getByRole('button', { name: 'Move' })).toBeInTheDocument(); @@ -304,12 +253,12 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('selecting an item hides the filters and shows the actions instead', async () => { - render(); + const { user } = render(); const checkbox = await screen.findByTestId( selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid) ); - await userEvent.click(checkbox); + await user.click(checkbox); // Check the filters are now hidden expect(screen.queryByText('Filter by tag')).not.toBeInTheDocument(); diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index a5e7861a2c9..c658fbaa6cd 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -7,7 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { config, reportInteraction } from '@grafana/runtime'; import { LinkButton, FilterInput, useStyles2, Text, Stack } from '@grafana/ui'; -import { useGetFolderQueryFacade } from 'app/api/clients/folder/v1beta1/hooks'; +import { useGetFolderQueryFacade, useUpdateFolder } from 'app/api/clients/folder/v1beta1/hooks'; import { Page } from 'app/core/components/Page/Page'; import { getConfig } from 'app/core/config'; import { useDispatch } from 'app/types/store'; @@ -21,7 +21,6 @@ import { useGetResourceRepositoryView } from '../provisioning/hooks/useGetResour import { useSearchStateManager } from '../search/state/SearchStateManager'; import { getSearchPlaceholder } from '../search/tempI18nPhrases'; -import { useSaveFolderMutation } from './api/browseDashboardsAPI'; import { BrowseActions } from './components/BrowseActions/BrowseActions'; import { BrowseFilters } from './components/BrowseFilters'; import { BrowseView } from './components/BrowseView'; @@ -73,7 +72,7 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record { if (!folderDTO) { return undefined; diff --git a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx index 72c4663fa6d..69f2e539cab 100644 --- a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx @@ -1,5 +1,4 @@ -import { screen } from '@testing-library/react'; -import { render } from 'test/test-utils'; +import { render, screen } from 'test/test-utils'; import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; diff --git a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.tsx b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.tsx index 3f775ef3a35..6fb5a1ee04a 100644 --- a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom-v5-compat'; import { t } from '@grafana/i18n'; import { Alert } from '@grafana/ui'; -import { useGetFolderQueryFacade } from 'app/api/clients/folder/v1beta1/hooks'; +import { useGetFolderQueryFacade, useUpdateFolder } from 'app/api/clients/folder/v1beta1/hooks'; import { Page } from 'app/core/components/Page/Page'; import { buildNavModel, getAlertingTabID } from 'app/features/folders/state/navModel'; @@ -13,7 +13,6 @@ import { GRAFANA_RULER_CONFIG } from '../alerting/unified/api/featureDiscoveryAp import { stringifyErrorLike } from '../alerting/unified/utils/misc'; import { rulerRuleType } from '../alerting/unified/utils/rules'; -import { useSaveFolderMutation } from './api/browseDashboardsAPI'; import { FolderActionsButton } from './components/FolderActionsButton'; const { useRulerNamespaceQuery } = alertRuleApi; @@ -31,7 +30,7 @@ export function BrowseFolderAlertingPage() { namespace: folderUID, }); - const [saveFolder] = useSaveFolderMutation(); + const [saveFolder] = useUpdateFolder(); const navModel = useMemo(() => { if (!folderDTO) { diff --git a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx index 9b96316add6..aeb5e352fc6 100644 --- a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx @@ -2,6 +2,7 @@ import { http, HttpResponse } from 'msw'; import { useParams } from 'react-router-dom-v5-compat'; import { render, screen } from 'test/test-utils'; +import { config, setBackendSrv } from '@grafana/runtime'; import server, { setupMockServer } from '@grafana/test-utils/server'; import { getFolderFixtures } from '@grafana/test-utils/unstable'; import { contextSrv } from 'app/core/core'; @@ -11,15 +12,9 @@ import BrowseFolderLibraryPanelsPage from './BrowseFolderLibraryPanelsPage'; import { getLibraryElementsResponse } from './fixtures/libraryElements.fixture'; import * as permissions from './permissions'; +setBackendSrv(backendSrv); setupMockServer(); -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - getBackendSrv: () => backendSrv, - config: { - ...jest.requireActual('@grafana/runtime').config, - unifiedAlertingEnabled: true, - }, -})); + jest.mock('react-router-dom-v5-compat', () => ({ ...jest.requireActual('react-router-dom-v5-compat'), useParams: jest.fn(), @@ -46,19 +41,15 @@ describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => { }; beforeEach(() => { + config.unifiedAlertingEnabled = true; server.use( http.get('/api/library-elements', () => { return HttpResponse.json({ result: mockLibraryElementsResponse, }); - }), - http.get('/api/search/sorting', () => { - return HttpResponse.json({}); }) ); - }); - beforeEach(() => { jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => mockPermissions); jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true); }); diff --git a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.tsx b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.tsx index 0211f45a1a9..a4ff0553a7f 100644 --- a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.tsx @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; +import { useGetFolderQueryFacade, useUpdateFolder } from 'app/api/clients/folder/v1beta1/hooks'; import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from '../../core/navigation/types'; @@ -10,15 +11,13 @@ import { LibraryPanelsSearch } from '../library-panels/components/LibraryPanelsS import { OpenLibraryPanelModal } from '../library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal'; import { LibraryElementDTO } from '../library-panels/types'; -import { useGetFolderQuery, useSaveFolderMutation } from './api/browseDashboardsAPI'; - export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} export function BrowseFolderLibraryPanelsPage() { const { uid: folderUID = '' } = useParams(); - const { data: folderDTO } = useGetFolderQuery(folderUID); + const { data: folderDTO } = useGetFolderQueryFacade(folderUID); const [selected, setSelected] = useState(undefined); - const [saveFolder] = useSaveFolderMutation(); + const [saveFolder] = useUpdateFolder(); const navModel = useMemo(() => { if (!folderDTO) { diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 2cdca411857..ed03d636017 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -115,7 +115,7 @@ export const browseDashboardsAPI = createApi({ }), // save an existing folder (e.g. rename) - saveFolder: builder.mutation({ + saveFolder: builder.mutation>({ // because the getFolder calls contain the parents, renaming a parent/grandparent/etc needs to invalidate all child folders // we could do something smart and recursively invalidate these child folders but it doesn't seem worth it // instead let's just invalidate all the getFolder calls