From 26fb21aa8befd417e2b12b1e37380faa7a37bfff Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 1 Feb 2023 13:05:01 -0500 Subject: [PATCH] feat(SearchType.Accordion): implement displaying result counts Signed-off-by: Phil Kuang --- .changeset/three-geckos-press.md | 5 ++ .../app/src/components/search/SearchPage.tsx | 1 + plugins/search/api-report.md | 1 + .../SearchType/SearchType.Accordion.test.tsx | 36 ++++++++++++++- .../SearchType/SearchType.Accordion.tsx | 46 +++++++++++++++++-- 5 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 .changeset/three-geckos-press.md diff --git a/.changeset/three-geckos-press.md b/.changeset/three-geckos-press.md new file mode 100644 index 0000000000..fc1af4fdc0 --- /dev/null +++ b/.changeset/three-geckos-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Implement a `showCounts` option to display result counts per type in `SearchType.Accordion` diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 8167889b02..6c4f06aed9 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -79,6 +79,7 @@ const SearchPage = () => { ; defaultValue?: string; + showCounts?: boolean; }; // @public diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx index da1cae46b9..e0acee025a 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx @@ -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( + + + , + ); + + 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(); + }); + }); }); diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx index 49f5cf74ca..67785b89cb 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx @@ -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 ( @@ -161,7 +196,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { className: classes.listItemIcon, })} - + ))}