2021-02-25 15:27:58 -08:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import { FilterOutlined } from '@ant-design/icons';
|
2021-03-18 15:40:24 -07:00
|
|
|
import styled from 'styled-components';
|
2021-04-03 01:58:40 +08:00
|
|
|
import { Alert, Button, Card, Divider, Drawer, List, Pagination, Row, Typography } from 'antd';
|
2021-03-23 15:18:32 -07:00
|
|
|
import { ListProps } from 'antd/lib/list';
|
2021-02-25 15:27:58 -08:00
|
|
|
import { SearchCfg } from '../../conf';
|
|
|
|
import { useGetSearchResultsQuery } from '../../graphql/search.generated';
|
2021-03-23 15:18:32 -07:00
|
|
|
import { EntityType, FacetFilterInput, SearchResult } from '../../types.generated';
|
2021-02-25 15:27:58 -08:00
|
|
|
import { IconStyleType } from '../entity/Entity';
|
|
|
|
import { Message } from '../shared/Message';
|
|
|
|
import { useEntityRegistry } from '../useEntityRegistry';
|
|
|
|
import { SearchFilters } from './SearchFilters';
|
2021-03-02 11:46:55 -08:00
|
|
|
import { filtersToGraphqlParams } from './utils/filtersToGraphqlParams';
|
2021-05-11 15:41:42 -07:00
|
|
|
import analytics, { EventType } from '../analytics';
|
2021-02-25 15:27:58 -08:00
|
|
|
|
|
|
|
const styles = {
|
|
|
|
loading: { marginTop: '10%' },
|
|
|
|
resultSummary: { color: 'gray', marginTop: '36px' },
|
|
|
|
resultHeaderCardBody: { padding: '16px 24px' },
|
|
|
|
resultHeaderCard: { right: '52px', top: '-40px', position: 'absolute' },
|
|
|
|
paginationRow: { padding: 40 },
|
|
|
|
resultsContainer: { width: '100%', padding: '20px 132px' },
|
|
|
|
};
|
|
|
|
|
2021-03-18 15:40:24 -07:00
|
|
|
const ResultList = styled(List)`
|
|
|
|
&&& {
|
|
|
|
width: 100%;
|
|
|
|
border-color: ${(props) => props.theme.styles['border-color-base']};
|
|
|
|
margin-top: 12px;
|
|
|
|
padding: 16px 32px;
|
|
|
|
box-shadow: ${(props) => props.theme.styles['box-shadow']};
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2021-04-03 01:58:40 +08:00
|
|
|
const ApplyButton = styled(Button)`
|
|
|
|
&& {
|
|
|
|
margin: 20px 25px 0 25px;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2021-02-25 15:27:58 -08:00
|
|
|
interface Props {
|
|
|
|
type: EntityType;
|
|
|
|
query: string;
|
|
|
|
page: number;
|
|
|
|
filters: Array<FacetFilterInput>;
|
|
|
|
onChangeFilters: (filters: Array<FacetFilterInput>) => void;
|
|
|
|
onChangePage: (page: number) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const EntitySearchResults = ({ type, query, page, filters, onChangeFilters, onChangePage }: Props) => {
|
|
|
|
const [isEditingFilters, setIsEditingFilters] = useState(false);
|
|
|
|
const [selectedFilters, setSelectedFilters] = useState(filters);
|
|
|
|
useEffect(() => {
|
|
|
|
setSelectedFilters(filters);
|
|
|
|
}, [filters]);
|
|
|
|
|
|
|
|
const entityRegistry = useEntityRegistry();
|
|
|
|
const { loading, error, data } = useGetSearchResultsQuery({
|
|
|
|
variables: {
|
|
|
|
input: {
|
|
|
|
type,
|
|
|
|
query,
|
|
|
|
start: (page - 1) * SearchCfg.RESULTS_PER_PAGE,
|
|
|
|
count: SearchCfg.RESULTS_PER_PAGE,
|
2021-03-02 11:46:55 -08:00
|
|
|
filters: filtersToGraphqlParams(filters),
|
2021-02-25 15:27:58 -08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-03-23 15:18:32 -07:00
|
|
|
const results = data?.search?.searchResults || [];
|
2021-02-25 15:27:58 -08:00
|
|
|
const pageStart = data?.search?.start || 0;
|
|
|
|
const pageSize = data?.search?.count || 0;
|
|
|
|
const totalResults = data?.search?.total || 0;
|
|
|
|
const lastResultIndex =
|
|
|
|
pageStart * pageSize + pageSize > totalResults ? totalResults : pageStart * pageSize + pageSize;
|
|
|
|
|
2021-05-11 15:41:42 -07:00
|
|
|
useEffect(() => {
|
|
|
|
if (!loading && !error) {
|
|
|
|
analytics.event({
|
|
|
|
type: EventType.SearchResultsViewEvent,
|
|
|
|
page,
|
|
|
|
query,
|
|
|
|
entityTypeFilter: type,
|
|
|
|
total: totalResults,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, [page, query, totalResults, type, loading, error, data]);
|
|
|
|
|
2021-02-25 15:27:58 -08:00
|
|
|
const onFilterSelect = (selected: boolean, field: string, value: string) => {
|
|
|
|
const newFilters = selected
|
|
|
|
? [...selectedFilters, { field, value }]
|
|
|
|
: selectedFilters.filter((filter) => filter.field !== field || filter.value !== value);
|
|
|
|
setSelectedFilters(newFilters);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onEditFilters = () => {
|
|
|
|
setIsEditingFilters(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onApplyFilters = () => {
|
|
|
|
onChangeFilters(selectedFilters);
|
|
|
|
setIsEditingFilters(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onCloseEditFilters = () => {
|
|
|
|
setIsEditingFilters(false);
|
|
|
|
setSelectedFilters(filters);
|
|
|
|
};
|
|
|
|
|
2021-05-11 15:41:42 -07:00
|
|
|
const onResultClick = (result: SearchResult, index: number) => {
|
|
|
|
analytics.event({
|
|
|
|
type: EventType.SearchResultClickEvent,
|
|
|
|
query,
|
|
|
|
entityUrn: result.entity.urn,
|
|
|
|
entityType: result.entity.type,
|
|
|
|
entityTypeFilter: type,
|
|
|
|
index,
|
|
|
|
total: totalResults,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-02-25 15:27:58 -08:00
|
|
|
if (error || (!loading && !error && !data)) {
|
|
|
|
return <Alert type="error" message={error?.message || 'Entity failed to load'} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div style={styles.resultsContainer}>
|
|
|
|
{loading && <Message type="loading" content="Loading..." style={styles.loading} />}
|
2021-03-09 23:14:52 -08:00
|
|
|
<Button onClick={onEditFilters} data-testid="filters-button">
|
2021-02-25 15:27:58 -08:00
|
|
|
<FilterOutlined />
|
|
|
|
Filters{' '}
|
|
|
|
{filters.length > 0 && (
|
|
|
|
<>
|
|
|
|
{' '}
|
|
|
|
(<b>{filters.length}</b>)
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Button>
|
2021-04-03 01:58:40 +08:00
|
|
|
<Drawer title="Filters" placement="left" closable visible={isEditingFilters} onClose={onCloseEditFilters}>
|
2021-02-25 15:27:58 -08:00
|
|
|
<SearchFilters
|
|
|
|
facets={data?.search?.facets || []}
|
|
|
|
selectedFilters={selectedFilters}
|
|
|
|
onFilterSelect={onFilterSelect}
|
|
|
|
/>
|
2021-04-03 01:58:40 +08:00
|
|
|
<ApplyButton onClick={onApplyFilters}>Apply</ApplyButton>
|
|
|
|
</Drawer>
|
2021-02-25 15:27:58 -08:00
|
|
|
<Typography.Paragraph style={styles.resultSummary}>
|
|
|
|
Showing{' '}
|
|
|
|
<b>
|
|
|
|
{(page - 1) * pageSize} - {lastResultIndex}
|
|
|
|
</b>{' '}
|
|
|
|
of <b>{totalResults}</b> results
|
|
|
|
</Typography.Paragraph>
|
2021-03-23 15:18:32 -07:00
|
|
|
<ResultList<React.FC<ListProps<SearchResult>>>
|
2021-02-25 15:27:58 -08:00
|
|
|
header={
|
|
|
|
<Card bodyStyle={styles.resultHeaderCardBody} style={styles.resultHeaderCard as any}>
|
|
|
|
{entityRegistry.getIcon(type, 36, IconStyleType.ACCENT)}
|
|
|
|
</Card>
|
|
|
|
}
|
|
|
|
dataSource={results}
|
|
|
|
split={false}
|
2021-03-23 15:18:32 -07:00
|
|
|
renderItem={(searchResult, index) => (
|
2021-02-25 15:27:58 -08:00
|
|
|
<>
|
2021-05-11 15:41:42 -07:00
|
|
|
<List.Item onClick={() => onResultClick(searchResult, index)}>
|
|
|
|
{entityRegistry.renderSearchResult(type, searchResult)}
|
|
|
|
</List.Item>
|
2021-02-25 15:27:58 -08:00
|
|
|
{index < results.length - 1 && <Divider />}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
bordered
|
|
|
|
/>
|
|
|
|
<Row justify="center" style={styles.paginationRow}>
|
|
|
|
<Pagination
|
|
|
|
current={page}
|
2021-03-04 23:18:50 -08:00
|
|
|
pageSize={SearchCfg.RESULTS_PER_PAGE}
|
2021-02-25 15:27:58 -08:00
|
|
|
total={totalResults}
|
|
|
|
showLessItems
|
|
|
|
onChange={onChangePage}
|
2021-04-27 16:12:03 -07:00
|
|
|
showSizeChanger={false}
|
2021-02-25 15:27:58 -08:00
|
|
|
/>
|
|
|
|
</Row>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|