Skip to content

Commit

Permalink
feat(java/ui) Add search suggestions to our search experience (#8710)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriscollins3456 authored Aug 24, 2023
1 parent 8648126 commit b6141f5
Show file tree
Hide file tree
Showing 17 changed files with 341 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public com.linkedin.metadata.query.SearchFlags apply(@Nonnull final SearchFlags
if (searchFlags.getSkipAggregates() != null) {
result.setSkipAggregates(searchFlags.getSkipAggregates());
}
if (searchFlags.getGetSuggestions() != null) {
result.setGetSuggestions(searchFlags.getGetSuggestions());
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.linkedin.datahub.graphql.generated.FacetMetadata;
import com.linkedin.datahub.graphql.generated.MatchedField;
import com.linkedin.datahub.graphql.generated.SearchResult;
import com.linkedin.datahub.graphql.generated.SearchSuggestion;
import com.linkedin.datahub.graphql.resolvers.EntityTypeMapper;
import com.linkedin.datahub.graphql.types.common.mappers.UrnToEntityMapper;
import com.linkedin.metadata.search.SearchEntity;
Expand Down Expand Up @@ -76,4 +77,8 @@ public static List<MatchedField> getMatchedFieldEntry(List<com.linkedin.metadata
})
.collect(Collectors.toList());
}

public static SearchSuggestion mapSearchSuggestion(com.linkedin.metadata.search.SearchSuggestion suggestion) {
return new SearchSuggestion(suggestion.getText(), suggestion.getScore(), Math.toIntExact(suggestion.getFrequency()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public SearchResults apply(com.linkedin.metadata.search.SearchResult input) {
final SearchResultMetadata searchResultMetadata = input.getMetadata();
result.setSearchResults(input.getEntities().stream().map(MapperUtils::mapResult).collect(Collectors.toList()));
result.setFacets(searchResultMetadata.getAggregations().stream().map(MapperUtils::mapFacet).collect(Collectors.toList()));
result.setSuggestions(searchResultMetadata.getSuggestions().stream().map(MapperUtils::mapSearchSuggestion).collect(Collectors.toList()));

return result;
}
Expand Down
35 changes: 35 additions & 0 deletions datahub-graphql-core/src/main/resources/search.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ input SearchFlags {
Whether to skip aggregates/facets
"""
skipAggregates: Boolean

"""
Whether to request for search suggestions on the _entityName virtualized field
"""
getSuggestions: Boolean
}

"""
Expand Down Expand Up @@ -483,6 +488,11 @@ type SearchResults {
Candidate facet aggregations used for search filtering
"""
facets: [FacetMetadata!]

"""
Search suggestions based on the query provided for alternate query texts
"""
suggestions: [SearchSuggestion!]
}

"""
Expand Down Expand Up @@ -727,6 +737,31 @@ type AggregationMetadata {
entity: Entity
}

"""
A suggestion for an alternate search query given an original query compared to all
of the entity names in our search index.
"""
type SearchSuggestion {
"""
The suggested text based on the provided query text compared to
the entity name field in the search index.
"""
text: String!

"""
The "edit distance" for this suggestion. The closer this number is to 1, the
closer the suggested text is to the original text. The closer it is to 0, the
further from the original text it is.
"""
score: Float

"""
The number of entities that would match on the name field given the suggested text
"""
frequency: Int
}


"""
Input for performing an auto completion query against a single Metadata Entity
"""
Expand Down
22 changes: 22 additions & 0 deletions datahub-web-react/src/Mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,7 @@ export const mocks = [
count: 10,
filters: [],
orFilters: [],
searchFlags: { getSuggestions: true },
},
},
},
Expand Down Expand Up @@ -2033,6 +2034,7 @@ export const mocks = [
],
},
],
suggestions: [],
},
} as GetSearchResultsQuery,
},
Expand All @@ -2059,6 +2061,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand Down Expand Up @@ -2112,6 +2115,7 @@ export const mocks = [
],
},
],
suggestions: [],
},
} as GetSearchResultsQuery,
},
Expand Down Expand Up @@ -2230,6 +2234,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -2251,6 +2256,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -2772,6 +2778,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -2794,6 +2801,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
__typename: 'FacetMetadata',
Expand Down Expand Up @@ -2886,6 +2894,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -2908,6 +2917,7 @@ export const mocks = [
},
],
facets: [],
suggestions: [],
},
} as GetSearchResultsForMultipleQuery,
},
Expand All @@ -2934,6 +2944,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -2955,6 +2966,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -3007,6 +3019,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -3028,6 +3041,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -3084,6 +3098,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand Down Expand Up @@ -3113,6 +3128,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -3175,6 +3191,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -3196,6 +3213,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -3258,6 +3276,7 @@ export const mocks = [
],
},
],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -3279,6 +3298,7 @@ export const mocks = [
insights: [],
},
],
suggestions: [],
facets: [
{
field: 'origin',
Expand Down Expand Up @@ -3451,6 +3471,7 @@ export const mocks = [
count: 10,
filters: [],
orFilters: [],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -3462,6 +3483,7 @@ export const mocks = [
total: 0,
searchResults: [],
facets: [],
suggestions: [],
},
},
},
Expand Down
90 changes: 90 additions & 0 deletions datahub-web-react/src/app/search/EmptySearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { RocketOutlined } from '@ant-design/icons';
import { useHistory } from 'react-router';
import { Button } from 'antd';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import { ANTD_GRAY_V2 } from '../entity/shared/constants';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
import analytics, { EventType } from '../analytics';
import { SuggestedText } from './suggestions/SearchQuerySugggester';
import useGetSearchQueryInputs from './useGetSearchQueryInputs';
import { FacetFilterInput, SearchSuggestion } from '../../types.generated';
import { useUserContext } from '../context/useUserContext';

const NoDataContainer = styled.div`
margin: 40px auto;
font-size: 16px;
color: ${ANTD_GRAY_V2[8]};
`;

const Section = styled.div`
margin-bottom: 16px;
`;

function getRefineSearchText(filters: FacetFilterInput[], viewUrn?: string | null) {
let text = '';
if (filters.length && viewUrn) {
text = 'clearing all filters and selected view';
} else if (filters.length) {
text = 'clearing all filters';
} else if (viewUrn) {
text = 'clearing the selected view';
}

return text;
}

interface Props {
suggestions: SearchSuggestion[];
}

export default function EmptySearchResults({ suggestions }: Props) {
const { query, filters, viewUrn } = useGetSearchQueryInputs();
const history = useHistory();
const userContext = useUserContext();
const suggestText = suggestions.length > 0 ? suggestions[0].text : '';
const refineSearchText = getRefineSearchText(filters, viewUrn);

const onClickExploreAll = useCallback(() => {
analytics.event({ type: EventType.SearchResultsExploreAllClickEvent });
navigateToSearchUrl({ query: '*', history });
}, [history]);

const searchForSuggestion = () => {
navigateToSearchUrl({ query: suggestText, history });
};

const clearFiltersAndView = () => {
navigateToSearchUrl({ query, history });
userContext.updateLocalState({
...userContext.localState,
selectedViewUrn: undefined,
});
};

return (
<NoDataContainer>
<Section>No results found for &quot;{query}&quot;</Section>
{refineSearchText && (
<>
Try <SuggestedText onClick={clearFiltersAndView}>{refineSearchText}</SuggestedText>{' '}
{suggestText && (
<>
or searching for <SuggestedText onClick={searchForSuggestion}>{suggestText}</SuggestedText>
</>
)}
</>
)}
{!refineSearchText && suggestText && (
<>
Did you mean <SuggestedText onClick={searchForSuggestion}>{suggestText}</SuggestedText>
</>
)}
{!refineSearchText && !suggestText && (
<Button onClick={onClickExploreAll}>
<RocketOutlined /> Explore all
</Button>
)}
</NoDataContainer>
);
}
2 changes: 2 additions & 0 deletions datahub-web-react/src/app/search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const SearchPage = () => {
orFilters,
viewUrn,
sortInput,
searchFlags: { getSuggestions: true },
},
},
});
Expand Down Expand Up @@ -235,6 +236,7 @@ export const SearchPage = () => {
error={error}
searchResponse={data?.searchAcrossEntities}
facets={data?.searchAcrossEntities?.facets}
suggestions={data?.searchAcrossEntities?.suggestions || []}
selectedFilters={filters}
loading={loading}
onChangeFilters={onChangeFilters}
Expand Down
Loading

0 comments on commit b6141f5

Please sign in to comment.