Skip to content

Commit

Permalink
Merge pull request datahub-project#21 from acryldata/GabeRefactoringS…
Browse files Browse the repository at this point in the history
…earchRoutingWithoutExtaCommits

[React App] Moving filters into url params + consolidating search routing logic (#2)
  • Loading branch information
gabe-lyons authored Jan 27, 2021
2 parents 85c8af8 + 6fe2363 commit 3925546
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 52 deletions.
5 changes: 5 additions & 0 deletions datahub-web-react/src/components/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const Routes = (): JSX.Element => {
render={() => <DatasetPage />}
/>
<ProtectedRoute isLoggedIn={isLoggedIn} path={PageRoutes.SEARCH} render={() => <SearchPage />} />
<ProtectedRoute
isLoggedIn={isLoggedIn}
path={PageRoutes.SEARCH_RESULTS}
render={() => <SearchPage />}
/>
<ProtectedRoute
isLoggedIn={isLoggedIn}
exact
Expand Down
14 changes: 5 additions & 9 deletions datahub-web-react/src/components/search/SearchFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ interface Props {
onFilterSelect: (selected: boolean, field: string, value: string) => void;
}

export const SearchFilters = ({
facets: _facets,
selectedFilters: _selectedFilters,
onFilterSelect: _onFilterSelect,
}: Props) => {
export const SearchFilters = ({ facets, selectedFilters, onFilterSelect }: Props) => {
return (
<Card
style={{ border: '1px solid #d2d2d2' }}
title={<h3 style={{ marginBottom: '0px' }}>Filters</h3>}
bodyStyle={{ padding: '24px 0px' }}
>
{_facets.map((facet) => (
{facets.map((facet) => (
<div style={{ padding: '0px 25px 15px 25px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '10px' }}>
{facet.field.charAt(0).toUpperCase() + facet.field.slice(1)}
Expand All @@ -40,13 +36,13 @@ export const SearchFilters = ({
<>
<Checkbox
style={{ margin: '5px 0px' }}
defaultChecked={
_selectedFilters.find(
checked={
selectedFilters.find(
(f) => 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})
Expand Down
61 changes: 26 additions & 35 deletions datahub-web-react/src/components/search/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -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<SearchPageParams>().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<FacetFilterInput> = useFilters(params);

const { loading, error, data } = useGetSearchResultsQuery({
variables: {
Expand All @@ -43,34 +49,19 @@ 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) => {
const newFilters = selected
? [...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) => {
Expand Down Expand Up @@ -130,39 +121,39 @@ export const SearchPage = () => {
}
};

const searchResults = (data && data?.search && toSearchResults(data.search.elements)) || [];
const searchResults = toSearchResults(data?.search?.elements || []);

return (
<SearchablePage initialQuery={query} initialType={type}>
<Layout.Content style={{ backgroundColor: 'white' }}>
<Affix offsetTop={64}>
<Tabs
tabBarStyle={{ backgroundColor: 'white', padding: '0px 165px', marginBottom: '0px' }}
defaultActiveKey={toCollectionName(type)}
activeKey={toCollectionName(type)}
size="large"
onChange={(newPath) => onSearchTypeChange(newPath)}
onChange={onSearchTypeChange}
>
{SEARCHABLE_ENTITY_TYPES.map((t) => (
<Tabs.TabPane tab={toCollectionName(t)} key={toCollectionName(t)} />
))}
</Tabs>
</Affix>
<Row style={{ width: '80%', margin: 'auto auto', backgroundColor: 'white' }}>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 15px' }} span={6}>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 16px' }} span={6}>
<SearchFilters
facets={(data && data?.search && data.search.facets) || []}
facets={data?.search?.facets || []}
selectedFilters={filters}
onFilterSelect={onFilterSelect}
/>
</Col>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 15px' }} span={18}>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 16px' }} span={18}>
{loading && <p>Loading results...</p>}
{error && !data && <p>Search error!</p>}
{data && data?.search && (
{data?.search && (
<SearchResults
typeName={toCollectionName(type)}
results={searchResults}
pageStart={data.search?.start}
pageStart={data?.search?.start}
pageSize={data.search?.count}
totalResults={data.search?.total}
onChangePage={onResultsPageChange}
Expand Down
14 changes: 6 additions & 8 deletions datahub-web-react/src/components/search/SearchablePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import 'antd/dist/antd.css';
import { Layout } from 'antd';
import { useHistory } from 'react-router';
import { SearchHeader } from './SearchHeader';
import { EntityType, fromCollectionName, toCollectionName, toPathName } from '../shared/EntityTypeUtil';
import { EntityType, fromCollectionName, toCollectionName } from '../shared/EntityTypeUtil';
import { SearchCfg } from '../../conf';
import { useGetAutoCompleteResultsLazyQuery } from '../../graphql/search.generated';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';

const { SEARCHABLE_ENTITY_TYPES, SEARCH_BAR_PLACEHOLDER_TEXT, SHOW_ALL_ENTITIES_SEARCH_TYPE } = SearchCfg;

Expand Down Expand Up @@ -49,13 +50,10 @@ export const SearchablePage = ({
const [getAutoCompleteResults, { data: suggestionsData }] = useGetAutoCompleteResultsLazyQuery();

const search = (type: string, query: string) => {
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,
});
};

Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/components/search/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const FILTER_URL_PREFIX = 'filter_';
Original file line number Diff line number Diff line change
@@ -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<FacetFilterInput> = []) {
return filters.reduce((acc, filter) => {
acc[`${FILTER_URL_PREFIX}${filter.field}`] = [
...(acc[`${FILTER_URL_PREFIX}${filter.field}`] || []),
filter.value,
];
return acc;
}, {} as Record<string, string[]>);
}
Original file line number Diff line number Diff line change
@@ -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<FacetFilterInput>;
history: RouteComponentProps['history'];
}) => {
const search = QueryString.stringify(
{
...filtersToQueryStringParams(newFilters),
query: newQuery,
page: newPage,
},
{ arrayFormat: 'comma' },
);

history.push({
pathname: `${PageRoutes.SEARCH}/${toPathName(newType)}`,
search,
});
};
27 changes: 27 additions & 0 deletions datahub-web-react/src/components/search/utils/useFilters.ts
Original file line number Diff line number Diff line change
@@ -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<string>): Array<FacetFilterInput> {
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],
);
}
5 changes: 5 additions & 0 deletions datahub-web-react/src/components/shared/EntityTypeUtil.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/conf/Global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 3925546

Please sign in to comment.