mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: Paginate nested folder picker (#71489)
* Paginate nested folder picker items * Prevent dashboards from appearing in the folder picker * fix tests * remove SkeletonGroup * more excludeKinds * cleanup
This commit is contained in:
@@ -3,11 +3,15 @@ import { DashboardViewItem, DashboardViewItemKind } from 'app/features/search/ty
|
||||
import { createAsyncThunk } from 'app/types';
|
||||
|
||||
import { listDashboards, listFolders, PAGE_SIZE } from '../api/services';
|
||||
import { DashboardViewItemWithUIItems, UIDashboardViewItem } from '../types';
|
||||
|
||||
import { findItem } from './utils';
|
||||
|
||||
interface FetchNextChildrenPageArgs {
|
||||
parentUID: string | undefined;
|
||||
|
||||
// Allow UI items to be excluded (they're always excluded) for convenience for callers
|
||||
excludeKinds?: Array<DashboardViewItemWithUIItems['kind'] | UIDashboardViewItem['uiKind']>;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@@ -87,9 +91,12 @@ export const refetchChildren = createAsyncThunk(
|
||||
export const fetchNextChildrenPage = createAsyncThunk(
|
||||
'browseDashboards/fetchNextChildrenPage',
|
||||
async (
|
||||
{ parentUID, pageSize }: FetchNextChildrenPageArgs,
|
||||
{ parentUID, excludeKinds = [], pageSize }: FetchNextChildrenPageArgs,
|
||||
thunkAPI
|
||||
): Promise<undefined | FetchNextChildrenPageResult> => {
|
||||
// TODO: invert prop to `includeKinds`, but also support not loading folders
|
||||
const loadDashboards = !excludeKinds.includes('dashboard');
|
||||
|
||||
const uid = parentUID === GENERAL_FOLDER_UID ? undefined : parentUID;
|
||||
|
||||
const state = thunkAPI.getState().browseDashboards;
|
||||
@@ -107,13 +114,13 @@ export const fetchNextChildrenPage = createAsyncThunk(
|
||||
fetchKind = 'folder';
|
||||
} else if (collection.lastFetchedKind === 'dashboard' && !collection.lastKindHasMoreItems) {
|
||||
// There's nothing to load at all
|
||||
console.warn(`FetchedChildren called for ${uid} but that collection is fully loaded`);
|
||||
console.warn(`fetchNextChildrenPage called for ${uid} but that collection is fully loaded`);
|
||||
// return;
|
||||
} else if (collection.lastFetchedKind === 'folder' && collection.lastKindHasMoreItems) {
|
||||
// Load additional pages of folders
|
||||
page = collection.lastFetchedPage + 1;
|
||||
fetchKind = 'folder';
|
||||
} else {
|
||||
} else if (loadDashboards) {
|
||||
// We've already checked if there's more folders to load, so if the last fetched is folder
|
||||
// then we fetch first page of dashboards
|
||||
page = collection.lastFetchedKind === 'folder' ? 1 : collection.lastFetchedPage + 1;
|
||||
@@ -133,7 +140,7 @@ export const fetchNextChildrenPage = createAsyncThunk(
|
||||
|
||||
// If we've loaded all folders, load the first page of dashboards.
|
||||
// This ensures dashboards are loaded if a folder contains only dashboards.
|
||||
if (fetchKind === 'folder' && lastPageOfKind) {
|
||||
if (fetchKind === 'folder' && lastPageOfKind && loadDashboards) {
|
||||
fetchKind = 'dashboard';
|
||||
page = 1;
|
||||
|
||||
|
||||
@@ -5,9 +5,16 @@ import { DashboardViewItem } from 'app/features/search/types';
|
||||
import { useSelector, StoreState, useDispatch } from 'app/types';
|
||||
|
||||
import { PAGE_SIZE } from '../api/services';
|
||||
import { BrowseDashboardsState, DashboardsTreeItem, DashboardTreeSelection } from '../types';
|
||||
import {
|
||||
BrowseDashboardsState,
|
||||
DashboardsTreeItem,
|
||||
DashboardTreeSelection,
|
||||
DashboardViewItemWithUIItems,
|
||||
UIDashboardViewItem,
|
||||
} from '../types';
|
||||
|
||||
import { fetchNextChildrenPage } from './actions';
|
||||
import { getPaginationPlaceholders } from './utils';
|
||||
|
||||
export const rootItemsSelector = (wholeState: StoreState) => wholeState.browseDashboards.rootItems;
|
||||
export const childrenByParentUIDSelector = (wholeState: StoreState) => wholeState.browseDashboards.childrenByParentUID;
|
||||
@@ -97,7 +104,9 @@ export function useActionSelectionState() {
|
||||
return useSelector((state) => selectedItemsForActionsSelector(state));
|
||||
}
|
||||
|
||||
export function useLoadNextChildrenPage() {
|
||||
export function useLoadNextChildrenPage(
|
||||
excludeKinds: Array<DashboardViewItemWithUIItems['kind'] | UIDashboardViewItem['uiKind']> = []
|
||||
) {
|
||||
const dispatch = useDispatch();
|
||||
const requestInFlightRef = useRef(false);
|
||||
|
||||
@@ -109,12 +118,12 @@ export function useLoadNextChildrenPage() {
|
||||
|
||||
requestInFlightRef.current = true;
|
||||
|
||||
const promise = dispatch(fetchNextChildrenPage({ parentUID: folderUID, pageSize: PAGE_SIZE }));
|
||||
const promise = dispatch(fetchNextChildrenPage({ parentUID: folderUID, excludeKinds, pageSize: PAGE_SIZE }));
|
||||
promise.finally(() => (requestInFlightRef.current = false));
|
||||
|
||||
return promise;
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, excludeKinds]
|
||||
);
|
||||
|
||||
return handleLoadMore;
|
||||
@@ -135,21 +144,25 @@ export function createFlatTree(
|
||||
childrenByUID: BrowseDashboardsState['childrenByParentUID'],
|
||||
openFolders: Record<string, boolean>,
|
||||
level = 0,
|
||||
insertEmptyFolderIndicator = true
|
||||
excludeKinds: Array<DashboardViewItemWithUIItems['kind'] | UIDashboardViewItem['uiKind']> = []
|
||||
): DashboardsTreeItem[] {
|
||||
function mapItem(item: DashboardViewItem, parentUID: string | undefined, level: number): DashboardsTreeItem[] {
|
||||
if (excludeKinds.includes(item.kind)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const mappedChildren = createFlatTree(
|
||||
item.uid,
|
||||
rootCollection,
|
||||
childrenByUID,
|
||||
openFolders,
|
||||
level + 1,
|
||||
insertEmptyFolderIndicator
|
||||
excludeKinds
|
||||
);
|
||||
|
||||
const isOpen = Boolean(openFolders[item.uid]);
|
||||
const emptyFolder = childrenByUID[item.uid]?.items.length === 0;
|
||||
if (isOpen && emptyFolder && insertEmptyFolderIndicator) {
|
||||
if (isOpen && emptyFolder && !excludeKinds.includes('empty-folder')) {
|
||||
mappedChildren.push({
|
||||
isOpen: false,
|
||||
level: level + 1,
|
||||
@@ -186,18 +199,3 @@ export function createFlatTree(
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
function getPaginationPlaceholders(amount: number, parentUID: string | undefined, level: number) {
|
||||
return new Array(amount).fill(null).map((_, index) => {
|
||||
return {
|
||||
parentUID,
|
||||
level,
|
||||
isOpen: false,
|
||||
item: {
|
||||
kind: 'ui' as const,
|
||||
uiKind: 'pagination-placeholder' as const,
|
||||
uid: `${parentUID}-pagination-${index}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function fetchNextChildrenPageFulfilled(
|
||||
}
|
||||
|
||||
const { children, page, kind, lastPageOfKind } = payload;
|
||||
const { parentUID } = action.meta.arg;
|
||||
const { parentUID, excludeKinds = [] } = action.meta.arg;
|
||||
|
||||
const collection = parentUID ? state.childrenByParentUID[parentUID] : state.rootItems;
|
||||
const prevItems = collection?.items ?? [];
|
||||
@@ -50,7 +50,7 @@ export function fetchNextChildrenPageFulfilled(
|
||||
lastFetchedKind: kind,
|
||||
lastFetchedPage: page,
|
||||
lastKindHasMoreItems: !lastPageOfKind,
|
||||
isFullyLoaded: kind === 'dashboard' && lastPageOfKind,
|
||||
isFullyLoaded: !excludeKinds.includes('dashboard') ? kind === 'dashboard' && lastPageOfKind : lastPageOfKind,
|
||||
};
|
||||
|
||||
if (!parentUID) {
|
||||
|
||||
@@ -28,3 +28,18 @@ export function findItem(
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPaginationPlaceholders(amount: number, parentUID: string | undefined, level: number) {
|
||||
return new Array(amount).fill(null).map((_, index) => {
|
||||
return {
|
||||
parentUID,
|
||||
level,
|
||||
isOpen: false,
|
||||
item: {
|
||||
kind: 'ui' as const,
|
||||
uiKind: 'pagination-placeholder' as const,
|
||||
uid: `${parentUID}-pagination-${index}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface UIDashboardViewItem {
|
||||
uid: string;
|
||||
}
|
||||
|
||||
type DashboardViewItemWithUIItems = DashboardViewItem | UIDashboardViewItem;
|
||||
export type DashboardViewItemWithUIItems = DashboardViewItem | UIDashboardViewItem;
|
||||
|
||||
export interface DashboardsTreeItem<T extends DashboardViewItemWithUIItems = DashboardViewItemWithUIItems> {
|
||||
item: T;
|
||||
|
||||
Reference in New Issue
Block a user