From 78a5c8a1359b3b0137ebd683994cad01d0f14f43 Mon Sep 17 00:00:00 2001 From: John Joyce Date: Wed, 2 Nov 2022 15:59:16 -0700 Subject: [PATCH 1/2] Improving how we handle fixed filters --- .../entity/container/ContainerEntitiesTab.tsx | 6 +- .../app/entity/domain/DomainEntitiesTab.tsx | 6 +- .../profile/GlossaryRelatedEntity.tsx | 39 ++++- .../src/app/entity/group/GroupAssets.tsx | 6 +- .../styled/search/DownloadAsCsvModal.tsx | 6 +- .../styled/search/EmbeddedListSearch.tsx | 45 +++-- .../search/EmbeddedListSearchHeader.tsx | 4 +- .../styled/search/EmbeddedListSearchModal.tsx | 7 +- .../search/EmbeddedListSearchSection.tsx | 10 +- .../styled/search/SearchExtendedMenu.tsx | 4 +- .../search/navigateToEntitySearchUrl.ts | 4 + .../shared/components/styled/search/types.ts | 10 ++ .../src/app/entity/user/UserAssets.tsx | 6 +- .../src/app/ingest/source/IngestedAssets.tsx | 6 +- .../src/app/search/SearchFiltersSection.tsx | 2 +- .../src/app/search/SearchResults.tsx | 3 +- .../src/app/search/utils/constants.ts | 6 +- .../src/app/search/utils/filterUtils.ts | 160 ++++++++++++++++++ .../src/app/shared/TagStyleEntity.tsx | 33 +++- 19 files changed, 315 insertions(+), 48 deletions(-) create mode 100644 datahub-web-react/src/app/search/utils/filterUtils.ts diff --git a/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx b/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx index 7aaa5b2c6ee81e..7d921d0c9c37cb 100644 --- a/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx +++ b/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useEntityData } from '../shared/EntityContext'; import { EmbeddedListSearchSection } from '../shared/components/styled/search/EmbeddedListSearchSection'; +import { UnionType } from '../../search/utils/constants'; export const ContainerEntitiesTab = () => { const { urn } = useEntityData(); @@ -12,7 +13,10 @@ export const ContainerEntitiesTab = () => { return ( diff --git a/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx b/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx index 8fe2b9c1178754..ff8bec27445bc2 100644 --- a/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx +++ b/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useEntityData } from '../shared/EntityContext'; import { EntityType } from '../../../types.generated'; import { EmbeddedListSearchSection } from '../shared/components/styled/search/EmbeddedListSearchSection'; +import { UnionType } from '../../search/utils/constants'; export const DomainEntitiesTab = () => { const { urn, entityType } = useEntityData(); @@ -17,7 +18,10 @@ export const DomainEntitiesTab = () => { return ( diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx index 91f160fb20e72f..da921adb3887a6 100644 --- a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx @@ -1,20 +1,49 @@ import * as React from 'react'; +import { UnionType } from '../../../search/utils/constants'; import { EmbeddedListSearchSection } from '../../shared/components/styled/search/EmbeddedListSearchSection'; import { useEntityData } from '../../shared/EntityContext'; export default function GlossaryRelatedEntity() { const { entityData }: any = useEntityData(); - const glossaryTermHierarchicalName = entityData?.hierarchicalName; - let fixedQueryString = `glossaryTerms:"${glossaryTermHierarchicalName}" OR fieldGlossaryTerms:"${glossaryTermHierarchicalName}" OR editedFieldGlossaryTerms:"${glossaryTermHierarchicalName}"`; + + const entityUrn = entityData?.urn; + + const fixedOrFilters = + (entityUrn && [ + { + field: 'glossaryTerms', + values: [entityUrn], + }, + { + field: 'fieldGlossaryTerms', + values: [entityUrn], + }, + ]) || + []; + entityData?.isAChildren?.relationships.forEach((term) => { - const name = term.entity?.hierarchicalName; - fixedQueryString += `OR glossaryTerms:"${name}" OR fieldGlossaryTerms:"${name}" OR editedFieldGlossaryTerms:"${name}"`; + const childUrn = term.entity?.urn; + + if (childUrn) { + fixedOrFilters.push({ + field: 'glossaryTerms', + values: [childUrn], + }); + + fixedOrFilters.push({ + field: 'fieldGlossaryTerms', + values: [childUrn], + }); + } }); return ( diff --git a/datahub-web-react/src/app/entity/group/GroupAssets.tsx b/datahub-web-react/src/app/entity/group/GroupAssets.tsx index 0417dc1b13b78e..75f9a38880ccc2 100644 --- a/datahub-web-react/src/app/entity/group/GroupAssets.tsx +++ b/datahub-web-react/src/app/entity/group/GroupAssets.tsx @@ -1,5 +1,6 @@ import React from 'react'; import styled from 'styled-components'; +import { UnionType } from '../../search/utils/constants'; import { EmbeddedListSearchSection } from '../shared/components/styled/search/EmbeddedListSearchSection'; const GroupAssetsWrapper = styled.div` @@ -14,7 +15,10 @@ export const GroupAssets = ({ urn }: Props) => { return ( diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/DownloadAsCsvModal.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/DownloadAsCsvModal.tsx index bcd5c6560bd4f7..1ea6b6bd6b4c12 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/DownloadAsCsvModal.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/DownloadAsCsvModal.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Button, Input, Modal } from 'antd'; import { useLocation } from 'react-router'; -import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; +import { EntityType, OrFilter, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; import { SearchResultsInterface } from './types'; import { getSearchCsvDownloadHeader, transformResultsToCsvRow } from './downloadAsCsvUtil'; import { downloadRowsAsCsv } from '../../../../../search/utils/csvUtils'; @@ -15,7 +15,7 @@ type Props = { input: SearchAcrossEntitiesInput; }) => Promise; entityFilters: EntityType[]; - filters: FacetFilterInput[]; + filters: OrFilter[]; query: string; setIsDownloadingCsv: (isDownloadingCsv: boolean) => any; showDownloadAsCsvModal: boolean; @@ -63,7 +63,7 @@ export default function DownloadAsCsvModal({ query, start: SEARCH_PAGE_SIZE_FOR_DOWNLOAD * downloadPage, count: SEARCH_PAGE_SIZE_FOR_DOWNLOAD, - filters, + orFilters: filters, }, }).then((refetchData) => { console.log('fetched data for page number ', downloadPage); diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx index 238d27faddfda8..57a29bd073e5c5 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx @@ -1,17 +1,18 @@ import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { ApolloError } from '@apollo/client'; -import { EntityType, FacetFilterInput } from '../../../../../../types.generated'; +import { EntityType, FacetFilterInput, FacetMetadata } from '../../../../../../types.generated'; import { ENTITY_FILTER_NAME, UnionType } from '../../../../../search/utils/constants'; import { SearchCfg } from '../../../../../../conf'; import { EmbeddedListSearchResults } from './EmbeddedListSearchResults'; import EmbeddedListSearchHeader from './EmbeddedListSearchHeader'; import { useGetSearchResultsForMultipleQuery } from '../../../../../../graphql/search.generated'; -import { GetSearchResultsParams, SearchResultsInterface } from './types'; +import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types'; import { isListSubset } from '../../../utils'; import { EntityAndType } from '../../../types'; import { Message } from '../../../../../shared/Message'; import { generateOrFilters } from '../../../../../search/utils/generateOrFilters'; +import { mergeFilterSets } from '../../../../../search/utils/filterUtils'; const Container = styled.div` display: flex; @@ -31,6 +32,7 @@ function useWrappedSearchResults(params: GetSearchResultsParams) { refetch(refetchParams).then((res) => res.data.searchAcrossEntities), }; } + // the addFixedQuery checks and generate the query as per params pass to embeddedListSearch export const addFixedQuery = (baseQuery: string, fixedQuery: string, emptyQuery: string) => { let finalQuery = ``; @@ -46,6 +48,13 @@ export const addFixedQuery = (baseQuery: string, fixedQuery: string, emptyQuery: return finalQuery; }; +// Simply remove the fields that were marked as fixed from the facets that the server +// responds. +export const removeFixedFiltersFromFacets = (fixedFilters: FilterSet, facets: FacetMetadata[]) => { + const fixedFields = fixedFilters.filters.map((filter) => filter.field); + return facets.filter((facet) => !(fixedFields.indexOf(facet.field) > -1)); +}; + type Props = { query: string; page: number; @@ -56,7 +65,7 @@ type Props = { onChangePage: (page) => void; onChangeUnionType: (unionType: UnionType) => void; emptySearchQuery?: string | null; - fixedFilter?: FacetFilterInput | null; + fixedFilters?: FilterSet; fixedQuery?: string | null; placeholderText?: string | null; defaultShowFilters?: boolean; @@ -81,7 +90,7 @@ export const EmbeddedListSearch = ({ onChangePage, onChangeUnionType, emptySearchQuery, - fixedFilter, + fixedFilters, fixedQuery, placeholderText, defaultShowFilters, @@ -97,7 +106,16 @@ export const EmbeddedListSearch = ({ const filtersWithoutEntities: Array = filters.filter( (filter) => filter.field !== ENTITY_FILTER_NAME, ); - const finalFilters = (fixedFilter && [...filtersWithoutEntities, fixedFilter]) || filtersWithoutEntities; + + const baseFilters = { + unionType, + filters: filtersWithoutEntities, + }; + + const finalFilters = + (fixedFilters && mergeFilterSets(fixedFilters, baseFilters)) || + generateOrFilters(unionType, filtersWithoutEntities); + const entityFilters: Array = filters .filter((filter) => filter.field === ENTITY_FILTER_NAME) .flatMap((filter) => filter.values?.map((value) => value?.toUpperCase() as EntityType) || []); @@ -114,8 +132,7 @@ export const EmbeddedListSearch = ({ query: finalQuery, start: (page - 1) * SearchCfg.RESULTS_PER_PAGE, count: SearchCfg.RESULTS_PER_PAGE, - filters: [], - orFilters: generateOrFilters(unionType, finalFilters), + orFilters: finalFilters, }, }, skip: true, @@ -132,8 +149,7 @@ export const EmbeddedListSearch = ({ query: finalQuery, start: (page - 1) * numResultsPerPage, count: numResultsPerPage, - filters: [], - orFilters: generateOrFilters(unionType, finalFilters), + orFilters: finalFilters, }, }, }); @@ -183,8 +199,13 @@ export const EmbeddedListSearch = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // Filter out the persistent filter values - const filteredFilters = data?.facets?.filter((facet) => facet.field !== fixedFilter?.field) || []; + /** + * Compute the final Facet fields that we show in the left hand search Filters (aggregation). + * + * Do this by filtering out any fields that are included in the fixed filters. + */ + const finalFacets = + (fixedFilters && removeFixedFiltersFromFacets(fixedFilters, data?.facets || [])) || data?.facets; return ( @@ -210,7 +231,7 @@ export const EmbeddedListSearch = ({ unionType={unionType} loading={loading} searchResponse={data} - filters={filteredFilters} + filters={finalFacets} selectedFilters={filters} onChangeFilters={onChangeFilters} onChangePage={onChangePage} diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchHeader.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchHeader.tsx index e37bc59d7664cc..e54b63de9ffc5c 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchHeader.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchHeader.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import TabToolbar from '../TabToolbar'; import { SearchBar } from '../../../../../search/SearchBar'; import { useEntityRegistry } from '../../../../../useEntityRegistry'; -import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; +import { EntityType, OrFilter, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; import { SearchResultsInterface } from './types'; import SearchExtendedMenu from './SearchExtendedMenu'; import { SearchSelectBar } from './SearchSelectBar'; @@ -36,7 +36,7 @@ type Props = { input: SearchAcrossEntitiesInput; }) => Promise; entityFilters: EntityType[]; - filters: FacetFilterInput[]; + filters: OrFilter[]; query: string; isSelectMode: boolean; isSelectAll: boolean; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx index c080c96bc57a3d..b2038ff4d6d033 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { FacetFilterInput } from '../../../../../../types.generated'; import { EmbeddedListSearch } from './EmbeddedListSearch'; import { UnionType } from '../../../../../search/utils/constants'; +import { FilterSet } from './types'; const SearchContainer = styled.div` height: 500px; @@ -18,7 +19,7 @@ const modalBodyStyle = { type Props = { emptySearchQuery?: string | null; - fixedFilter?: FacetFilterInput | null; + fixedFilters?: FilterSet; fixedQuery?: string | null; placeholderText?: string | null; defaultShowFilters?: boolean; @@ -30,7 +31,7 @@ type Props = { export const EmbeddedListSearchModal = ({ emptySearchQuery, - fixedFilter, + fixedFilters, fixedQuery, placeholderText, defaultShowFilters, @@ -79,7 +80,7 @@ export const EmbeddedListSearchModal = ({ onChangePage={onChangePage} onChangeUnionType={setUnionType} emptySearchQuery={emptySearchQuery} - fixedFilter={fixedFilter} + fixedFilters={fixedFilters} fixedQuery={fixedQuery} placeholderText={placeholderText} defaultShowFilters={defaultShowFilters} diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx index b4fa2ecddd4b6d..5f7134ea939c3a 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx @@ -5,14 +5,14 @@ import { ApolloError } from '@apollo/client'; import { FacetFilterInput } from '../../../../../../types.generated'; import useFilters from '../../../../../search/utils/useFilters'; import { navigateToEntitySearchUrl } from './navigateToEntitySearchUrl'; -import { GetSearchResultsParams, SearchResultsInterface } from './types'; +import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types'; import { useEntityQueryParams } from '../../../containers/profile/utils'; import { EmbeddedListSearch } from './EmbeddedListSearch'; import { UnionType } from '../../../../../search/utils/constants'; type Props = { emptySearchQuery?: string | null; - fixedFilter?: FacetFilterInput | null; + fixedFilters?: FilterSet; fixedQuery?: string | null; placeholderText?: string | null; defaultShowFilters?: boolean; @@ -29,7 +29,7 @@ type Props = { export const EmbeddedListSearchSection = ({ emptySearchQuery, - fixedFilter, + fixedFilters, fixedQuery, placeholderText, defaultShowFilters, @@ -69,8 +69,8 @@ export const EmbeddedListSearchSection = ({ query, page: 1, filters: newFilters, - history, unionType, + history, }); }; @@ -109,7 +109,7 @@ export const EmbeddedListSearchSection = ({ onChangePage={onChangePage} onChangeUnionType={onChangeUnionType} emptySearchQuery={emptySearchQuery} - fixedFilter={fixedFilter} + fixedFilters={fixedFilters} fixedQuery={fixedQuery} placeholderText={placeholderText} defaultShowFilters={defaultShowFilters} diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/SearchExtendedMenu.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/SearchExtendedMenu.tsx index 7dd6caa291b97c..61d2d3ed6527f5 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/SearchExtendedMenu.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/SearchExtendedMenu.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Button, Dropdown, Menu } from 'antd'; import { FormOutlined, MoreOutlined } from '@ant-design/icons'; import styled from 'styled-components'; -import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; +import { EntityType, OrFilter, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; import { SearchResultsInterface } from './types'; import DownloadAsCsvButton from './DownloadAsCsvButton'; import DownloadAsCsvModal from './DownloadAsCsvModal'; @@ -27,7 +27,7 @@ type Props = { input: SearchAcrossEntitiesInput; }) => Promise; entityFilters: EntityType[]; - filters: FacetFilterInput[]; + filters: OrFilter[]; query: string; setShowSelectMode?: (showSelectMode: boolean) => any; }; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/navigateToEntitySearchUrl.ts b/datahub-web-react/src/app/entity/shared/components/styled/search/navigateToEntitySearchUrl.ts index ac5440401c77e0..d54f07916c7922 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/navigateToEntitySearchUrl.ts +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/navigateToEntitySearchUrl.ts @@ -28,6 +28,10 @@ export const navigateToEntitySearchUrl = ({ constructedFilters.push({ field: 'entity', values: [newType] }); } + console.log(baseUrl); + console.log(baseParams); + console.log(JSON.stringify(newFilters)); + const search = QueryString.stringify( { ...baseParams, diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/types.ts b/datahub-web-react/src/app/entity/shared/components/styled/search/types.ts index 9d8ed6e97e7622..317b937d27abd1 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/types.ts +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/types.ts @@ -1,5 +1,6 @@ import { Entity, + FacetFilterInput, FacetMetadata, MatchedField, Maybe, @@ -7,6 +8,7 @@ import { SearchAcrossEntitiesInput, SearchInsight, } from '../../../../../../types.generated'; +import { UnionType } from '../../../../../search/utils/constants'; export type GetSearchResultsParams = { variables: { @@ -47,3 +49,11 @@ export enum SelectActionGroups { CHANGE_DEPRECATION, DELETE, } + +/** + * A fixed set of Filters, joined in conjunction or disjunction. + */ +export type FilterSet = { + unionType: UnionType; + filters: FacetFilterInput[]; +}; diff --git a/datahub-web-react/src/app/entity/user/UserAssets.tsx b/datahub-web-react/src/app/entity/user/UserAssets.tsx index bd1f0b738fcda8..7638d2f8a416da 100644 --- a/datahub-web-react/src/app/entity/user/UserAssets.tsx +++ b/datahub-web-react/src/app/entity/user/UserAssets.tsx @@ -1,5 +1,6 @@ import React from 'react'; import styled from 'styled-components'; +import { UnionType } from '../../search/utils/constants'; import { EmbeddedListSearchSection } from '../shared/components/styled/search/EmbeddedListSearchSection'; const UserAssetsWrapper = styled.div` @@ -15,7 +16,10 @@ export const UserAssets = ({ urn }: Props) => { return ( diff --git a/datahub-web-react/src/app/ingest/source/IngestedAssets.tsx b/datahub-web-react/src/app/ingest/source/IngestedAssets.tsx index d13a0092729e9a..fbec91631763d5 100644 --- a/datahub-web-react/src/app/ingest/source/IngestedAssets.tsx +++ b/datahub-web-react/src/app/ingest/source/IngestedAssets.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { useGetSearchResultsForMultipleQuery } from '../../../graphql/search.generated'; import { EmbeddedListSearchModal } from '../../entity/shared/components/styled/search/EmbeddedListSearchModal'; import { ANTD_GRAY } from '../../entity/shared/constants'; +import { UnionType } from '../../search/utils/constants'; import { formatNumber } from '../../shared/formatNumber'; import { Message } from '../../shared/Message'; import { useEntityRegistry } from '../../useEntityRegistry'; @@ -135,7 +136,10 @@ export default function IngestedAssets({ id }: Props) { {showAssetSearch && ( setShowAssetSearch(false)} /> )} diff --git a/datahub-web-react/src/app/search/SearchFiltersSection.tsx b/datahub-web-react/src/app/search/SearchFiltersSection.tsx index 5fddd4d81f75d9..a063b37e6feb7b 100644 --- a/datahub-web-react/src/app/search/SearchFiltersSection.tsx +++ b/datahub-web-react/src/app/search/SearchFiltersSection.tsx @@ -71,7 +71,7 @@ export const SearchFiltersSection = ({ type="link" onClick={() => setSeeAdvancedFilters(!seeAdvancedFilters)} > - {seeAdvancedFilters ? 'Filter' : 'Advanced'} + {seeAdvancedFilters ? 'Basic' : 'Advanced'} diff --git a/datahub-web-react/src/app/search/SearchResults.tsx b/datahub-web-react/src/app/search/SearchResults.tsx index 3198b7caf054fa..83b6cf6f56c360 100644 --- a/datahub-web-react/src/app/search/SearchResults.tsx +++ b/datahub-web-react/src/app/search/SearchResults.tsx @@ -24,6 +24,7 @@ import { EntityAndType } from '../entity/shared/types'; import { ErrorSection } from '../shared/error/ErrorSection'; import { UnionType } from './utils/constants'; import { SearchFiltersSection } from './SearchFiltersSection'; +import { generateOrFilters } from './utils/generateOrFilters'; const SearchBody = styled.div` display: flex; @@ -161,7 +162,7 @@ export const SearchResults = ({ diff --git a/datahub-web-react/src/app/search/utils/constants.ts b/datahub-web-react/src/app/search/utils/constants.ts index 4e7194174933e6..fdac81c659e3aa 100644 --- a/datahub-web-react/src/app/search/utils/constants.ts +++ b/datahub-web-react/src/app/search/utils/constants.ts @@ -29,8 +29,8 @@ export const FIELD_TO_LABEL = { domains: 'Domain', platform: 'Platform', fieldTags: 'Column Tag', - glossaryTerms: 'Term', - fieldGlossaryTerms: 'Column Term', + glossaryTerms: 'Glossary Term', + fieldGlossaryTerms: 'Column Glossary Term', fieldPaths: 'Column Name', description: 'Description', fieldDescriptions: 'Column Description', @@ -46,7 +46,9 @@ export const FIELDS_THAT_USE_CONTAINS_OPERATOR = ['description', 'fieldDescripti export const ADVANCED_SEARCH_ONLY_FILTERS = [ 'fieldGlossaryTerms', + 'editedFieldGlossaryTerms', 'fieldTags', + 'editedFieldTags', 'fieldPaths', 'description', 'fieldDescriptions', diff --git a/datahub-web-react/src/app/search/utils/filterUtils.ts b/datahub-web-react/src/app/search/utils/filterUtils.ts new file mode 100644 index 00000000000000..90580602497726 --- /dev/null +++ b/datahub-web-react/src/app/search/utils/filterUtils.ts @@ -0,0 +1,160 @@ +import { FacetFilterInput, OrFilter } from '../../../types.generated'; +import { FilterSet } from '../../entity/shared/components/styled/search/types'; +import { UnionType } from './constants'; + +/** + * Combines 2 sets of conjunctive filters in Disjunctive Normal Form + * + * Input: + * + * conjunction1 -> [a, b] + * conjunction2 -> [c, d] + * + * Output: + * + * or: [{ + * and: [a, b, c, d] + * }] + * + * @param conjunction1 a conjunctive set of filters + * @param conjunction2 a conjunctive set of filters + */ +export const mergeConjunctions = (conjunction1: FacetFilterInput[], conjunction2: FacetFilterInput[]): OrFilter[] => { + return [ + { + and: [...conjunction1, ...conjunction2], + }, + ]; +}; + +/** + * Combines 2 sets of disjunctive filters in Disjunctive Normal Form + * + * Input: + * + * disjunction1 -> [a, b] + * disjunction2 -> [c, d] + * + * Output: + * + * or: [ + * { + * and: [a, c] + * }, + * { + * and: [a, d] + * }, + * { + * and: [b, c] + * }, + * { + * and: [b, d] + * } + * ] + * + * @param disjunction1 a disjunctive set of filters + * @param disjunction2 a disjunctive set of filters + */ +export const mergeDisjunctions = (disjunction1: FacetFilterInput[], disjunction2: FacetFilterInput[]): OrFilter[] => { + const finalOrFilters: OrFilter[] = []; + + disjunction1.forEach((d1) => { + disjunction2.forEach((d2) => { + const andFilters = [d1, d2]; + finalOrFilters.push({ and: andFilters }); + }); + }); + + return finalOrFilters; +}; + +/** + * Combines 2 sets of filters, one conjunctive and the other disjunctive in Disjunctive Normal Form + * + * Input: + * + * conjunction -> [a, b] + * disjunction -> [c, d] + * + * Output: + * + * or: [ + * { + * and: [a, b, c] + * }, + * { + * and: [a, b, d] + * } + * ] + * + * @param conjunction a conjunctive set of filters + * @param disjunction a disjunctive set of filters + */ +export const mergeConjunctionDisjunction = ( + conjunction: FacetFilterInput[], + disjunction: FacetFilterInput[], +): OrFilter[] => { + const finalOrFilters: OrFilter[] = []; + + disjunction.forEach((filter) => { + const andFilters = [filter, ...conjunction]; + finalOrFilters.push({ and: andFilters }); + }); + + return finalOrFilters; +}; + +/** + * Merges the "fixed" set of disjunctive (OR) filters with a + * set of base filters that are themselves joined by a logical operator (AND, OR) + * + * It does this by producing a set of filters that model the Disjunctive Normal Form, + * which is a way of representing any boolean logical operator as an OR (disjunction) performed + * across a set of AND (conjunctions) conditions. + * + * @param orFilters the fixed set of filters in disjunction + * @param baseFilters a base set of filters in either conjunction or disjunction + */ +export const mergeOrFilters = (orFilters: FacetFilterInput[], baseFilters: FilterSet): OrFilter[] => { + // If the user-provided union type is AND, we need to treat differenty + // than if user-provided union type is OR. + if (baseFilters.unionType === UnionType.AND) { + return mergeConjunctionDisjunction(baseFilters.filters, orFilters); + } + return mergeDisjunctions(orFilters, baseFilters.filters); +}; + +/** + * Merges the "fixed" set of conjunctive (AND) filters with a + * set of base filters that are themselves joined by a logical operator (AND, OR) + * + * It does this by producing a set of filters that model the Disjunctive Normal Form, + * which is a way of representing any boolean logical operator as an OR (disjunction) performed + * across a set of AND (conjunctions) conditions. + * + * @param andFilters the fixed set of filters in conjunction + * @param baseFilters a base set of filters in either conjunction or disjunction + */ +export const mergeAndFilters = (andFilters: FacetFilterInput[], baseFilters: FilterSet): OrFilter[] => { + // If the user-provided union type is AND, we need to treat differenty + // than if user-provided union type is OR. + if (baseFilters.unionType === UnionType.AND) { + return mergeConjunctions(andFilters, baseFilters.filters); + } + return mergeConjunctionDisjunction(andFilters, baseFilters.filters); +}; + +/** + * Merges in a set of Fixed Filters with user-provided base filters. + * + * @param filterSet1 the fixed set of filters to be merged. + * @param filterSet2 the set of base filters to merge into. + */ +export const mergeFilterSets = (filterSet1: FilterSet, filterSet2: FilterSet): OrFilter[] => { + if (filterSet1.unionType === UnionType.AND) { + // Inject fixed AND filters. + return mergeAndFilters(filterSet1.filters, filterSet2); + } + // Inject fixed OR filters. + return mergeOrFilters(filterSet1.filters, filterSet2); +}; diff --git a/datahub-web-react/src/app/shared/TagStyleEntity.tsx b/datahub-web-react/src/app/shared/TagStyleEntity.tsx index 59c76a9dac0987..f8e19b755c6604 100644 --- a/datahub-web-react/src/app/shared/TagStyleEntity.tsx +++ b/datahub-web-react/src/app/shared/TagStyleEntity.tsx @@ -22,6 +22,8 @@ import CopyUrn from './CopyUrn'; import EntityDropdown from '../entity/shared/EntityDropdown'; import { EntityMenuItems } from '../entity/shared/EntityDropdown/EntityDropdown'; import { ErrorSection } from './error/ErrorSection'; +import { generateOrFilters } from '../search/utils/generateOrFilters'; +import { UnionType } from '../search/utils/constants'; function useWrappedSearchResults(params: GetSearchResultsParams) { const { data, loading, error } = useGetSearchResultsForMultipleQuery(params); @@ -184,8 +186,24 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe const { error, data, refetch } = useGetTagQuery({ variables: { urn } }); const [updateDescription] = useUpdateDescriptionMutation(); const [setTagColorMutation] = useSetTagColorMutation(); - const entityAndSchemaQuery = `tags:"${data?.tag?.name}" OR fieldTags:"${data?.tag?.name}" OR editedFieldTags:"${data?.tag?.name}"`; - const entityQuery = `tags:"${data?.tag?.name}"`; + const entityUrn = data?.tag?.urn; + const entityFilters = + (entityUrn && [ + { + field: 'tags', + values: [entityUrn], + }, + ]) || + []; + const entityAndSchemaFilters = + (entityUrn && [ + ...entityFilters, + { + field: 'fieldTags', + values: [entityUrn], + }, + ]) || + []; const description = data?.tag?.properties?.description || ''; const [updatedDescription, setUpdatedDescription] = useState(''); @@ -207,10 +225,10 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe const { data: facetData, loading: facetLoading } = useGetSearchResults({ variables: { input: { - query: entityAndSchemaQuery, + query: '*', start: 0, count: 1, - filters: [], + orFilters: generateOrFilters(UnionType.OR, entityAndSchemaFilters), }, }, }); @@ -378,10 +396,11 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe onClick={() => navigateToSearchUrl({ type: aggregation?.value as EntityType, - query: + filters: aggregation?.value === EntityType.Dataset - ? entityAndSchemaQuery - : entityQuery, + ? entityAndSchemaFilters + : entityFilters, + unionType: UnionType.OR, history, }) } From 8290d1e6a74bd30196e81e36be4f96e06f72ff51 Mon Sep 17 00:00:00 2001 From: John Joyce Date: Wed, 2 Nov 2022 16:32:43 -0700 Subject: [PATCH 2/2] Adding filter tests --- .../app/search/__tests__/filterUtils.test.tsx | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 datahub-web-react/src/app/search/__tests__/filterUtils.test.tsx diff --git a/datahub-web-react/src/app/search/__tests__/filterUtils.test.tsx b/datahub-web-react/src/app/search/__tests__/filterUtils.test.tsx new file mode 100644 index 00000000000000..47bfcb912aa405 --- /dev/null +++ b/datahub-web-react/src/app/search/__tests__/filterUtils.test.tsx @@ -0,0 +1,218 @@ +import { UnionType } from '../utils/constants'; +import { mergeFilterSets } from '../utils/filterUtils'; + +describe('filterUtils', () => { + describe('mergeFilterSets', () => { + it('merge conjunction and conjunction', () => { + const conjunction1 = { + unionType: UnionType.AND, + filters: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + ], + }; + const conjunction2 = { + unionType: UnionType.AND, + filters: [ + { + field: 'c', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }; + const expectedResult = [ + { + and: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + { + field: 'c', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }, + ]; + expect(mergeFilterSets(conjunction1, conjunction2)).toEqual(expectedResult); + }); + it('merge conjunction and disjunction', () => { + const conjunction = { + unionType: UnionType.AND, + filters: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + ], + }; + const disjunction = { + unionType: UnionType.OR, + filters: [ + { + field: 'c', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }; + const expectedResult = [ + { + and: [ + { + field: 'c', + values: ['1', '2'], + }, + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + ], + }, + { + and: [ + { + field: 'd', + values: ['1', '2'], + }, + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + ], + }, + ]; + expect(mergeFilterSets(conjunction, disjunction)).toEqual(expectedResult); + }); + it('merge disjunction and disjunction', () => { + const disjunction1 = { + unionType: UnionType.OR, + filters: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'b', + values: ['1', '2'], + }, + ], + }; + const disjunction2 = { + unionType: UnionType.OR, + filters: [ + { + field: 'c', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }; + const expectedResult = [ + { + and: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'c', + values: ['1', '2'], + }, + ], + }, + { + and: [ + { + field: 'a', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }, + { + and: [ + { + field: 'b', + values: ['1', '2'], + }, + { + field: 'c', + values: ['1', '2'], + }, + ], + }, + { + and: [ + { + field: 'b', + values: ['1', '2'], + }, + { + field: 'd', + values: ['1', '2'], + }, + ], + }, + ]; + expect(mergeFilterSets(disjunction1, disjunction2)).toEqual(expectedResult); + }); + it('bad arguments', () => { + expect( + mergeFilterSets( + { + unionType: UnionType.OR, + filters: [{ field: 'field', values: [] }], + }, + null!, + ), + ).toEqual([]); + expect( + mergeFilterSets(null!, { + unionType: UnionType.OR, + filters: [{ field: 'field', values: [] }], + }), + ).toEqual([]); + expect(mergeFilterSets(null!, null!)).toEqual([]); + }); + }); +});