diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index 0ab1c1d8d91..5a4acea4d42 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -39,6 +39,9 @@ export interface NestedFolderPickerProps { /* Start tree from this folder instead of root */ rootFolderUID?: string; + /* Custom root folder item, default is "Dashboards" */ + rootFolderItem?: DashboardsTreeItem; + /* Show folders matching this permission, mainly used to also show folders user can view. Defaults to showing only folders user has Edit */ permission?: 'view' | 'edit'; @@ -73,6 +76,7 @@ export function NestedFolderPicker({ clearable = false, excludeUIDs, rootFolderUID, + rootFolderItem, permission = 'edit', onChange, id, @@ -110,7 +114,13 @@ export function NestedFolderPicker({ items: browseFlatTree, isLoading: isBrowseLoading, requestNextPage: fetchFolderPage, - } = useFoldersQuery(isBrowsing, foldersOpenState, permissionLevel, rootFolderUID); + } = useFoldersQuery({ + isBrowsing, + openFolders: foldersOpenState, + permission: permissionLevel, + rootFolderUID, + rootFolderItem, + }); useEffect(() => { if (!search) { diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx index 23460388f4e..534c175d436 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx +++ b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx @@ -5,11 +5,12 @@ import * as runtime from '@grafana/runtime'; import { setupMockServer } from '@grafana/test-utils/server'; import { getFolderFixtures } from '@grafana/test-utils/unstable'; import { backendSrv } from 'app/core/services/backend_srv'; +import { ManagerKind } from 'app/features/apiserver/types'; import { DashboardViewItem } from '../../../features/search/types'; import { useFoldersQuery } from './useFoldersQuery'; -import { getRootFolderItem } from './utils'; +import { getCustomRootFolderItem, getRootFolderItem } from './utils'; const [_, { folderA, folderB, folderC }] = getFolderFixtures(); @@ -47,11 +48,42 @@ describe('useFoldersQuery', () => { expect(sortedItemTitles).toEqual(expectedTitles); }); + + it('uses custom root folder display name when rootFolderItem is provided', async () => { + runtime.config.featureToggles.foldersAppPlatformAPI = featureToggleState; + const { result } = renderHook( + () => + useFoldersQuery({ + isBrowsing: true, + openFolders: {}, + rootFolderItem: getCustomRootFolderItem({ + title: 'Test Repo', + managedBy: ManagerKind.Repo, + }), + }), + { wrapper } + ); + + // Test that root folder item uses the custom display name + expect(result.current.items[0]).toEqual( + getCustomRootFolderItem({ + title: 'Test Repo', + managedBy: ManagerKind.Repo, + }) + ); + }); }); }); async function testFn() { - const { result } = renderHook(() => useFoldersQuery(true, {}), { wrapper }); + const { result } = renderHook( + () => + useFoldersQuery({ + isBrowsing: true, + openFolders: {}, + }), + { wrapper } + ); expect(result.current.items[0]).toEqual(getRootFolderItem()); expect(result.current.isLoading).toBe(false); diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQuery.ts b/public/app/core/components/NestedFolderPicker/useFoldersQuery.ts index ee96989b9a8..a85bbf09d8a 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQuery.ts +++ b/public/app/core/components/NestedFolderPicker/useFoldersQuery.ts @@ -1,18 +1,28 @@ import { config } from '@grafana/runtime'; +import { DashboardsTreeItem } from 'app/features/browse-dashboards/types'; import { PermissionLevelString } from 'app/types/acl'; import { useFoldersQueryAppPlatform } from './useFoldersQueryAppPlatform'; import { useFoldersQueryLegacy } from './useFoldersQueryLegacy'; -export function useFoldersQuery( - isBrowsing: boolean, - openFolders: Record, - permission?: PermissionLevelString, +export interface UseFoldersQueryProps { + isBrowsing: boolean; + openFolders: Record; + permission?: PermissionLevelString; + rootFolderUID?: string; + rootFolderItem?: DashboardsTreeItem; +} + +export function useFoldersQuery({ + isBrowsing, + openFolders, + permission, /* Start tree from this folder instead of root */ - rootFolderUID?: string -) { - const resultLegacy = useFoldersQueryLegacy(isBrowsing, openFolders, permission, rootFolderUID); - const resultAppPlatform = useFoldersQueryAppPlatform(isBrowsing, openFolders, rootFolderUID); + rootFolderUID, + rootFolderItem, +}: UseFoldersQueryProps) { + const resultLegacy = useFoldersQueryLegacy({ isBrowsing, openFolders, permission, rootFolderUID, rootFolderItem }); + const resultAppPlatform = useFoldersQueryAppPlatform({ isBrowsing, openFolders, rootFolderUID, rootFolderItem }); // Running the hooks themselves don't have any side effects, so we can just conditionally use one or the other // requestNextPage function from the result diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts index b1541cbfcd2..86ebf294413 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts +++ b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts @@ -10,6 +10,7 @@ import { AnnoKeyManagerKind, ManagerKind } from '../../../features/apiserver/typ import { PAGE_SIZE } from '../../../features/browse-dashboards/api/services'; import { getPaginationPlaceholders } from '../../../features/browse-dashboards/state/utils'; +import { UseFoldersQueryProps } from './useFoldersQuery'; import { getRootFolderItem } from './utils'; type GetFolderChildrenQuery = ReturnType>; @@ -25,12 +26,15 @@ const collator = new Intl.Collator(); * This version uses the getFolderChildren API from the folder v1beta1 API. Compared to legacy API, the v1beta1 API * does not have pagination at the moment. */ -export function useFoldersQueryAppPlatform( - isBrowsing: boolean, - openFolders: Record, + +type Props = Omit; +export function useFoldersQueryAppPlatform({ + isBrowsing, + openFolders, /* rootFolderUID: configure which folder to start browsing from */ - rootFolderUID?: string -) { + rootFolderUID, + rootFolderItem, +}: Props) { const dispatch = useDispatch(); // Keep a list of all request subscriptions so we can unsubscribe from them when the component is unmounted @@ -157,10 +161,10 @@ export function useFoldersQueryAppPlatform( const startingToken = rootFolderUID ?? rootFolderToken; const rootFlatTree = createFlatList(startingToken, state.responseByParent[startingToken], 1); - rootFlatTree.unshift(getRootFolderItem()); + rootFlatTree.unshift(rootFolderItem || getRootFolderItem()); return rootFlatTree; - }, [state, isBrowsing, openFolders, rootFolderUID]); + }, [state, isBrowsing, openFolders, rootFolderUID, rootFolderItem]); return { items: treeList, diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts b/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts index 52e33a538d7..a80b22ac254 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts +++ b/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts @@ -7,10 +7,10 @@ import { ListFolderQueryArgs, browseDashboardsAPI } from 'app/features/browse-da import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services'; import { getPaginationPlaceholders } from 'app/features/browse-dashboards/state/utils'; import { DashboardViewItemWithUIItems, DashboardsTreeItem } from 'app/features/browse-dashboards/types'; -import { PermissionLevelString } from 'app/types/acl'; import { FolderListItemDTO } from 'app/types/folders'; import { useDispatch, useSelector } from 'app/types/store'; +import { UseFoldersQueryProps } from './useFoldersQuery'; import { getRootFolderItem } from './utils'; type ListFoldersQuery = ReturnType>; @@ -45,13 +45,14 @@ function getPagesLoadStatus(pages: ListFoldersQuery[]): [boolean, number | undef /** * Returns a loaded folder hierarchy as a flat list and a function to load more pages. */ -export function useFoldersQueryLegacy( - isBrowsing: boolean, - openFolders: Record, - permission?: PermissionLevelString, +export function useFoldersQueryLegacy({ + isBrowsing, + openFolders, + permission, /* rootFolderUID: configure which folder to start browsing from */ - rootFolderUID?: string -) { + rootFolderUID, + rootFolderItem, +}: UseFoldersQueryProps) { const dispatch = useDispatch(); // Keep a list of all request subscriptions so we can unsubscribe from them when the component is unmounted @@ -183,10 +184,10 @@ export function useFoldersQueryLegacy( const startingPages = rootFolderUID ? state.pagesByParent[rootFolderUID] : state.rootPages; const rootFlatTree = createFlatList(rootFolderUID ?? undefined, startingPages ?? [], 1); - rootFlatTree.unshift(getRootFolderItem()); + rootFlatTree.unshift(rootFolderItem || getRootFolderItem()); return rootFlatTree; - }, [state, isBrowsing, openFolders, rootFolderUID]); + }, [state, isBrowsing, openFolders, rootFolderUID, rootFolderItem]); return { items: treeList, diff --git a/public/app/core/components/NestedFolderPicker/utils.ts b/public/app/core/components/NestedFolderPicker/utils.ts index 1d92fc57f00..d6b9ccfd914 100644 --- a/public/app/core/components/NestedFolderPicker/utils.ts +++ b/public/app/core/components/NestedFolderPicker/utils.ts @@ -1,6 +1,8 @@ import { t } from '@grafana/i18n'; +import { ManagerKind } from 'app/features/apiserver/types'; +import { DashboardsTreeItem } from 'app/features/browse-dashboards/types'; -export const getRootFolderItem = () => ({ +export const getRootFolderItem = (): DashboardsTreeItem => ({ isOpen: true, level: 0, item: { @@ -9,3 +11,22 @@ export const getRootFolderItem = () => ({ uid: '', }, }); + +export const getCustomRootFolderItem = ({ + title, + managedBy, + uid, +}: { + title: string; + managedBy?: ManagerKind; + uid?: string; +}): DashboardsTreeItem => ({ + isOpen: true, + level: 0, + item: { + kind: 'folder' as const, + title, + uid: uid || '', + managedBy, + }, +});