mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -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 { css } from '@emotion/css';
|
||||||
import { FixedSizeList } from 'react-window';
|
import { FixedSizeList } from 'react-window';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
@ -22,82 +22,78 @@ export interface Props {
|
|||||||
|
|
||||||
const { section: sectionLabel, items: itemsLabel } = selectors.components.Search;
|
const { section: sectionLabel, items: itemsLabel } = selectors.components.Search;
|
||||||
|
|
||||||
export const SearchResults: FC<Props> = ({
|
export const SearchResults: FC<Props> = memo(
|
||||||
editable,
|
({ editable, loading, onTagSelected, onToggleChecked, onToggleSection, results, layout }) => {
|
||||||
loading,
|
const theme = useTheme();
|
||||||
onTagSelected,
|
const styles = getSectionStyles(theme);
|
||||||
onToggleChecked,
|
const itemProps = { editable, onToggleChecked, onTagSelected };
|
||||||
onToggleSection,
|
const renderFolders = () => {
|
||||||
results,
|
return (
|
||||||
layout,
|
<div className={styles.wrapper}>
|
||||||
}) => {
|
{results.map((section) => {
|
||||||
const theme = useTheme();
|
return (
|
||||||
const styles = getSectionStyles(theme);
|
<div aria-label={sectionLabel} className={styles.section} key={section.id || section.title}>
|
||||||
const itemProps = { editable, onToggleChecked, onTagSelected };
|
<SectionHeader onSectionClick={onToggleSection} {...{ onToggleChecked, editable, section }} />
|
||||||
const renderFolders = () => {
|
{section.expanded && (
|
||||||
return (
|
<div aria-label={itemsLabel} className={styles.sectionItems}>
|
||||||
<div className={styles.wrapper}>
|
{section.items.map((item) => (
|
||||||
{results.map((section) => {
|
<SearchItem key={item.id} {...itemProps} item={item} />
|
||||||
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} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
}}
|
</div>
|
||||||
</FixedSizeList>
|
);
|
||||||
)}
|
})}
|
||||||
</AutoSizer>
|
</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>
|
</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 (
|
SearchResults.displayName = 'SearchResults';
|
||||||
<div className={styles.resultsContainer}>
|
|
||||||
{layout === SearchLayout.Folders ? renderFolders() : renderDashboards()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getSectionStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const { md } = theme.spacing;
|
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 { FolderDTO } from 'app/types';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked } from '../types';
|
import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked } from '../types';
|
||||||
@ -23,9 +23,12 @@ export const useManageDashboards = (
|
|||||||
dispatch,
|
dispatch,
|
||||||
} = useSearch<ManageDashboardsState>(query, reducer, {});
|
} = useSearch<ManageDashboardsState>(query, reducer, {});
|
||||||
|
|
||||||
const onToggleChecked: OnToggleChecked = (item) => {
|
const onToggleChecked: OnToggleChecked = useCallback(
|
||||||
dispatch({ type: TOGGLE_CHECKED, payload: item });
|
(item) => {
|
||||||
};
|
dispatch({ type: TOGGLE_CHECKED, payload: item });
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const onToggleAllChecked = () => {
|
const onToggleAllChecked = () => {
|
||||||
dispatch({ type: TOGGLE_ALL_CHECKED });
|
dispatch({ type: TOGGLE_ALL_CHECKED });
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
import { SearchSrv } from 'app/core/services/search_srv';
|
import { SearchSrv } from 'app/core/services/search_srv';
|
||||||
import { backendSrv } from 'app/core/services/backend_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]);
|
useDebounce(search, 300, [query, queryParsing]);
|
||||||
|
|
||||||
const onToggleSection = (section: DashboardSection) => {
|
const onToggleSection = useCallback(
|
||||||
if (hasId(section.title) && !section.items.length) {
|
(section: DashboardSection) => {
|
||||||
dispatch({ type: FETCH_ITEMS_START, payload: section.id });
|
if (hasId(section.title) && !section.items.length) {
|
||||||
backendSrv.search({ folderIds: [section.id] }).then((items) => {
|
dispatch({ type: FETCH_ITEMS_START, payload: section.id });
|
||||||
dispatch({ type: FETCH_ITEMS, payload: { section, items } });
|
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 });
|
dispatch({ type: TOGGLE_SECTION, payload: section });
|
||||||
});
|
}
|
||||||
} else {
|
},
|
||||||
dispatch({ type: TOGGLE_SECTION, payload: section });
|
[dispatch]
|
||||||
}
|
);
|
||||||
};
|
|
||||||
|
|
||||||
return { state, dispatch, onToggleSection };
|
return { state, dispatch, onToggleSection };
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FormEvent, useReducer } from 'react';
|
import { FormEvent, useCallback, useReducer } from 'react';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
@ -32,10 +32,13 @@ export const useSearchQuery = (defaults: Partial<DashboardQuery>) => {
|
|||||||
updateLocation({ tag: tags });
|
updateLocation({ tag: tags });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTagAdd = (tag: string) => {
|
const onTagAdd = useCallback(
|
||||||
dispatch({ type: ADD_TAG, payload: tag });
|
(tag: string) => {
|
||||||
updateLocation({ tag: [...query.tag, tag] });
|
dispatch({ type: ADD_TAG, payload: tag });
|
||||||
};
|
updateLocation({ tag: [...query.tag, tag] });
|
||||||
|
},
|
||||||
|
[query.tag]
|
||||||
|
);
|
||||||
|
|
||||||
const onClearFilters = () => {
|
const onClearFilters = () => {
|
||||||
dispatch({ type: CLEAR_FILTERS });
|
dispatch({ type: CLEAR_FILTERS });
|
||||||
|
Loading…
Reference in New Issue
Block a user