feat(SearchType.Accordion): implement displaying result counts
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search': patch
|
||||
---
|
||||
|
||||
Implement a `showCounts` option to display result counts per type in `SearchType.Accordion`
|
||||
@@ -79,6 +79,7 @@ const SearchPage = () => {
|
||||
<SearchType.Accordion
|
||||
name="Result Type"
|
||||
defaultValue="software-catalog"
|
||||
showCounts
|
||||
types={[
|
||||
{
|
||||
value: 'software-catalog',
|
||||
|
||||
@@ -97,6 +97,7 @@ export type SearchTypeAccordionProps = {
|
||||
icon: JSX.Element;
|
||||
}>;
|
||||
defaultValue?: string;
|
||||
showCounts?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import {
|
||||
searchApiRef,
|
||||
@@ -34,6 +34,8 @@ jest.mock('@backstage/plugin-search-react', () => ({
|
||||
setTypes: (types: any) => setTypesMock(types),
|
||||
pageCursor: '',
|
||||
setPageCursor: (pageCursor: any) => setPageCursorMock(pageCursor),
|
||||
term: 'abc',
|
||||
filters: { foo: 'bar' },
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -48,7 +50,7 @@ describe('SearchType.Accordion', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
query.mockResolvedValue({ results: [] });
|
||||
query.mockResolvedValue({ results: [], numberOfResults: 1234 });
|
||||
});
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
@@ -132,4 +134,34 @@ describe('SearchType.Accordion', () => {
|
||||
|
||||
expect(queryByText('Collapse')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show result counts if enabled', async () => {
|
||||
const { getAllByText } = render(
|
||||
<Wrapper>
|
||||
<SearchType.Accordion
|
||||
name={expectedLabel}
|
||||
showCounts
|
||||
types={[expectedType]}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
term: 'abc',
|
||||
types: [],
|
||||
filters: { foo: 'bar' },
|
||||
pageLimit: 0,
|
||||
});
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
term: 'abc',
|
||||
types: [expectedType.value],
|
||||
filters: {},
|
||||
pageLimit: 0,
|
||||
});
|
||||
await waitFor(() => {
|
||||
const countLabels = getAllByText('1234 results');
|
||||
expect(countLabels.length).toEqual(2);
|
||||
expect(countLabels[0]).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
|
||||
import React, { cloneElement, Fragment, useEffect, useState } from 'react';
|
||||
import { useSearch } from '@backstage/plugin-search-react';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { searchApiRef, useSearch } from '@backstage/plugin-search-react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import AllIcon from '@material-ui/icons/FontDownload';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
@@ -81,13 +83,15 @@ export type SearchTypeAccordionProps = {
|
||||
icon: JSX.Element;
|
||||
}>;
|
||||
defaultValue?: string;
|
||||
showCounts?: boolean;
|
||||
};
|
||||
|
||||
export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
|
||||
const classes = useStyles();
|
||||
const { setPageCursor, setTypes, types } = useSearch();
|
||||
const { filters, setPageCursor, setTypes, term, types } = useSearch();
|
||||
const searchApi = useApi(searchApiRef);
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const { defaultValue, name, types: givenTypes } = props;
|
||||
const { defaultValue, name, showCounts, types: givenTypes } = props;
|
||||
|
||||
const toggleExpanded = () => setExpanded(prevState => !prevState);
|
||||
const handleClick = (type: string) => {
|
||||
@@ -116,6 +120,37 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
|
||||
];
|
||||
const selected = types[0] || '';
|
||||
|
||||
const { value: resultCounts } = useAsync(async () => {
|
||||
if (!showCounts) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const counts = await Promise.all(
|
||||
definedTypes
|
||||
.map(t => t.value)
|
||||
.map(async type => {
|
||||
const { numberOfResults } = await searchApi.query({
|
||||
term,
|
||||
types: type ? [type] : [],
|
||||
filters:
|
||||
types.includes(type) || (!types.length && !type) ? filters : {},
|
||||
pageLimit: 0,
|
||||
});
|
||||
|
||||
return [
|
||||
type,
|
||||
numberOfResults !== undefined
|
||||
? `${
|
||||
numberOfResults >= 10000 ? `>10000` : numberOfResults
|
||||
} results`
|
||||
: ' -- ',
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
return Object.fromEntries(counts);
|
||||
}, [filters, showCounts, term, types]);
|
||||
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title={name} titleTypographyProps={{ variant: 'overline' }} />
|
||||
@@ -161,7 +196,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
|
||||
className: classes.listItemIcon,
|
||||
})}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={type.name} />
|
||||
<ListItemText
|
||||
primary={type.name}
|
||||
secondary={resultCounts && resultCounts[type.value]}
|
||||
/>
|
||||
</ListItem>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user