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

Combine siblings in autocomplete #8610

Merged
merged 21 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 19 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

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions datahub-web-react/src/app/entity/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ANTD_GRAY = {
export const ANTD_GRAY_V2 = {
2: '#F3F5F6',
5: '#DDE0E4',
6: '#B2B8BD',
8: '#5E666E',
10: '#1B1E22',
};
Expand Down
80 changes: 38 additions & 42 deletions datahub-web-react/src/app/entity/shared/siblingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import merge from 'deepmerge';
import { unionBy, keyBy, values } from 'lodash';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we have a conflict from my changes to display consistent combined search results for sibling entities in this PR: https://github.com/datahub-project/datahub/pull/8602/files#diff-dbeb733a628595000d108fe2f57b5c464d52b6da55f57d57ad4bc6e246af3914

so let's make sure we keep that functionlity! the main thing I did is simply combine the siblings entity data in combineSiblingsInSearchResults and refactored a function above it to make it reusable between its two uses

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think I got the conflict resolved 👍

import { useLocation } from 'react-router-dom';
import * as QueryString from 'query-string';
import { Dataset, Entity, MatchedField, Maybe, SiblingProperties } from '../../../types.generated';
import { Dataset, Entity, Maybe, SiblingProperties } from '../../../types.generated';
import { GenericEntityProperties } from './types';

export function stripSiblingsFromEntity(entity: any) {
return {
Expand Down Expand Up @@ -202,53 +203,48 @@ export const combineEntityDataWithSiblings = <T>(baseEntity: T): T => {
return { [baseEntityKey]: combinedBaseEntity } as unknown as T;
};

export type CombinedSearchResult = {
export type CombinedEntity = {
entity: Entity;
matchedFields: MatchedField[];
matchedEntities?: Entity[];
matchedEntities?: Array<Entity>;
};

export function combineSiblingsInSearchResults(
results:
| {
entity: Entity;
matchedFields: MatchedField[];
}[]
| undefined,
) {
const combinedResults: CombinedSearchResult[] | undefined = [];
const siblingsToPair: Record<string, CombinedSearchResult> = {};

// set sibling associations
results?.forEach((result) => {
if (result.entity.urn in siblingsToPair) {
// filter from repeating
// const siblingsCombinedResult = siblingsToPair[result.entity.urn];
// siblingsCombinedResult.matchedEntities?.push(result.entity);
return;
}

const combinedResult: CombinedSearchResult = result;
const { entity }: { entity: any } = result;
const siblingUrns = entity?.siblings?.siblings?.map((sibling) => sibling.urn) || [];
if (siblingUrns.length > 0) {
combinedResult.matchedEntities = entity.siblings.isPrimary
? [stripSiblingsFromEntity(entity), ...entity.siblings.siblings]
: [...entity.siblings.siblings, stripSiblingsFromEntity(entity)];

combinedResult.matchedEntities = combinedResult.matchedEntities.filter(
(resultToFilter) => (resultToFilter as Dataset).exists,
);
type CombinedEntityResult =
| {
skipped: true;
}
| {
skipped: false;
combinedEntity: CombinedEntity;
};

export function combineSiblingsForEntity(entity: Entity, visitedSiblingUrns: Set<string>): CombinedEntityResult {
if (visitedSiblingUrns.has(entity.urn)) return { skipped: true };

const combinedEntity: CombinedEntity = { entity };
const siblings = (entity as GenericEntityProperties).siblings?.siblings ?? [];
const isPrimary = (entity as GenericEntityProperties).siblings?.isPrimary;
const siblingUrns = siblings.map((sibling) => sibling?.urn);

if (siblingUrns.length > 0) {
combinedEntity.matchedEntities = isPrimary
? [stripSiblingsFromEntity(entity), ...siblings]
: [...siblings, stripSiblingsFromEntity(entity)];

combinedEntity.matchedEntities = combinedEntity.matchedEntities.filter(
(resultToFilter) => (resultToFilter as Dataset).exists,
);

siblingUrns.forEach((urn) => urn && visitedSiblingUrns.add(urn));
}

siblingUrns.forEach((urn) => {
siblingsToPair[urn] = combinedResult;
});
}
combinedResults.push(combinedResult);
});
return { combinedEntity, skipped: false };
}

return combinedResults;
export function createSiblingEntityCombiner() {
const visitedSiblingUrns: Set<string> = new Set();
return (entity: Entity) => combineSiblingsForEntity(entity, visitedSiblingUrns);
}

// used to determine whether sibling entities should be shown merged or not
export const SEPARATE_SIBLINGS_URL_PARAM = 'separate_siblings';

Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/app/home/HomePageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export const HomePageHeader = () => {
autoCompleteStyle={styles.searchBox}
entityRegistry={entityRegistry}
viewsEnabled={viewsEnabled}
combineSiblings
showQuickFilters
/>
{searchResultsToShow && searchResultsToShow.length > 0 && (
Expand Down
1 change: 0 additions & 1 deletion datahub-web-react/src/app/preview/DefaultPreviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ export default function DefaultPreviewCard({
/>
)}
</EntityTitleContainer>

{degree !== undefined && degree !== null && (
<Tooltip
title={`This entity is a ${getNumberWithOrdinal(degree)} degree connection to ${
Expand Down
42 changes: 24 additions & 18 deletions datahub-web-react/src/app/search/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Input, AutoComplete, Button } from 'antd';
import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons';
import styled from 'styled-components/macro';
import { useHistory } from 'react-router';
import { AutoCompleteResultForEntity, Entity, EntityType, FacetFilterInput, ScenarioType } from '../../types.generated';
import { AutoCompleteResultForEntity, EntityType, FacetFilterInput, ScenarioType } from '../../types.generated';
import EntityRegistry from '../entity/EntityRegistry';
import filterSearchQuery from './utils/filterSearchQuery';
import { ANTD_GRAY, ANTD_GRAY_V2 } from '../entity/shared/constants';
Expand All @@ -23,6 +23,7 @@ import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
import { getQuickFilterDetails } from './autoComplete/quickFilters/utils';
import ViewAllSearchItem from './ViewAllSearchItem';
import { ViewSelect } from '../entity/view/select/ViewSelect';
import { combineSiblingsInAutoComplete } from './utils/combineSiblingsInAutoComplete';

const StyledAutoComplete = styled(AutoComplete)`
width: 100%;
Expand Down Expand Up @@ -88,15 +89,6 @@ const QUICK_FILTER_AUTO_COMPLETE_OPTION = {
],
};

const renderItem = (query: string, entity: Entity) => {
return {
value: entity.urn,
label: <AutoCompleteItem query={query} entity={entity} />,
type: entity.type,
style: { padding: '12px 12px 12px 16px' },
};
};

const renderRecommendedQuery = (query: string) => {
return {
value: query,
Expand All @@ -123,6 +115,7 @@ interface Props {
hideRecommendations?: boolean;
showQuickFilters?: boolean;
viewsEnabled?: boolean;
combineSiblings?: boolean;
setIsSearchBarFocused?: (isSearchBarFocused: boolean) => void;
onFocus?: () => void;
onBlur?: () => void;
Expand All @@ -149,6 +142,7 @@ export const SearchBar = ({
hideRecommendations,
showQuickFilters,
viewsEnabled = false,
combineSiblings = false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we default want to not combine siblings in our search bar? is there somewhere where we particularly don't want them combined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right the idea was to make siblings an opt-in behavior, where the home/searchResults pages have that behavior but not assume that all use cases in the UI would get that behavior was my thought.

setIsSearchBarFocused,
onFocus,
onBlur,
Expand Down Expand Up @@ -227,14 +221,26 @@ export const SearchBar = ({
];
}, [showQuickFilters, suggestions.length, effectiveQuery, selectedQuickFilter, entityRegistry]);

const autoCompleteEntityOptions = useMemo(
() =>
suggestions.map((entity: AutoCompleteResultForEntity) => ({
label: <SectionHeader entityType={entity.type} />,
options: [...entity.entities.map((e: Entity) => renderItem(effectiveQuery, e))],
})),
[effectiveQuery, suggestions],
);
const autoCompleteEntityOptions = useMemo(() => {
return suggestions.map((suggestion: AutoCompleteResultForEntity) => {
const combinedSuggestion = combineSiblingsInAutoComplete(suggestion, { combineSiblings });
return {
label: <SectionHeader entityType={combinedSuggestion.type} />,
options: combinedSuggestion.combinedEntities.map((combinedEntity) => ({
value: combinedEntity.entity.urn,
label: (
<AutoCompleteItem
query={effectiveQuery}
entity={combinedEntity.entity}
siblings={combineSiblings ? combinedEntity.matchedEntities : undefined}
/>
),
type: combinedEntity.entity.type,
style: { padding: '12px 12px 12px 16px' },
})),
};
});
}, [combineSiblings, effectiveQuery, suggestions]);

const previousSelectedQuickFilterValue = usePrevious(selectedQuickFilter?.value);
useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/app/search/SearchHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const SearchHeader = ({
entityRegistry={entityRegistry}
setIsSearchBarFocused={setIsSearchBarFocused}
viewsEnabled={viewsEnabled}
combineSiblings
fixAutoComplete
showQuickFilters
/>
Expand Down
3 changes: 2 additions & 1 deletion datahub-web-react/src/app/search/SearchResultList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { useHistory } from 'react-router';
import { RocketOutlined } from '@ant-design/icons';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
import { ANTD_GRAY } from '../entity/shared/constants';
import { CombinedSearchResult, SEPARATE_SIBLINGS_URL_PARAM } from '../entity/shared/siblingUtils';
import { SEPARATE_SIBLINGS_URL_PARAM } from '../entity/shared/siblingUtils';
import { CompactEntityNameList } from '../recommendations/renderer/component/CompactEntityNameList';
import { useEntityRegistry } from '../useEntityRegistry';
import { SearchResult } from '../../types.generated';
import analytics, { EventType } from '../analytics';
import { EntityAndType } from '../entity/shared/types';
import { useIsSearchV2 } from './useSearchAndBrowseVersion';
import { CombinedSearchResult } from './utils/combineSiblingsInSearchResults';

const ResultList = styled(List)`
&&& {
Expand Down
2 changes: 1 addition & 1 deletion datahub-web-react/src/app/search/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Entity, FacetFilterInput, FacetMetadata, MatchedField } from '../../typ
import { SearchCfg } from '../../conf';
import { SearchResultsRecommendations } from './SearchResultsRecommendations';
import SearchExtendedMenu from '../entity/shared/components/styled/search/SearchExtendedMenu';
import { combineSiblingsInSearchResults } from '../entity/shared/siblingUtils';
import { SearchSelectBar } from '../entity/shared/components/styled/search/SearchSelectBar';
import { SearchResultList } from './SearchResultList';
import { isListSubset } from '../entity/shared/utils';
Expand All @@ -26,6 +25,7 @@ import { BrowseProvider } from './sidebar/BrowseContext';
import { useIsBrowseV2, useIsSearchV2 } from './useSearchAndBrowseVersion';
import useToggleSidebar from './useToggleSidebar';
import SearchSortSelect from './sorting/SearchSortSelect';
import { combineSiblingsInSearchResults } from './utils/combineSiblingsInSearchResults';

const SearchResultsWrapper = styled.div<{ v2Styles: boolean }>`
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Image, Typography } from 'antd';
import { Typography } from 'antd';
import React from 'react';
import styled from 'styled-components/macro';
import { Entity } from '../../../types.generated';
import { useEntityRegistry } from '../../useEntityRegistry';
import { getPlatformName } from '../../entity/shared/utils';
import { IconStyleType } from '../../entity/Entity';
import { getAutoCompleteEntityText } from './utils';
import { SuggestionText } from './AutoCompleteUser';
import ParentContainers from './ParentContainers';
import { ANTD_GRAY } from '../../entity/shared/constants';
import { ANTD_GRAY_V2 } from '../../entity/shared/constants';
import AutoCompleteEntityIcon from './AutoCompleteEntityIcon';
import { SuggestionText } from './styledComponents';
import AutoCompletePlatformNames from './AutoCompletePlatformNames';
import { capitalizeFirstLetterOnly } from '../../shared/textUtil';

const AutoCompleteEntityWrapper = styled.div`
display: flex;
Expand All @@ -17,12 +18,8 @@ const AutoCompleteEntityWrapper = styled.div`
align-items: center;
`;

const PreviewImage = styled(Image)`
height: 22px;
width: 22px;
width: auto;
object-fit: contain;
background-color: transparent;
const IconsContainer = styled.div`
display: flex;
`;

const ContentWrapper = styled.div`
Expand All @@ -32,42 +29,82 @@ const ContentWrapper = styled.div`
`;

const Subtype = styled.span`
color: ${ANTD_GRAY[9]};
border: 1px solid ${ANTD_GRAY[9]};
color: ${ANTD_GRAY_V2[8]};
border: 1px solid ${ANTD_GRAY_V2[6]};
border-radius: 16px;
padding: 4px 8px;
line-height: 12px;
font-size: 12px;
margin-right: 8px;
`;

const ItemHeader = styled.div`
display: flex;
align-items: center;
margin-bottom: 3px;
gap: 8px;
`;

const Divider = styled.div`
border-right: 1px solid ${ANTD_GRAY_V2[6]};
height: 12px;
`;

interface Props {
query: string;
entity: Entity;
siblings?: Array<Entity>;
hasParentTooltip: boolean;
}

export default function AutoCompleteEntity({ query, entity, hasParentTooltip }: Props) {
export default function AutoCompleteEntity({ query, entity, siblings, hasParentTooltip }: Props) {
const entityRegistry = useEntityRegistry();
const genericEntityProps = entityRegistry.getGenericEntityProperties(entity.type, entity);
const platformName = getPlatformName(genericEntityProps);
const platformLogoUrl = genericEntityProps?.platform?.properties?.logoUrl;
const displayName = entityRegistry.getDisplayName(entity.type, entity);
const icon =
(platformLogoUrl && <PreviewImage preview={false} src={platformLogoUrl} alt={platformName || ''} />) ||
entityRegistry.getIcon(entity.type, 12, IconStyleType.ACCENT);
const { matchedText, unmatchedText } = getAutoCompleteEntityText(displayName, query);
const parentContainers = genericEntityProps?.parentContainers?.containers || [];
// Need to reverse parentContainers since it returns direct parent first.
const orderedParentContainers = [...parentContainers].reverse();
const subtype = genericEntityProps?.subTypes?.typeNames?.[0];
const entities = siblings?.length ? siblings : [entity];

const platformNames = entities
.map((ent) => {
const genericPropsForEnt = entityRegistry.getGenericEntityProperties(ent.type, ent);
const platform = genericPropsForEnt?.platform;
if (platform?.properties?.displayName) return platform.properties.displayName;
if (platform?.name) return capitalizeFirstLetterOnly(platform.name) || '';
return '';
})
.filter(Boolean);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i feel like this could be pulled out into its own function. Also I see elsewhere that we have siblingPlatforms on genericProperties that you can maybe use here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! I left the iteration in this component but reused an existing helper function we already have.


const parentContainers =
entities
.map((ent) => {
const genericPropsForEnt = entityRegistry.getGenericEntityProperties(ent.type, ent);
const entParentContainers = genericPropsForEnt?.parentContainers?.containers || [];
return [...entParentContainers].reverse();
})
.find((containers) => containers.length > 0) ?? [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should be taken care of when we combine sibling data so we end up just getting one parentContainers based on the combined data.

In fact, with my changes in #8602 we can now get proper parentContainers combined so you can just use the combined entity here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. So like if one sibling had no parentContainers and another one missed them, it would automatically choose the one that had them present?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seem to work thought! Shows the bigquery containers even on the dbt primary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup exactly! so it'll choose the primary if both fields are filled out, but if one is filled out and not the other, it'll take the non-empty one


const showPlatforms = !!platformNames.length;
const showPlatformDivider = !!platformNames.length && !!parentContainers.length;
const showParentContainers = !!parentContainers.length;
const showHeader = showPlatforms || showParentContainers;

return (
<AutoCompleteEntityWrapper data-testid={`auto-complete-entity-name-${displayName}`}>
<ContentWrapper>
{icon}
<SuggestionText>
<ParentContainers parentContainers={orderedParentContainers} />
{showHeader && (
<ItemHeader>
<IconsContainer>
{entities.map((ent) => (
<AutoCompleteEntityIcon key={ent.urn} entity={ent} />
))}
</IconsContainer>
{showPlatforms && <AutoCompletePlatformNames platforms={platformNames} />}
{showPlatformDivider && <Divider />}
{showParentContainers && <ParentContainers parentContainers={parentContainers} />}
</ItemHeader>
)}
<Typography.Text
ellipsis={
hasParentTooltip ? {} : { tooltip: { title: displayName, color: 'rgba(0, 0, 0, 0.9)' } }
Expand Down
Loading