feat(react): show search suggestions (#2270)

This commit is contained in:
Gabe Lyons 2021-03-19 15:40:48 -07:00 committed by GitHub
parent 183f7259c7
commit aea35c1417
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 192 additions and 12 deletions

View File

@ -161,6 +161,7 @@ const dataset2 = {
};
const dataset3 = {
__typename: 'Dataset',
urn: 'urn:li:dataset:3',
type: EntityType.Dataset,
platform: {
@ -237,6 +238,11 @@ const dataset3 = {
deprecation: null,
} as Dataset;
const dataset4 = {
...dataset3,
name: 'Fourth Test Dataset',
};
const sampleTag = {
urn: 'urn:li:tag:abc-sample-tag',
name: 'abc-sample-tag',
@ -755,6 +761,60 @@ export const mocks = [
} as GetSearchResultsQuery,
},
},
{
request: {
query: GetSearchResultsDocument,
variables: {
input: {
type: 'DATASET',
query: '*',
start: 0,
count: 20,
filters: [],
},
},
},
result: {
data: {
__typename: 'Query',
search: {
__typename: 'SearchResults',
start: 0,
count: 1,
total: 1,
entities: [
{
__typename: 'Dataset',
...dataset3,
},
{
__typename: 'Dataset',
...dataset4,
},
],
facets: [
{
field: 'origin',
aggregations: [
{
value: 'PROD',
count: 3,
},
],
},
{
field: 'platform',
aggregations: [
{ value: 'hdfs', count: 1 },
{ value: 'mysql', count: 1 },
{ value: 'kafka', count: 1 },
],
},
],
},
} as GetSearchResultsQuery,
},
},
{
request: {
query: GetTagDocument,

View File

@ -1,15 +1,17 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useHistory } from 'react-router';
import { Typography, Image, Space, AutoComplete, Input, Row } from 'antd';
import { Typography, Image, AutoComplete, Input, Row, Button, Carousel } from 'antd';
import styled, { useTheme } from 'styled-components';
import { ManageAccount } from '../shared/ManageAccount';
import { useGetAuthenticatedUser } from '../useGetAuthenticatedUser';
import { useEntityRegistry } from '../useEntityRegistry';
import { navigateToSearchUrl } from '../search/utils/navigateToSearchUrl';
import { useGetAutoCompleteResultsLazyQuery } from '../../graphql/search.generated';
import { GetSearchResultsQuery, useGetAutoCompleteResultsLazyQuery } from '../../graphql/search.generated';
import { useGetAllEntitySearchResults } from '../../utils/customGraphQL/useGetAllEntitySearchResults';
import { EntityType } from '../../types.generated';
const Background = styled(Space)`
const Background = styled.div`
width: 100%;
background-image: linear-gradient(
${(props) => props.theme.styles['homepage-background-upper-fade']},
@ -25,12 +27,63 @@ const WelcomeText = styled(Typography.Text)`
const styles = {
navBar: { padding: '24px' },
searchContainer: { width: '100%', marginTop: '40px', marginBottom: '160px' },
searchContainer: { width: '100%', marginTop: '40px' },
logoImage: { width: 140 },
searchBox: { width: 540, margin: '40px 0px' },
subHeaderText: { color: '#FFFFFF', fontSize: 20 },
subHeaderLabel: { marginTop: '-16px', color: '#FFFFFF', fontSize: 12 },
};
const CarouselElement = styled.div`
height: 120px;
color: #fff;
line-height: 120px;
text-align: center;
`;
const CarouselContainer = styled.div`
margin-top: -24px;
padding-bottom: 40px;
`;
const HeaderContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;
function getSuggestionFieldsFromResult(result: GetSearchResultsQuery): string[] {
return (
(result?.search?.entities
?.map((entity) => {
switch (entity.__typename) {
case 'Dataset':
return entity.name.split('.').slice(-1)[0];
case 'CorpUser':
return entity.username;
case 'Chart':
return entity.info?.name;
case 'Dashboard':
return entity.info?.name;
default:
return undefined;
}
})
.filter(Boolean) as string[]) || []
);
}
function truncate(input, length) {
if (input.length > length) {
return `${input.substring(0, length)}...`;
}
return input;
}
function sortRandom() {
return 0.5 - Math.random();
}
export const HomePageHeader = () => {
const history = useHistory();
const entityRegistry = useEntityRegistry();
@ -57,8 +110,37 @@ export const HomePageHeader = () => {
});
};
// fetch some results from each entity to display search suggestions
const allSearchResultsByType = useGetAllEntitySearchResults({
query: '*',
start: 0,
count: 20,
filters: [],
});
const suggestionsLoading = Object.keys(allSearchResultsByType).some((type) => {
return allSearchResultsByType[type].loading;
});
const suggestionsToShow = useMemo(() => {
let result: string[] = [];
if (!suggestionsLoading) {
[EntityType.Dashboard, EntityType.Chart, EntityType.Dataset].forEach((type) => {
const suggestionsToShowForEntity = getSuggestionFieldsFromResult(
allSearchResultsByType[type]?.data,
).sort(sortRandom);
const suggestionToAddToFront = suggestionsToShowForEntity?.pop();
result = [...result, ...suggestionsToShowForEntity];
if (suggestionToAddToFront) {
result.splice(0, 0, suggestionToAddToFront);
}
});
}
return result;
}, [suggestionsLoading, allSearchResultsByType]);
return (
<Background direction="vertical">
<Background>
<Row justify="space-between" style={styles.navBar}>
<WelcomeText>
{data && (
@ -72,7 +154,7 @@ export const HomePageHeader = () => {
pictureLink={data?.corpUser?.editableInfo?.pictureLink || ''}
/>
</Row>
<Space direction="vertical" align="center" style={styles.searchContainer}>
<HeaderContainer>
<Image src={themeConfig.assets.logoUrl} preview={false} style={styles.logoImage} />
<AutoComplete
style={styles.searchBox}
@ -88,11 +170,36 @@ export const HomePageHeader = () => {
data-testid="search-input"
/>
</AutoComplete>
<Typography.Text style={styles.subHeaderText}>
{themeConfig.content.homepage.homepageMessage}
</Typography.Text>
</Space>
{suggestionsToShow.length === 0 && !suggestionsLoading && (
<Typography.Text style={styles.subHeaderText}>
{themeConfig.content.homepage.homepageMessage}
</Typography.Text>
)}
<Typography.Text style={styles.subHeaderLabel}>Try searching for...</Typography.Text>
</HeaderContainer>
<CarouselContainer>
<Carousel autoplay>
{suggestionsToShow.length > 0 &&
suggestionsToShow.slice(0, 3).map((suggestion) => (
<CarouselElement>
<Button
type="text"
style={styles.subHeaderText}
onClick={() =>
navigateToSearchUrl({
type: undefined,
query: suggestion,
history,
entityRegistry,
})
}
>
{truncate(suggestion, 40)}
</Button>
</CarouselElement>
))}
</Carousel>
</CarouselContainer>
</Background>
);
};

View File

@ -41,4 +41,17 @@ describe('HomePage', () => {
await waitFor(() => expect(queryByTitle('The Great Test Dataset')).toBeInTheDocument());
await waitFor(() => expect(queryByTitle('Some other test')).toBeInTheDocument());
});
it('renders search suggestions', async () => {
const { getByText, queryAllByText } = render(
<MockedProvider mocks={mocks} addTypename>
<TestPageContainer>
<HomePage />
</TestPageContainer>
</MockedProvider>,
);
await waitFor(() => expect(getByText('Try searching for...')).toBeInTheDocument());
expect(queryAllByText('Yet Another Dataset').length).toBeGreaterThanOrEqual(1);
expect(queryAllByText('Fourth Test Dataset').length).toBeGreaterThanOrEqual(1);
});
});