diff --git a/datahub-web-react/src/components/Routes.tsx b/datahub-web-react/src/components/Routes.tsx index 6c7c8eab31c561..12aec2a8d57d03 100644 --- a/datahub-web-react/src/components/Routes.tsx +++ b/datahub-web-react/src/components/Routes.tsx @@ -39,6 +39,11 @@ export const Routes = (): JSX.Element => { render={() => } /> } /> + } + /> void; } -export const SearchFilters = ({ - facets: _facets, - selectedFilters: _selectedFilters, - onFilterSelect: _onFilterSelect, -}: Props) => { +export const SearchFilters = ({ facets, selectedFilters, onFilterSelect }: Props) => { return ( Filters} bodyStyle={{ padding: '24px 0px' }} > - {_facets.map((facet) => ( + {facets.map((facet) => ( {facet.field.charAt(0).toUpperCase() + facet.field.slice(1)} @@ -40,13 +36,13 @@ export const SearchFilters = ({ <> f.field === facet.field && f.value === aggregation.value, ) !== undefined } onChange={(e: CheckboxChangeEvent) => - _onFilterSelect(e.target.checked, facet.field, aggregation.value) + onFilterSelect(e.target.checked, facet.field, aggregation.value) } > {aggregation.value} ({aggregation.count}) diff --git a/datahub-web-react/src/components/search/SearchPage.tsx b/datahub-web-react/src/components/search/SearchPage.tsx index bf7785bde921a0..13d03f15808432 100644 --- a/datahub-web-react/src/components/search/SearchPage.tsx +++ b/datahub-web-react/src/components/search/SearchPage.tsx @@ -1,19 +1,25 @@ -import * as React from 'react'; +import React from 'react'; import * as QueryString from 'query-string'; -import { useHistory, useLocation } from 'react-router'; +import { useHistory, useLocation, useParams } from 'react-router'; import { Affix, Col, Row, Tabs, Layout } from 'antd'; import { SearchablePage } from './SearchablePage'; -import { fromCollectionName, fromPathName, toCollectionName, toPathName } from '../shared/EntityTypeUtil'; +import { fromCollectionName, fromPathnameOrEmptyString, toCollectionName } from '../shared/EntityTypeUtil'; import { useGetSearchResultsQuery } from '../../graphql/search.generated'; import { SearchResults } from './SearchResults'; -import { EntityType, PlatformNativeType } from '../../types.generated'; +import { EntityType, FacetFilterInput, PlatformNativeType } from '../../types.generated'; import { SearchFilters } from './SearchFilters'; import { SearchCfg } from '../../conf'; import { PageRoutes } from '../../conf/Global'; +import useFilters from './utils/useFilters'; +import { navigateToSearchUrl } from './utils/navigateToSearchUrl'; const { SEARCHABLE_ENTITY_TYPES, RESULTS_PER_PAGE } = SearchCfg; +type SearchPageParams = { + type?: string; +}; + /** * A dedicated search page. * @@ -23,11 +29,11 @@ export const SearchPage = () => { const history = useHistory(); const location = useLocation(); - const params = QueryString.parse(location.search); - const type = params.type ? fromPathName(params.type as string) : SEARCHABLE_ENTITY_TYPES[0]; - const query = params.query ? (params.query as string) : ''; - const page = params.page && Number(params.page as string) > 0 ? Number(params.page as string) : 1; - const filters = location.state ? ((location.state as any).filters as Array<{ field: string; value: string }>) : []; + const params = QueryString.parse(location.search, { arrayFormat: 'comma' }); + const type = fromPathnameOrEmptyString(useParams().type || '') || SEARCHABLE_ENTITY_TYPES[0]; + const query: string = params.query ? (params.query as string) : ''; + const page: number = params.page && Number(params.page as string) > 0 ? Number(params.page as string) : 1; + const filters: Array = useFilters(params); const { loading, error, data } = useGetSearchResultsQuery({ variables: { @@ -43,10 +49,7 @@ export const SearchPage = () => { const onSearchTypeChange = (newType: string) => { const entityType = fromCollectionName(newType); - history.push({ - pathname: PageRoutes.SEARCH, - search: `?type=${toPathName(entityType)}&query=${query}&page=1`, - }); + navigateToSearchUrl({ type: entityType, query, page: 1, history }); }; const onFilterSelect = (selected: boolean, field: string, value: string) => { @@ -54,23 +57,11 @@ export const SearchPage = () => { ? [...filters, { field, value }] : filters.filter((filter) => filter.field !== field || filter.value !== value); - history.push({ - pathname: PageRoutes.SEARCH, - search: `?type=${toPathName(type)}&query=${query}&page=1`, - state: { - filters: newFilters, - }, - }); + navigateToSearchUrl({ type, query, page: 1, filters: newFilters, history }); }; const onResultsPageChange = (newPage: number) => { - return history.push({ - pathname: PageRoutes.SEARCH, - search: `?type=${toPathName(type)}&query=${query}&page=${newPage}`, - state: { - filters: [...filters], - }, - }); + navigateToSearchUrl({ type, query, page: newPage, filters, history }); }; const navigateToDataset = (urn: string) => { @@ -130,7 +121,7 @@ export const SearchPage = () => { } }; - const searchResults = (data && data?.search && toSearchResults(data.search.elements)) || []; + const searchResults = toSearchResults(data?.search?.elements || []); return ( @@ -138,9 +129,9 @@ export const SearchPage = () => { onSearchTypeChange(newPath)} + onChange={onSearchTypeChange} > {SEARCHABLE_ENTITY_TYPES.map((t) => ( @@ -148,21 +139,21 @@ export const SearchPage = () => { - + - + {loading && Loading results...} {error && !data && Search error!} - {data && data?.search && ( + {data?.search && ( { - const typeParam = - ALL_ENTITIES_SEARCH_TYPE_NAME === type ? EMPTY_STRING : `type=${toPathName(fromCollectionName(type))}`; - const queryParam = `query=${query}`; - - history.push({ - pathname: '/search', - search: `?${typeParam}&${queryParam}`, + navigateToSearchUrl({ + type: ALL_ENTITIES_SEARCH_TYPE_NAME === type ? SEARCHABLE_ENTITY_TYPES[0] : fromCollectionName(type), + query, + history, }); }; diff --git a/datahub-web-react/src/components/search/utils/constants.ts b/datahub-web-react/src/components/search/utils/constants.ts new file mode 100644 index 00000000000000..2aab0b3721b95c --- /dev/null +++ b/datahub-web-react/src/components/search/utils/constants.ts @@ -0,0 +1 @@ +export const FILTER_URL_PREFIX = 'filter_'; diff --git a/datahub-web-react/src/components/search/utils/filtersToQueryStringParams.ts b/datahub-web-react/src/components/search/utils/filtersToQueryStringParams.ts new file mode 100644 index 00000000000000..fd2367784bd57a --- /dev/null +++ b/datahub-web-react/src/components/search/utils/filtersToQueryStringParams.ts @@ -0,0 +1,13 @@ +import { FacetFilterInput } from '../../../types.generated'; +import { FILTER_URL_PREFIX } from './constants'; + +// transform filters from [{ filter, value }, { filter, value }] to { filter: [value, value ] } that QueryString can parse +export default function filtersToQueryStringParams(filters: Array = []) { + return filters.reduce((acc, filter) => { + acc[`${FILTER_URL_PREFIX}${filter.field}`] = [ + ...(acc[`${FILTER_URL_PREFIX}${filter.field}`] || []), + filter.value, + ]; + return acc; + }, {} as Record); +} diff --git a/datahub-web-react/src/components/search/utils/navigateToSearchUrl.ts b/datahub-web-react/src/components/search/utils/navigateToSearchUrl.ts new file mode 100644 index 00000000000000..b667aacf9ff80d --- /dev/null +++ b/datahub-web-react/src/components/search/utils/navigateToSearchUrl.ts @@ -0,0 +1,35 @@ +import * as QueryString from 'query-string'; +import { RouteComponentProps } from 'react-router-dom'; + +import filtersToQueryStringParams from './filtersToQueryStringParams'; +import { EntityType, FacetFilterInput } from '../../../types.generated'; +import { toPathName } from '../../shared/EntityTypeUtil'; +import { PageRoutes } from '../../../conf/Global'; + +export const navigateToSearchUrl = ({ + type: newType, + query: newQuery, + page: newPage = 1, + filters: newFilters, + history, +}: { + type: EntityType; + query?: string; + page?: number; + filters?: Array; + history: RouteComponentProps['history']; +}) => { + const search = QueryString.stringify( + { + ...filtersToQueryStringParams(newFilters), + query: newQuery, + page: newPage, + }, + { arrayFormat: 'comma' }, + ); + + history.push({ + pathname: `${PageRoutes.SEARCH}/${toPathName(newType)}`, + search, + }); +}; diff --git a/datahub-web-react/src/components/search/utils/useFilters.ts b/datahub-web-react/src/components/search/utils/useFilters.ts new file mode 100644 index 00000000000000..bc345a3c24871d --- /dev/null +++ b/datahub-web-react/src/components/search/utils/useFilters.ts @@ -0,0 +1,27 @@ +import { useMemo } from 'react'; +import * as QueryString from 'query-string'; + +import { FILTER_URL_PREFIX } from './constants'; +import { FacetFilterInput } from '../../../types.generated'; + +export default function useFilters(params: QueryString.ParsedQuery): Array { + return useMemo( + () => + // get all query params + Object.entries(params) + // select only the ones with the `filter_` prefix + .filter(([key, _]) => key.indexOf(FILTER_URL_PREFIX) >= 0) + // transform the filters currently in format [key, [value1, value2]] to [{key: key, value: value1}, { key: key, value: value2}] format that graphql expects + .flatMap(([key, value]) => { + // remove the `filter_` prefix + const field = key.replace(FILTER_URL_PREFIX, ''); + if (!value) return []; + + if (Array.isArray(value)) { + return value.map((distinctValue) => ({ field, value: distinctValue })); + } + return [{ field, value }]; + }), + [params], + ); +} diff --git a/datahub-web-react/src/components/shared/EntityTypeUtil.tsx b/datahub-web-react/src/components/shared/EntityTypeUtil.tsx index ca2a2ccfb0b3ba..e3f38e6ac136a8 100644 --- a/datahub-web-react/src/components/shared/EntityTypeUtil.tsx +++ b/datahub-web-react/src/components/shared/EntityTypeUtil.tsx @@ -13,6 +13,11 @@ export const fromPathName = (pathName: string): EntityType => { } }; +export const fromPathnameOrEmptyString = (pathName: string): EntityType | null => { + if (pathName === '') return null; + return fromPathName(pathName); +}; + export const toPathName = (type: EntityType): string => { switch (type) { case EntityType.Dataset: diff --git a/datahub-web-react/src/conf/Global.tsx b/datahub-web-react/src/conf/Global.tsx index cac2dc54dcd69c..03e98f92c33046 100644 --- a/datahub-web-react/src/conf/Global.tsx +++ b/datahub-web-react/src/conf/Global.tsx @@ -10,6 +10,7 @@ export const LOGO_IMAGE = DataHubLogo; */ export enum PageRoutes { LOG_IN = '/login', + SEARCH_RESULTS = '/search/:type', SEARCH = '/search', BROWSE = '/browse', BROWSE_RESULTS = '/browse/:type',
Loading results...
Search error!