feat(SearchType.Accordion): implement displaying result counts

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2023-02-01 13:05:01 -05:00
parent 422e6b050c
commit 26fb21aa8b
5 changed files with 83 additions and 6 deletions
+5
View File
@@ -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',
+1
View File
@@ -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>
))}