mirror of
https://github.com/grafana/grafana.git
synced 2025-09-23 09:13:32 +08:00

* 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 d0f046ccd3d820575f58d9a60cfcedf5cbdb217d Author: joshhunt <josh@trtr.co> Date: Wed Feb 8 18:35:56 2023 +0000 undo async commit abe2777a1fdb6418802102fbb1b6aca7ae4d8e66 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>
126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
/* eslint-disable react/jsx-no-undef */
|
|
import { css } from '@emotion/css';
|
|
import React, { useEffect, useRef, useCallback, useState, CSSProperties } from 'react';
|
|
import { FixedSizeList } from 'react-window';
|
|
import InfiniteLoader from 'react-window-infinite-loader';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { useStyles2 } from '@grafana/ui';
|
|
|
|
import { SearchItem } from '../../components/SearchItem';
|
|
import { useSearchKeyboardNavigation } from '../../hooks/useSearchKeyboardSelection';
|
|
import { queryResultToViewItem } from '../../service/utils';
|
|
|
|
import { SearchResultsProps } from './SearchResultsTable';
|
|
|
|
export const SearchResultsCards = React.memo(
|
|
({
|
|
response,
|
|
width,
|
|
height,
|
|
selection,
|
|
selectionToggle,
|
|
onTagSelected,
|
|
keyboardEvents,
|
|
onClickItem,
|
|
}: SearchResultsProps) => {
|
|
const styles = useStyles2(getStyles);
|
|
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
|
|
const [listEl, setListEl] = useState<FixedSizeList | null>(null);
|
|
const highlightIndex = useSearchKeyboardNavigation(keyboardEvents, 0, response);
|
|
|
|
// Scroll to the top and clear loader cache when the query results change
|
|
useEffect(() => {
|
|
if (infiniteLoaderRef.current) {
|
|
infiniteLoaderRef.current.resetloadMoreItemsCache();
|
|
}
|
|
if (listEl) {
|
|
listEl.scrollTo(0);
|
|
}
|
|
}, [response, listEl]);
|
|
|
|
const RenderRow = useCallback(
|
|
({ index: rowIndex, style }: { index: number; style: CSSProperties }) => {
|
|
let className = '';
|
|
if (rowIndex === highlightIndex.y) {
|
|
className += ' ' + styles.selectedRow;
|
|
}
|
|
|
|
const item = response.view.get(rowIndex);
|
|
const searchItem = queryResultToViewItem(item, response.view);
|
|
const isSelected = selectionToggle && selection?.(searchItem.kind, searchItem.uid);
|
|
|
|
return (
|
|
<div style={style} key={item.uid} className={className} role="row">
|
|
<SearchItem
|
|
item={searchItem}
|
|
onTagSelected={onTagSelected}
|
|
onToggleChecked={(item) => {
|
|
if (selectionToggle) {
|
|
selectionToggle('dashboard', item.uid!);
|
|
}
|
|
}}
|
|
editable={Boolean(selection != null)}
|
|
onClickItem={onClickItem}
|
|
isSelected={isSelected}
|
|
/>
|
|
</div>
|
|
);
|
|
},
|
|
[response.view, highlightIndex, styles, onTagSelected, selection, selectionToggle, onClickItem]
|
|
);
|
|
|
|
if (!response.totalRows) {
|
|
return (
|
|
<div className={styles.noData} style={{ width }}>
|
|
No data
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div aria-label="Search results list" style={{ width }} role="list">
|
|
<InfiniteLoader
|
|
ref={infiniteLoaderRef}
|
|
isItemLoaded={response.isItemLoaded}
|
|
itemCount={response.totalRows}
|
|
loadMoreItems={response.loadMoreItems}
|
|
>
|
|
{({ onItemsRendered, ref }) => (
|
|
<FixedSizeList
|
|
ref={(innerRef) => {
|
|
ref(innerRef);
|
|
setListEl(innerRef);
|
|
}}
|
|
onItemsRendered={onItemsRendered}
|
|
height={height}
|
|
itemCount={response.totalRows}
|
|
itemSize={72}
|
|
width="100%"
|
|
style={{ overflow: 'hidden auto' }}
|
|
>
|
|
{RenderRow}
|
|
</FixedSizeList>
|
|
)}
|
|
</InfiniteLoader>
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
SearchResultsCards.displayName = 'SearchResultsCards';
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => {
|
|
return {
|
|
noData: css`
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
`,
|
|
selectedRow: css`
|
|
border-left: 3px solid ${theme.colors.primary.border};
|
|
`,
|
|
};
|
|
};
|