Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java/ui) Add search suggestions to our search experience #8710

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4,9 +4,11 @@
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;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -57,4 +59,8 @@ public static List<MatchedField> getMatchedFieldEntry(List<com.linkedin.metadata
.map(field -> new MatchedField(field.getName(), field.getValue()))
.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 @@ -722,6 +732,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 @@ -3450,6 +3470,7 @@ export const mocks = [
count: 10,
filters: [],
orFilters: [],
searchFlags: { getSuggestions: true },
},
},
},
Expand All @@ -3461,6 +3482,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
Loading