grafana/public/app/features/search/page/components/RootFolderView.tsx
Josh Hunt d13488a435
NestedFolders: Show nested folders in Browse folder view (#63746)
* dirty dirty code for showing nested folders in folder view

refactor to NestedFolderItem

Update dashboard grid view to new types

update tests

REBASE OUT OF THIS BRANCH - joshhunt/star-by-uid merged into this

Squashed commit of the following:

commit d0f046ccd3
Author: joshhunt <josh@trtr.co>
Date:   Wed Feb 8 18:35:56 2023 +0000

    undo async

commit abe2777a1f
Author: joshhunt <josh@trtr.co>
Date:   Wed Feb 8 18:34:11 2023 +0000

    Dashboards: Star dashboards by UID

add type for dashboard search dto

clean DashboardSearchItem type

simplify DashboardSearchHit type

remove unused properties from DashboardSearchHit

make uid non-optional

rename + move NestedFolderItem type to DashboardViewItem

clean up

* wip

* fix checkbox selection of nested folders

* show folder's parent correctly

* Add dashboard result kind

* don't render folder empty view in SearchView

* call nested folders api only if feature flag enabled

* remove unused import

* un-rename variable to reduce PR diff

* fix typo in comment

* fix order of pseudoFolders

* Fix General folder not showing in browse

* rename folder view tests

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-03-23 13:28:45 +00:00

132 lines
3.7 KiB
TypeScript

import { css } from '@emotion/css';
import React from 'react';
import { useAsync } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getBackendSrv } from '@grafana/runtime';
import { Alert, Spinner, useStyles2 } from '@grafana/ui';
import config from 'app/core/config';
import { contextSrv } from '../../../../core/services/context_srv';
import impressionSrv from '../../../../core/services/impression_srv';
import { GENERAL_FOLDER_UID } from '../../constants';
import { getGrafanaSearcher } from '../../service';
import { getFolderChildren } from '../../service/folders';
import { queryResultToViewItem } from '../../service/utils';
import { FolderSection } from './FolderSection';
import { SearchResultsProps } from './SearchResultsTable';
async function getChildren() {
if (config.featureToggles.nestedFolders) {
return getFolderChildren();
}
const searcher = getGrafanaSearcher();
const results = await searcher.search({
query: '*',
kind: ['folder'],
sort: searcher.getFolderViewSort(),
limit: 1000,
});
return results.view.map((v) => queryResultToViewItem(v, results.view));
}
type Props = Pick<SearchResultsProps, 'selection' | 'selectionToggle' | 'onTagSelected' | 'onClickItem'> & {
tags?: string[];
hidePseudoFolders?: boolean;
};
export const RootFolderView = ({
selection,
selectionToggle,
onTagSelected,
tags,
hidePseudoFolders,
onClickItem,
}: Props) => {
const styles = useStyles2(getStyles);
const results = useAsync(async () => {
const folders = await getChildren();
folders.unshift({ title: 'General', url: '/dashboards', kind: 'folder', uid: GENERAL_FOLDER_UID });
if (!hidePseudoFolders) {
const itemsUIDs = await impressionSrv.getDashboardOpened();
if (itemsUIDs.length) {
folders.unshift({ title: 'Recent', icon: 'clock-nine', kind: 'folder', uid: '__recent', itemsUIDs });
}
if (contextSrv.isSignedIn) {
const stars = await getBackendSrv().get('api/user/stars');
if (stars.length > 0) {
folders.unshift({ title: 'Starred', icon: 'star', kind: 'folder', uid: '__starred', itemsUIDs: stars });
}
}
}
return folders;
}, []);
const renderResults = () => {
if (results.loading) {
return <Spinner className={styles.spinner} />;
} else if (!results.value) {
return <Alert className={styles.error} title={results.error ? results.error.message : 'Something went wrong'} />;
} else {
return results.value.map((section) => (
<div data-testid={selectors.components.Search.sectionV2} className={styles.section} key={section.title}>
{section.title && (
<FolderSection
selection={selection}
selectionToggle={selectionToggle}
onTagSelected={onTagSelected}
section={section}
tags={tags}
onClickItem={onClickItem}
/>
)}
</div>
));
}
};
return <div className={styles.wrapper}>{renderResults()}</div>;
};
const getStyles = (theme: GrafanaTheme2) => {
return {
wrapper: css`
display: flex;
flex-direction: column;
overflow: auto;
> ul {
list-style: none;
}
border: solid 1px ${theme.v1.colors.border2};
`,
section: css`
display: flex;
flex-direction: column;
background: ${theme.v1.colors.panelBg};
&:not(:last-child) {
border-bottom: solid 1px ${theme.v1.colors.border2};
}
`,
spinner: css`
align-items: center;
display: flex;
justify-content: center;
min-height: 100px;
`,
error: css`
margin: ${theme.spacing(4)} auto;
`,
};
};