mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
Memoize SearchResults (#35851)
This commit is contained in:
parent
5f07381796
commit
91e9ef232d
@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, memo } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@ -22,82 +22,78 @@ export interface Props {
|
||||
|
||||
const { section: sectionLabel, items: itemsLabel } = selectors.components.Search;
|
||||
|
||||
export const SearchResults: FC<Props> = ({
|
||||
editable,
|
||||
loading,
|
||||
onTagSelected,
|
||||
onToggleChecked,
|
||||
onToggleSection,
|
||||
results,
|
||||
layout,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const styles = getSectionStyles(theme);
|
||||
const itemProps = { editable, onToggleChecked, onTagSelected };
|
||||
const renderFolders = () => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{results.map((section) => {
|
||||
return (
|
||||
<div aria-label={sectionLabel} className={styles.section} key={section.id || section.title}>
|
||||
<SectionHeader onSectionClick={onToggleSection} {...{ onToggleChecked, editable, section }} />
|
||||
{section.expanded && (
|
||||
<div aria-label={itemsLabel} className={styles.sectionItems}>
|
||||
{section.items.map((item) => (
|
||||
<SearchItem key={item.id} {...itemProps} item={item} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderDashboards = () => {
|
||||
const items = results[0]?.items;
|
||||
return (
|
||||
<div className={styles.listModeWrapper}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<FixedSizeList
|
||||
aria-label="Search items"
|
||||
className={styles.wrapper}
|
||||
innerElementType="ul"
|
||||
itemSize={SEARCH_ITEM_HEIGHT + SEARCH_ITEM_MARGIN}
|
||||
height={height}
|
||||
itemCount={items.length}
|
||||
width="100%"
|
||||
>
|
||||
{({ index, style }) => {
|
||||
const item = items[index];
|
||||
// The wrapper div is needed as the inner SearchItem has margin-bottom spacing
|
||||
// And without this wrapper there is no room for that margin
|
||||
return (
|
||||
<div style={style}>
|
||||
<SearchItem key={item.id} {...itemProps} item={item} />
|
||||
export const SearchResults: FC<Props> = memo(
|
||||
({ editable, loading, onTagSelected, onToggleChecked, onToggleSection, results, layout }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getSectionStyles(theme);
|
||||
const itemProps = { editable, onToggleChecked, onTagSelected };
|
||||
const renderFolders = () => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{results.map((section) => {
|
||||
return (
|
||||
<div aria-label={sectionLabel} className={styles.section} key={section.id || section.title}>
|
||||
<SectionHeader onSectionClick={onToggleSection} {...{ onToggleChecked, editable, section }} />
|
||||
{section.expanded && (
|
||||
<div aria-label={itemsLabel} className={styles.sectionItems}>
|
||||
{section.items.map((item) => (
|
||||
<SearchItem key={item.id} {...itemProps} item={item} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderDashboards = () => {
|
||||
const items = results[0]?.items;
|
||||
return (
|
||||
<div className={styles.listModeWrapper}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<FixedSizeList
|
||||
aria-label="Search items"
|
||||
className={styles.wrapper}
|
||||
innerElementType="ul"
|
||||
itemSize={SEARCH_ITEM_HEIGHT + SEARCH_ITEM_MARGIN}
|
||||
height={height}
|
||||
itemCount={items.length}
|
||||
width="100%"
|
||||
>
|
||||
{({ index, style }) => {
|
||||
const item = items[index];
|
||||
// The wrapper div is needed as the inner SearchItem has margin-bottom spacing
|
||||
// And without this wrapper there is no room for that margin
|
||||
return (
|
||||
<div style={style}>
|
||||
<SearchItem key={item.id} {...itemProps} item={item} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Spinner className={styles.spinner} />;
|
||||
} else if (!results || !results.length) {
|
||||
return <div className={styles.noResults}>No dashboards matching your query were found.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.resultsContainer}>
|
||||
{layout === SearchLayout.Folders ? renderFolders() : renderDashboards()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Spinner className={styles.spinner} />;
|
||||
} else if (!results || !results.length) {
|
||||
return <div className={styles.noResults}>No dashboards matching your query were found.</div>;
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.resultsContainer}>
|
||||
{layout === SearchLayout.Folders ? renderFolders() : renderDashboards()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
SearchResults.displayName = 'SearchResults';
|
||||
|
||||
const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const { md } = theme.spacing;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useMemo, useReducer } from 'react';
|
||||
import { useCallback, useMemo, useReducer } from 'react';
|
||||
import { FolderDTO } from 'app/types';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked } from '../types';
|
||||
@ -23,9 +23,12 @@ export const useManageDashboards = (
|
||||
dispatch,
|
||||
} = useSearch<ManageDashboardsState>(query, reducer, {});
|
||||
|
||||
const onToggleChecked: OnToggleChecked = (item) => {
|
||||
dispatch({ type: TOGGLE_CHECKED, payload: item });
|
||||
};
|
||||
const onToggleChecked: OnToggleChecked = useCallback(
|
||||
(item) => {
|
||||
dispatch({ type: TOGGLE_CHECKED, payload: item });
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onToggleAllChecked = () => {
|
||||
dispatch({ type: TOGGLE_ALL_CHECKED });
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
@ -35,17 +35,20 @@ export const useSearch: UseSearch = (query, reducer, params = {}) => {
|
||||
|
||||
useDebounce(search, 300, [query, queryParsing]);
|
||||
|
||||
const onToggleSection = (section: DashboardSection) => {
|
||||
if (hasId(section.title) && !section.items.length) {
|
||||
dispatch({ type: FETCH_ITEMS_START, payload: section.id });
|
||||
backendSrv.search({ folderIds: [section.id] }).then((items) => {
|
||||
dispatch({ type: FETCH_ITEMS, payload: { section, items } });
|
||||
const onToggleSection = useCallback(
|
||||
(section: DashboardSection) => {
|
||||
if (hasId(section.title) && !section.items.length) {
|
||||
dispatch({ type: FETCH_ITEMS_START, payload: section.id });
|
||||
backendSrv.search({ folderIds: [section.id] }).then((items) => {
|
||||
dispatch({ type: FETCH_ITEMS, payload: { section, items } });
|
||||
dispatch({ type: TOGGLE_SECTION, payload: section });
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: TOGGLE_SECTION, payload: section });
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: TOGGLE_SECTION, payload: section });
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return { state, dispatch, onToggleSection };
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormEvent, useReducer } from 'react';
|
||||
import { FormEvent, useCallback, useReducer } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
@ -32,10 +32,13 @@ export const useSearchQuery = (defaults: Partial<DashboardQuery>) => {
|
||||
updateLocation({ tag: tags });
|
||||
};
|
||||
|
||||
const onTagAdd = (tag: string) => {
|
||||
dispatch({ type: ADD_TAG, payload: tag });
|
||||
updateLocation({ tag: [...query.tag, tag] });
|
||||
};
|
||||
const onTagAdd = useCallback(
|
||||
(tag: string) => {
|
||||
dispatch({ type: ADD_TAG, payload: tag });
|
||||
updateLocation({ tag: [...query.tag, tag] });
|
||||
},
|
||||
[query.tag]
|
||||
);
|
||||
|
||||
const onClearFilters = () => {
|
||||
dispatch({ type: CLEAR_FILTERS });
|
||||
|
Loading…
Reference in New Issue
Block a user