From a9b52589ff0a06a98b1a0f1745d6ee5d3a36ed39 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 11 Nov 2025 18:11:57 +0000 Subject: [PATCH] Folders: Deprecate `getFolderByUID` method (#113173) --- .../app/api/clients/folder/v1beta1/hooks.ts | 145 ++++++++++++------ .../app/api/clients/folder/v1beta1/index.ts | 7 +- public/app/api/clients/iam/v0alpha1/index.ts | 3 + public/app/core/services/backend_srv.ts | 5 + .../ConfirmRestoreDeletedRuleModal.tsx | 10 +- .../dashboard-scene/pages/utils.test.ts | 4 +- .../features/dashboard-scene/pages/utils.ts | 4 +- .../PanelDataAlertingTab.test.tsx | 3 - .../dashboard/api/UnifiedDashboardAPI.test.ts | 7 +- public/app/features/dashboard/api/v1.test.ts | 7 +- public/app/features/dashboard/api/v1.ts | 4 +- public/app/features/dashboard/api/v2.test.ts | 8 +- public/app/features/dashboard/api/v2.ts | 4 +- public/app/features/search/loaders.ts | 12 -- public/app/types/store.ts | 2 + 15 files changed, 134 insertions(+), 91 deletions(-) delete mode 100644 public/app/features/search/loaders.ts diff --git a/public/app/api/clients/folder/v1beta1/hooks.ts b/public/app/api/clients/folder/v1beta1/hooks.ts index 3ee45cde1ae..1793818bdd6 100644 --- a/public/app/api/clients/folder/v1beta1/hooks.ts +++ b/public/app/api/clients/folder/v1beta1/hooks.ts @@ -4,6 +4,7 @@ import { useEffect, useMemo } from 'react'; import { AppEvents } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config, getAppEvents } from '@grafana/runtime'; +import { DisplayList, iamAPIv0alpha1, useLazyGetDisplayMappingQuery } from 'app/api/clients/iam/v0alpha1'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useDeleteFolderMutation as useDeleteFolderMutationLegacy, @@ -17,9 +18,11 @@ import { MoveFoldersArgs, DeleteFoldersArgs, MoveFolderArgs, + browseDashboardsAPI, } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { DashboardTreeSelection } from 'app/features/browse-dashboards/types'; import { FolderDTO, NewFolder } from 'app/types/folders'; +import { dispatch } from 'app/types/store'; import kbn from '../../../../core/utils/kbn'; import { @@ -36,12 +39,12 @@ import { refetchChildren, refreshParents } from '../../../../features/browse-das import { GENERAL_FOLDER_UID } from '../../../../features/search/constants'; import { deletedDashboardsCache } from '../../../../features/search/service/deletedDashboardsCache'; import { useDispatch } from '../../../../types/store'; -import { useLazyGetDisplayMappingQuery } from '../../iam/v0alpha1'; import { isProvisionedFolderCheck } from './utils'; import { rootFolder, sharedWithMeFolder } from './virtualFolders'; import { + folderAPIv1beta1, useGetFolderQuery, useGetFolderParentsQuery, useDeleteFolderMutation, @@ -52,6 +55,7 @@ import { useReplaceFolderMutation, ReplaceFolderApiArg, useGetAffectedItemsQuery, + FolderInfo, } from './index'; function getFolderUrl(uid: string, title: string): string { @@ -62,6 +66,91 @@ function getFolderUrl(uid: string, title: string): string { return `${config.appSubUrl}/dashboards/f/${uid}/${slug}`; } +const combineFolderResponses = ( + folder: Folder, + legacyFolder: FolderDTO, + parents: FolderInfo[], + userDisplay?: DisplayList +) => { + const updatedBy = folder.metadata.annotations?.[AnnoKeyUpdatedBy]; + const createdBy = folder.metadata.annotations?.[AnnoKeyCreatedBy]; + + const newData: FolderDTO = { + canAdmin: legacyFolder.canAdmin, + canDelete: legacyFolder.canDelete, + canEdit: legacyFolder.canEdit, + canSave: legacyFolder.canSave, + accessControl: legacyFolder.accessControl, + createdBy: (createdBy && userDisplay?.display[userDisplay?.keys.indexOf(createdBy)]?.displayName) || 'Anonymous', + updatedBy: (updatedBy && userDisplay?.display[userDisplay?.keys.indexOf(updatedBy)]?.displayName) || 'Anonymous', + ...appPlatformFolderToLegacyFolder(folder), + }; + + if (parents.length) { + newData.parents = parents + .filter((i) => i.name !== folder.metadata.name) + .map(({ name, title }) => ({ + title: title, + uid: name, + // No idea how to make slug, on the server it uses a go lib: https://github.com/grafana/grafana/blob/aac66e91198004bc044754105e18bfff8fbfd383/pkg/infra/slugify/slugify.go#L56 + // Don't think slug is needed for the URL to work though + url: getFolderUrl(name, title), + })); + } + + return newData; +}; + +export async function getFolderByUidFacade(uid: string): Promise { + const isVirtualFolder = uid && [GENERAL_FOLDER_UID, config.sharedWithMeFolderUID].includes(uid); + // We need the legacy API call regardless, for now + const legacyApiCall = dispatch(browseDashboardsAPI.endpoints.getFolder.initiate(uid)); + + const shouldUseAppPlatformAPI = Boolean(config.featureToggles.foldersAppPlatformAPI); + if (shouldUseAppPlatformAPI) { + let virtualFolderResponse; + if (isVirtualFolder) { + virtualFolderResponse = GENERAL_FOLDER_UID === uid ? rootFolder : sharedWithMeFolder; + } + + const responses = await Promise.all([ + // We still need to call legacy endpoints for access control metadata + legacyApiCall, + isVirtualFolder + ? Promise.resolve({ data: virtualFolderResponse }) + : dispatch(folderAPIv1beta1.endpoints.getFolder.initiate({ name: uid })), + dispatch(folderAPIv1beta1.endpoints.getFolderParents.initiate({ name: uid })), + ]); + + const [legacyFolderResponse, folderResponse, parentsResponse] = responses; + + if (!folderResponse?.data || !legacyFolderResponse?.data || !parentsResponse?.data) { + throw new Error('One of the folder responses is undefined'); + } + + const userKeys = getUserKeys(folderResponse.data); + let userResponse; + if (userKeys.length) { + userResponse = await dispatch(iamAPIv0alpha1.endpoints.getDisplayMapping.initiate({ key: userKeys })); + } + + return combineFolderResponses( + folderResponse.data, + legacyFolderResponse.data, + parentsResponse.data.items, + userResponse?.data + ); + } + + const legacyFolderResponse = await legacyApiCall; + + if (legacyFolderResponse.error || !legacyFolderResponse.data) { + throw legacyFolderResponse.error || new Error('Legacy folder response is undefined'); + } + + return legacyFolderResponse.data; +} + /** * A proxy function that uses either legacy folder client or the new app platform APIs to get the data in the same * format of a FolderDTO object. As the schema isn't the same, using the app platform needs multiple different calls @@ -84,12 +173,12 @@ export function useGetFolderQueryFacade(uid?: string) { const [triggerGetUserDisplayMapping, resultUserDisplay] = useLazyGetDisplayMappingQuery(); const needsUserData = useMemo(() => { - const userKeys = getUserKeys(resultFolder); + const userKeys = getUserKeys(resultFolder.data); return !isVirtualFolder && Boolean(userKeys.length); }, [isVirtualFolder, resultFolder]); useEffect(() => { - const userKeys = getUserKeys(resultFolder); + const userKeys = getUserKeys(resultFolder.data); if (needsUserData && userKeys.length) { triggerGetUserDisplayMapping({ key: userKeys }, true); } @@ -125,39 +214,12 @@ export function useGetFolderQueryFacade(uid?: string) { legacyFolderResult.data && (needsUserData ? resultUserDisplay.data : true) ) { - const updatedBy = resultFolder.data.metadata.annotations?.[AnnoKeyUpdatedBy]; - const createdBy = resultFolder.data.metadata.annotations?.[AnnoKeyCreatedBy]; - - const parsed = appPlatformFolderToLegacyFolder(resultFolder.data); - - newData = { - canAdmin: legacyFolderResult.data.canAdmin, - canDelete: legacyFolderResult.data.canDelete, - canEdit: legacyFolderResult.data.canEdit, - canSave: legacyFolderResult.data.canSave, - accessControl: legacyFolderResult.data.accessControl, - - createdBy: - (createdBy && resultUserDisplay.data?.display[resultUserDisplay.data?.keys.indexOf(createdBy)]?.displayName) || - 'Anonymous', - - updatedBy: - (updatedBy && resultUserDisplay.data?.display[resultUserDisplay.data?.keys.indexOf(updatedBy)]?.displayName) || - 'Anonymous', - ...parsed, - }; - - if (resultParents.data.items?.length) { - newData.parents = resultParents.data.items - .filter((i) => i.name !== resultFolder.data!.metadata.name) - .map((i) => ({ - title: i.title, - uid: i.name, - // No idea how to make slug, on the server it uses a go lib: https://github.com/grafana/grafana/blob/aac66e91198004bc044754105e18bfff8fbfd383/pkg/infra/slugify/slugify.go#L56 - // Don't think slug is needed for the URL to work though - url: getFolderUrl(i.name, i.title), - })); - } + newData = combineFolderResponses( + resultFolder.data, + legacyFolderResult.data, + resultParents.data.items, + resultUserDisplay.data + ); } // Wrap the stitched data into single RTK query response type object so this looks like a single API call @@ -440,13 +502,10 @@ function combinedState( }; } -function getUserKeys(resultFolder: ReturnType): string[] { - return resultFolder.data - ? [ - resultFolder.data.metadata.annotations?.[AnnoKeyUpdatedBy], - resultFolder.data.metadata.annotations?.[AnnoKeyCreatedBy], - ].filter((v) => v !== undefined) - : []; +function getUserKeys(folder?: Folder): string[] { + return [folder?.metadata.annotations?.[AnnoKeyUpdatedBy], folder?.metadata.annotations?.[AnnoKeyCreatedBy]].filter( + (v) => v !== undefined + ); } const appPlatformFolderToLegacyFolder = ( diff --git a/public/app/api/clients/folder/v1beta1/index.ts b/public/app/api/clients/folder/v1beta1/index.ts index d701554cce7..13d2fa05791 100644 --- a/public/app/api/clients/folder/v1beta1/index.ts +++ b/public/app/api/clients/folder/v1beta1/index.ts @@ -80,9 +80,4 @@ export const { } = folderAPIv1beta1; // eslint-disable-next-line no-barrel-files/no-barrel-files -export { - type Folder, - type FolderList, - type CreateFolderApiArg, - type ReplaceFolderApiArg, -} from '@grafana/api-clients/rtkq/folder/v1beta1'; +export * from '@grafana/api-clients/rtkq/folder/v1beta1'; diff --git a/public/app/api/clients/iam/v0alpha1/index.ts b/public/app/api/clients/iam/v0alpha1/index.ts index 3f9e7b1fb6d..190f7ede868 100644 --- a/public/app/api/clients/iam/v0alpha1/index.ts +++ b/public/app/api/clients/iam/v0alpha1/index.ts @@ -3,3 +3,6 @@ import { generatedAPI } from '@grafana/api-clients/rtkq/iam/v0alpha1'; export const iamAPIv0alpha1 = generatedAPI.enhanceEndpoints({}); export const { useGetDisplayMappingQuery, useLazyGetDisplayMappingQuery } = iamAPIv0alpha1; + +// eslint-disable-next-line no-barrel-files/no-barrel-files +export type { DisplayList } from '@grafana/api-clients/rtkq/iam/v0alpha1'; diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 193a03a6847..2f6aed7e115 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -654,7 +654,12 @@ export class BackendSrv implements BackendService { return this.get(`/api/public/dashboards/${uid}`); } + /** + * @deprecated Use getFolderByUidFacade from app/api/clients/folder/v1beta1/hooks instead + * or manually handle calling legacy vs app platform API based on feature toggles + */ getFolderByUid(uid: string, options: FolderRequestOptions = {}) { + deprecationWarning('backend_srv', 'getFolderByUid(uid)', 'getFolderByUidFacade(uid)'); const queryParams = new URLSearchParams(); if (options.withAccessControl) { queryParams.set('accesscontrol', 'true'); diff --git a/public/app/features/alerting/unified/components/rules/deleted-rules/ConfirmRestoreDeletedRuleModal.tsx b/public/app/features/alerting/unified/components/rules/deleted-rules/ConfirmRestoreDeletedRuleModal.tsx index 82bf0d1ab1b..d477180e21c 100644 --- a/public/app/features/alerting/unified/components/rules/deleted-rules/ConfirmRestoreDeletedRuleModal.tsx +++ b/public/app/features/alerting/unified/components/rules/deleted-rules/ConfirmRestoreDeletedRuleModal.tsx @@ -4,7 +4,7 @@ import { ComponentProps } from 'react'; import { Trans, t } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; import { Alert, CodeEditor, ConfirmModal, Stack, useStyles2 } from '@grafana/ui'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { useGetFolderQueryFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { getMessageFromError } from 'app/core/utils/errors'; import { useAsync } from 'app/features/alerting/unified/hooks/useAsync'; import { GrafanaRuleDefinition, RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; @@ -32,6 +32,7 @@ export const ConfirmRestoreDeletedRuleModal = ({ onRestoreError, }: ModalProps) => { const [restoreMethod, { error }] = useRestoreDeletedRule(); + const { data: folder } = useGetFolderQueryFacade(ruleToRestore?.grafana_alert.namespace_uid); const title = t('alerting.deleted-rules.restore-modal.title', 'Restore deleted alert rule'); const errorTitle = t('alerting.deleted-rules.restore-modal.error', 'Could not restore deleted alert rule'); @@ -60,7 +61,7 @@ export const ConfirmRestoreDeletedRuleModal = ({ if (!ruleToRestore) { return; } - await redirectToRestoreForm(ruleToRestore); + await redirectToRestoreForm(ruleToRestore, folder?.title ?? ''); } return ( @@ -141,11 +142,8 @@ export function useRestoreDeletedRule() { }); } -const redirectToRestoreForm = async (ruleToRecover: RulerGrafanaRuleDTO) => { +const redirectToRestoreForm = async (ruleToRecover: RulerGrafanaRuleDTO, namespaceName: string) => { let formValues: Partial | undefined; - const namespaceName = await backendSrv - .getFolderByUid(ruleToRecover.grafana_alert.namespace_uid) - .then((folder) => folder.title); try { formValues = grafanaRuleDtoToFormValues(ruleToRecover, namespaceName); diff --git a/public/app/features/dashboard-scene/pages/utils.test.ts b/public/app/features/dashboard-scene/pages/utils.test.ts index 416181dee15..0edcf14bea5 100644 --- a/public/app/features/dashboard-scene/pages/utils.test.ts +++ b/public/app/features/dashboard-scene/pages/utils.test.ts @@ -1,4 +1,4 @@ -import { backendSrv } from 'app/core/services/backend_srv'; +import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks'; import { configureStore } from 'app/store/configureStore'; import { updateNavModel } from './utils'; @@ -7,7 +7,7 @@ describe('utils', () => { it('Should update nav model', async () => { const reduxStore = configureStore(); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValue({ id: 1, uid: 'new-folder', title: 'NewFolder', diff --git a/public/app/features/dashboard-scene/pages/utils.ts b/public/app/features/dashboard-scene/pages/utils.ts index d540e8fbdb5..6e786123a87 100644 --- a/public/app/features/dashboard-scene/pages/utils.ts +++ b/public/app/features/dashboard-scene/pages/utils.ts @@ -1,13 +1,13 @@ import { UrlQueryMap, getTimeZone, getDefaultTimeRange, dateMath } from '@grafana/data'; import { locationService } from '@grafana/runtime'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { updateNavIndex } from 'app/core/actions'; -import { backendSrv } from 'app/core/services/backend_srv'; import { buildNavModel } from 'app/features/folders/state/navModel'; import { store } from 'app/store/store'; export async function updateNavModel(folderUid: string) { try { - const folder = await backendSrv.getFolderByUid(folderUid); + const folder = await getFolderByUidFacade(folderUid); store.dispatch(updateNavIndex(buildNavModel(folder))); } catch (err) { console.warn('Error fetching parent folder', folderUid, 'for dashboard', err); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx index 06573cc06ef..15e323aa8a9 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx @@ -4,7 +4,6 @@ import { byTestId } from 'testing-library-selector'; import { PromOptions } from '@grafana/prometheus'; import { config, locationService, setPluginLinksHook } from '@grafana/runtime'; -import { backendSrv } from 'app/core/services/backend_srv'; import * as ruler from 'app/features/alerting/unified/api/ruler'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import * as alertingAbilities from 'app/features/alerting/unified/hooks/useAbilities'; @@ -12,7 +11,6 @@ import { mockAlertRuleApi, setupMswServer } from 'app/features/alerting/unified/ import { grantUserPermissions, mockDataSource, - mockFolder, mockPromAlert, mockPromAlertingRule, mockRulerAlertingRule, @@ -190,7 +188,6 @@ describe('PanelAlertTabContent', () => { AccessControlAction.AlertingRuleExternalWrite, ]); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue(mockFolder()); setupDataSources(...Object.values(dataSources)); mocks.rulerBuilderMock.mockReturnValue({ diff --git a/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts b/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts index 737fda747ab..0476e591ddb 100644 --- a/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts +++ b/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts @@ -89,12 +89,7 @@ describe('UnifiedDashboardAPI', () => { }; v1Client.getDashboardDTO.mockRejectedValue(new DashboardVersionError('v2beta1', 'Dashboard is V1 format')); - v2Client.getDashboardDTO.mockImplementation((params) => { - const actualClient = jest.requireActual('./v2').K8sDashboardV2API; - const client = new actualClient(); - return client.getDashboardDTO(params); - }); - mockBackendSrvGet = mockV2Response; + v2Client.getDashboardDTO.mockResolvedValue(mockV2Response as DashboardWithAccessInfo); const result = await api.getDashboardDTO('123'); expect(result).toEqual(mockV2Response); expect(v2Client.getDashboardDTO).toHaveBeenCalledWith('123'); diff --git a/public/app/features/dashboard/api/v1.test.ts b/public/app/features/dashboard/api/v1.test.ts index edf419685ae..433c74b99c0 100644 --- a/public/app/features/dashboard/api/v1.test.ts +++ b/public/app/features/dashboard/api/v1.test.ts @@ -1,4 +1,5 @@ import { GrafanaConfig, locationUtil } from '@grafana/data'; +import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks'; import { backendSrv } from 'app/core/services/backend_srv'; import { AnnoKeyFolder, AnnoKeyMessage, AnnoReloadOnParamsChange } from 'app/features/apiserver/types'; import { DashboardDataDTO } from 'app/types/dashboard'; @@ -128,7 +129,7 @@ describe('v1 dashboard API', () => { }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValueOnce({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValueOnce({ id: 1, uid: 'new-folder', title: 'New Folder', @@ -174,7 +175,7 @@ describe('v1 dashboard API', () => { }, }); jest - .spyOn(backendSrv, 'getFolderByUid') + .spyOn(folderHooks, 'getFolderByUidFacade') .mockRejectedValueOnce({ message: 'folder not found', status: 'not-found' }); const api = new K8sDashboardAPI(); @@ -186,7 +187,7 @@ describe('v1 dashboard API', () => { ...mockDashboardDto, metadata: { ...mockDashboardDto.metadata, annotations: { [AnnoKeyFolder]: 'new-folder' } }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); const api = new K8sDashboardAPI(); const dashboardDTO = await api.getDashboardDTO('test'); diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index 8761afd48b2..b8c57653923 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -2,7 +2,7 @@ import { locationUtil, UrlQueryMap } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Dashboard } from '@grafana/schema'; import { Status } from '@grafana/schema/src/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { getMessageFromError, getStatusFromError } from 'app/core/utils/errors'; import { ScopedResourceClient } from 'app/features/apiserver/client'; import { @@ -166,7 +166,7 @@ export class K8sDashboardAPI implements DashboardAPI { if (dash.metadata.annotations?.[AnnoKeyFolder]) { try { - const folder = await backendSrv.getFolderByUid(dash.metadata.annotations[AnnoKeyFolder]); + const folder = await getFolderByUidFacade(dash.metadata.annotations[AnnoKeyFolder]); result.meta.folderTitle = folder.title; result.meta.folderUrl = folder.url; result.meta.folderUid = folder.uid; diff --git a/public/app/features/dashboard/api/v2.test.ts b/public/app/features/dashboard/api/v2.test.ts index 8556b765702..58242c07c10 100644 --- a/public/app/features/dashboard/api/v2.test.ts +++ b/public/app/features/dashboard/api/v2.test.ts @@ -2,7 +2,7 @@ import { Spec as DashboardV2Spec, defaultSpec as defaultDashboardV2Spec, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks'; import { AnnoKeyFolder, AnnoKeyFolderTitle, @@ -101,7 +101,7 @@ describe('v2 dashboard API', () => { }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValue({ id: 1, uid: 'new-folder', title: 'New Folder', @@ -136,7 +136,7 @@ describe('v2 dashboard API', () => { }, }); jest - .spyOn(backendSrv, 'getFolderByUid') + .spyOn(folderHooks, 'getFolderByUidFacade') .mockRejectedValueOnce({ message: 'folder not found', status: 'not-found' }); const api = new K8sDashboardV2API(); @@ -148,7 +148,7 @@ describe('v2 dashboard API', () => { ...mockDashboardDto, metadata: { ...mockDashboardDto.metadata, annotations: { [AnnoKeyFolder]: 'new-folder' } }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); const api = new K8sDashboardV2API(); const dashboardDTO = await api.getDashboardDTO('test'); diff --git a/public/app/features/dashboard/api/v2.ts b/public/app/features/dashboard/api/v2.ts index dfcbcc638f5..33ec2323786 100644 --- a/public/app/features/dashboard/api/v2.ts +++ b/public/app/features/dashboard/api/v2.ts @@ -2,7 +2,7 @@ import { locationUtil } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { Status } from '@grafana/schema/src/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { getMessageFromError, getStatusFromError } from 'app/core/utils/errors'; import { ScopedResourceClient } from 'app/features/apiserver/client'; import { @@ -59,7 +59,7 @@ export class K8sDashboardV2API // load folder info if available if (dashboard.metadata.annotations && dashboard.metadata.annotations[AnnoKeyFolder]) { try { - const folder = await backendSrv.getFolderByUid(dashboard.metadata.annotations[AnnoKeyFolder]); + const folder = await getFolderByUidFacade(dashboard.metadata.annotations[AnnoKeyFolder]); dashboard.metadata.annotations[AnnoKeyFolderTitle] = folder.title; dashboard.metadata.annotations[AnnoKeyFolderUrl] = folder.url; } catch (e) { diff --git a/public/app/features/search/loaders.ts b/public/app/features/search/loaders.ts deleted file mode 100644 index 2ff2312fbb9..00000000000 --- a/public/app/features/search/loaders.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { backendSrv } from 'app/core/services/backend_srv'; - -import { buildNavModel } from '../folders/state/navModel'; - -export const loadFolderPage = (uid: string) => { - return backendSrv.getFolderByUid(uid, { withAccessControl: true }).then((folder) => { - const navModel = buildNavModel(folder); - navModel.children![0].active = true; - - return { folder, folderNav: navModel }; - }); -}; diff --git a/public/app/types/store.ts b/public/app/types/store.ts index 116b2b743dd..e9c2881013a 100644 --- a/public/app/types/store.ts +++ b/public/app/types/store.ts @@ -18,6 +18,7 @@ import { ThunkAction, ThunkDispatch as GenericThunkDispatch } from 'redux-thunk' import type { createRootReducer } from 'app/core/reducers/root'; import { AppDispatch, RootState } from 'app/store/configureStore'; +import { dispatch as storeDispatch } from 'app/store/store'; export type StoreState = ReturnType>; @@ -41,3 +42,4 @@ export const createAsyncThunk = (typePrefix, payloadCreator, options); export const addListener = addListenerUntyped as TypedAddListener; +export const dispatch: AppDispatch = storeDispatch;