mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-27 01:55:17 +00:00
feat(customHomePage/assetCollection): add dynamic filters (#14435)
Co-authored-by: Chris Collins <chriscollins3456@gmail.com>
This commit is contained in:
parent
b3fafc38be
commit
d9dda1b059
@ -122,6 +122,12 @@ public class UpsertPageModuleResolver implements DataFetcher<CompletableFuture<D
|
|||||||
UrnArray urnArray = new UrnArray(urns);
|
UrnArray urnArray = new UrnArray(urns);
|
||||||
|
|
||||||
assetCollectionParams.setAssetUrns(urnArray);
|
assetCollectionParams.setAssetUrns(urnArray);
|
||||||
|
|
||||||
|
if (paramsInput.getAssetCollectionParams().getDynamicFilterJson() != null) {
|
||||||
|
assetCollectionParams.setDynamicFilterJson(
|
||||||
|
paramsInput.getAssetCollectionParams().getDynamicFilterJson());
|
||||||
|
}
|
||||||
|
|
||||||
gmsParams.setAssetCollectionParams(assetCollectionParams);
|
gmsParams.setAssetCollectionParams(assetCollectionParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,12 @@ public class PageModuleParamsMapper
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
assetCollectionParams.setAssetUrns(assetUrnStrings);
|
assetCollectionParams.setAssetUrns(assetUrnStrings);
|
||||||
|
|
||||||
|
if (params.getAssetCollectionParams().getDynamicFilterJson() != null) {
|
||||||
|
assetCollectionParams.setDynamicFilterJson(
|
||||||
|
params.getAssetCollectionParams().getDynamicFilterJson());
|
||||||
|
}
|
||||||
|
|
||||||
result.setAssetCollectionParams(assetCollectionParams);
|
result.setAssetCollectionParams(assetCollectionParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,15 @@ input AssetCollectionModuleParamsInput {
|
|||||||
The list of asset urns for the asset collection module
|
The list of asset urns for the asset collection module
|
||||||
"""
|
"""
|
||||||
assetUrns: [String!]!
|
assetUrns: [String!]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Optional dynamic filters
|
||||||
|
|
||||||
|
The stringified json representing the logical predicate built in the UI to select assets.
|
||||||
|
This predicate is turned into orFilters to send through graphql since graphql doesn't support
|
||||||
|
arbitrary nesting. This string is used to restore the UI for this logical predicate.
|
||||||
|
"""
|
||||||
|
dynamicFilterJson: String
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -311,6 +320,15 @@ type AssetCollectionModuleParams {
|
|||||||
The list of asset urns for the asset collection module
|
The list of asset urns for the asset collection module
|
||||||
"""
|
"""
|
||||||
assetUrns: [String!]!
|
assetUrns: [String!]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Optional dynamic filter
|
||||||
|
|
||||||
|
The stringified json representing the logical predicate built in the UI to select assets.
|
||||||
|
This predicate is turned into orFilters to send through graphql since graphql doesn't support
|
||||||
|
arbitrary nesting. This string is used to restore the UI for this logical predicate.
|
||||||
|
"""
|
||||||
|
dynamicFilterJson: String
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
|
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
|
||||||
import BaseModuleModal from '@app/homeV3/moduleModals/common/BaseModuleModal';
|
import BaseModuleModal from '@app/homeV3/moduleModals/common/BaseModuleModal';
|
||||||
import ModuleDetailsForm from '@app/homeV3/moduleModals/common/ModuleDetailsForm';
|
import ModuleDetailsForm from '@app/homeV3/moduleModals/common/ModuleDetailsForm';
|
||||||
import AssetsSection from '@app/homeV3/modules/assetCollection/AssetsSection';
|
import AssetsSection from '@app/homeV3/modules/assetCollection/AssetsSection';
|
||||||
|
import { SELECT_ASSET_TYPE_DYNAMIC, SELECT_ASSET_TYPE_MANUAL } from '@app/homeV3/modules/assetCollection/constants';
|
||||||
|
import { LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
|
import { isEmptyLogicalPredicate } from '@app/sharedV2/queryBuilder/builder/utils';
|
||||||
|
|
||||||
import { DataHubPageModuleType } from '@types';
|
import { DataHubPageModuleType } from '@types';
|
||||||
|
|
||||||
@ -26,15 +29,66 @@ const AssetCollectionModal = () => {
|
|||||||
(urn): urn is string => typeof urn === 'string',
|
(urn): urn is string => typeof urn === 'string',
|
||||||
);
|
);
|
||||||
const urn = initialState?.urn;
|
const urn = initialState?.urn;
|
||||||
|
|
||||||
|
const currentDynamicFilterLogicalPredicate: LogicalPredicate | undefined = useMemo(
|
||||||
|
() =>
|
||||||
|
initialState?.properties?.params?.assetCollectionParams?.dynamicFilterJson
|
||||||
|
? JSON.parse(initialState.properties.params.assetCollectionParams.dynamicFilterJson)
|
||||||
|
: undefined,
|
||||||
|
[initialState?.properties?.params?.assetCollectionParams?.dynamicFilterJson],
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentSelectAssetType = useMemo(() => {
|
||||||
|
if (currentAssets.length === 0 && currentDynamicFilterLogicalPredicate) {
|
||||||
|
return SELECT_ASSET_TYPE_DYNAMIC;
|
||||||
|
}
|
||||||
|
return SELECT_ASSET_TYPE_MANUAL;
|
||||||
|
}, [currentDynamicFilterLogicalPredicate, currentAssets]);
|
||||||
|
|
||||||
|
const [selectAssetType, setSelectAssetType] = useState<string>(currentSelectAssetType);
|
||||||
|
|
||||||
|
const [dynamicFilter, setDynamicFilter] = useState<LogicalPredicate | null | undefined>(
|
||||||
|
currentDynamicFilterLogicalPredicate,
|
||||||
|
);
|
||||||
|
|
||||||
const [selectedAssetUrns, setSelectedAssetUrns] = useState<string[]>(currentAssets);
|
const [selectedAssetUrns, setSelectedAssetUrns] = useState<string[]>(currentAssets);
|
||||||
|
|
||||||
const nameValue = Form.useWatch('name', form);
|
const nameValue = Form.useWatch('name', form);
|
||||||
|
|
||||||
const isDisabled = !nameValue?.trim() || !selectedAssetUrns.length;
|
const isDisabled = useMemo(() => {
|
||||||
|
if (!nameValue?.trim()) return true;
|
||||||
|
|
||||||
|
switch (selectAssetType) {
|
||||||
|
case SELECT_ASSET_TYPE_MANUAL:
|
||||||
|
return !selectedAssetUrns.length;
|
||||||
|
case SELECT_ASSET_TYPE_DYNAMIC:
|
||||||
|
return isEmptyLogicalPredicate(dynamicFilter);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, [nameValue, selectAssetType, selectedAssetUrns, dynamicFilter]);
|
||||||
|
|
||||||
const handleUpsertAssetCollectionModule = () => {
|
const handleUpsertAssetCollectionModule = () => {
|
||||||
|
const getAssetCollectionParams = () => {
|
||||||
|
switch (selectAssetType) {
|
||||||
|
case SELECT_ASSET_TYPE_MANUAL:
|
||||||
|
return {
|
||||||
|
assetUrns: selectedAssetUrns,
|
||||||
|
dynamicFilterJson: undefined,
|
||||||
|
};
|
||||||
|
case SELECT_ASSET_TYPE_DYNAMIC:
|
||||||
|
return {
|
||||||
|
assetUrns: [],
|
||||||
|
dynamicFilterJson: JSON.stringify(dynamicFilter),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
form.validateFields().then((values) => {
|
form.validateFields().then((values) => {
|
||||||
const { name } = values;
|
const { name } = values;
|
||||||
|
|
||||||
upsertModule({
|
upsertModule({
|
||||||
urn,
|
urn,
|
||||||
name,
|
name,
|
||||||
@ -42,9 +96,7 @@ const AssetCollectionModal = () => {
|
|||||||
// scope: initialState?.properties.visibility.scope || undefined,
|
// scope: initialState?.properties.visibility.scope || undefined,
|
||||||
type: DataHubPageModuleType.AssetCollection,
|
type: DataHubPageModuleType.AssetCollection,
|
||||||
params: {
|
params: {
|
||||||
assetCollectionParams: {
|
assetCollectionParams: getAssetCollectionParams(),
|
||||||
assetUrns: selectedAssetUrns,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
@ -61,7 +113,14 @@ const AssetCollectionModal = () => {
|
|||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModuleDetailsForm form={form} formValues={{ name: currentName }} />
|
<ModuleDetailsForm form={form} formValues={{ name: currentName }} />
|
||||||
<AssetsSection selectedAssetUrns={selectedAssetUrns} setSelectedAssetUrns={setSelectedAssetUrns} />
|
<AssetsSection
|
||||||
|
selectAssetType={selectAssetType}
|
||||||
|
setSelectAssetType={setSelectAssetType}
|
||||||
|
selectedAssetUrns={selectedAssetUrns}
|
||||||
|
setSelectedAssetUrns={setSelectedAssetUrns}
|
||||||
|
dynamicFilter={dynamicFilter}
|
||||||
|
setDynamicFilter={setDynamicFilter}
|
||||||
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</BaseModuleModal>
|
</BaseModuleModal>
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,9 @@ import EmptyContent from '@app/homeV3/module/components/EmptyContent';
|
|||||||
import EntityItem from '@app/homeV3/module/components/EntityItem';
|
import EntityItem from '@app/homeV3/module/components/EntityItem';
|
||||||
import LargeModule from '@app/homeV3/module/components/LargeModule';
|
import LargeModule from '@app/homeV3/module/components/LargeModule';
|
||||||
import { ModuleProps } from '@app/homeV3/module/types';
|
import { ModuleProps } from '@app/homeV3/module/types';
|
||||||
|
import { excludeEmptyAndFilters } from '@app/searchV2/utils/filterUtils';
|
||||||
|
import { LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
|
import { convertLogicalPredicateToOrFilters } from '@app/sharedV2/queryBuilder/builder/utils';
|
||||||
|
|
||||||
import { useGetSearchResultsForMultipleQuery } from '@graphql/search.generated';
|
import { useGetSearchResultsForMultipleQuery } from '@graphql/search.generated';
|
||||||
import { DataHubPageModuleType, Entity } from '@types';
|
import { DataHubPageModuleType, Entity } from '@types';
|
||||||
@ -20,19 +23,72 @@ const AssetCollectionModule = (props: ModuleProps) => {
|
|||||||
[props.module.properties.params.assetCollectionParams?.assetUrns],
|
[props.module.properties.params.assetCollectionParams?.assetUrns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dynamicFilterLogicalPredicate: LogicalPredicate | undefined = useMemo(
|
||||||
|
() =>
|
||||||
|
props.module.properties.params.assetCollectionParams?.dynamicFilterJson
|
||||||
|
? JSON.parse(props.module.properties.params.assetCollectionParams?.dynamicFilterJson)
|
||||||
|
: undefined,
|
||||||
|
[props.module.properties.params.assetCollectionParams?.dynamicFilterJson],
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldFetchByDynamicFilter = useMemo(
|
||||||
|
() => assetUrns.length === 0 && !!dynamicFilterLogicalPredicate,
|
||||||
|
[assetUrns, dynamicFilterLogicalPredicate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const dynamicOrFilters = useMemo(() => {
|
||||||
|
if (dynamicFilterLogicalPredicate) {
|
||||||
|
const orFilters = excludeEmptyAndFilters(convertLogicalPredicateToOrFilters(dynamicFilterLogicalPredicate));
|
||||||
|
return orFilters;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [dynamicFilterLogicalPredicate]);
|
||||||
|
|
||||||
|
const totalForInfiniteScroll = useMemo(
|
||||||
|
() => (shouldFetchByDynamicFilter ? undefined : assetUrns.length),
|
||||||
|
[shouldFetchByDynamicFilter, assetUrns],
|
||||||
|
);
|
||||||
|
|
||||||
const { loading, refetch } = useGetSearchResultsForMultipleQuery({
|
const { loading, refetch } = useGetSearchResultsForMultipleQuery({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
start: 0,
|
start: 0,
|
||||||
count: DEFAULT_PAGE_SIZE,
|
count: DEFAULT_PAGE_SIZE,
|
||||||
query: '*',
|
query: '*',
|
||||||
|
...(shouldFetchByDynamicFilter
|
||||||
|
? { orFilters: dynamicOrFilters }
|
||||||
|
: {
|
||||||
filters: [{ field: 'urn', values: assetUrns }],
|
filters: [{ field: 'urn', values: assetUrns }],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
skip: assetUrns.length === 0,
|
skip: assetUrns.length === 0 && !dynamicOrFilters?.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchEntities = useCallback(
|
const fetchEntitiesByDynamicFilter = useCallback(
|
||||||
|
async (start: number, count: number): Promise<Entity[]> => {
|
||||||
|
if (!dynamicOrFilters?.length) return [];
|
||||||
|
|
||||||
|
const result = await refetch({
|
||||||
|
input: {
|
||||||
|
start,
|
||||||
|
count,
|
||||||
|
query: '*',
|
||||||
|
orFilters: dynamicOrFilters,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results =
|
||||||
|
result.data?.searchAcrossEntities?.searchResults
|
||||||
|
?.map((res) => res.entity)
|
||||||
|
.filter((entity): entity is Entity => !!entity) || [];
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
[dynamicOrFilters, refetch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchEntitiesByAssetUrns = useCallback(
|
||||||
async (start: number, count: number): Promise<Entity[]> => {
|
async (start: number, count: number): Promise<Entity[]> => {
|
||||||
if (assetUrns.length === 0) return [];
|
if (assetUrns.length === 0) return [];
|
||||||
// urn slicing is done at the front-end to maintain the order of assets to show with pagination
|
// urn slicing is done at the front-end to maintain the order of assets to show with pagination
|
||||||
@ -57,6 +113,16 @@ const AssetCollectionModule = (props: ModuleProps) => {
|
|||||||
[assetUrns, refetch],
|
[assetUrns, refetch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchEntities = useCallback(
|
||||||
|
async (start: number, count: number): Promise<Entity[]> => {
|
||||||
|
if (shouldFetchByDynamicFilter) {
|
||||||
|
return fetchEntitiesByDynamicFilter(start, count);
|
||||||
|
}
|
||||||
|
return fetchEntitiesByAssetUrns(start, count);
|
||||||
|
},
|
||||||
|
[fetchEntitiesByDynamicFilter, fetchEntitiesByAssetUrns, shouldFetchByDynamicFilter],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LargeModule {...props} loading={loading}>
|
<LargeModule {...props} loading={loading}>
|
||||||
<InfiniteScrollList<Entity>
|
<InfiniteScrollList<Entity>
|
||||||
@ -73,7 +139,7 @@ const AssetCollectionModule = (props: ModuleProps) => {
|
|||||||
description="Edit the module and add assets to see them in this list"
|
description="Edit the module and add assets to see them in this list"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
totalItemCount={assetUrns.length}
|
totalItemCount={totalForInfiniteScroll}
|
||||||
/>
|
/>
|
||||||
</LargeModule>
|
</LargeModule>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { colors } from '@components';
|
import { colors } from '@components';
|
||||||
import { Divider } from 'antd';
|
import { Divider } from 'antd';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import SelectAssetsSection from '@app/homeV3/modules/assetCollection/SelectAssetsSection';
|
import SelectAssetsSection from '@app/homeV3/modules/assetCollection/SelectAssetsSection';
|
||||||
import SelectedAssetsSection from '@app/homeV3/modules/assetCollection/SelectedAssetsSection';
|
import SelectedAssetsSection from '@app/homeV3/modules/assetCollection/SelectedAssetsSection';
|
||||||
|
import { SELECT_ASSET_TYPE_MANUAL } from '@app/homeV3/modules/assetCollection/constants';
|
||||||
|
import { LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -27,26 +29,48 @@ export const VerticalDivider = styled(Divider)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
selectAssetType: string;
|
||||||
|
setSelectAssetType: (newSelectAssetType: string) => void;
|
||||||
selectedAssetUrns: string[];
|
selectedAssetUrns: string[];
|
||||||
setSelectedAssetUrns: React.Dispatch<React.SetStateAction<string[]>>;
|
setSelectedAssetUrns: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
dynamicFilter: LogicalPredicate | null | undefined;
|
||||||
|
setDynamicFilter: (newDynamicFilter: LogicalPredicate | null | undefined) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AssetsSection = ({ selectedAssetUrns, setSelectedAssetUrns }: Props) => {
|
const AssetsSection = ({
|
||||||
|
selectAssetType,
|
||||||
|
setSelectAssetType,
|
||||||
|
selectedAssetUrns,
|
||||||
|
setSelectedAssetUrns,
|
||||||
|
dynamicFilter,
|
||||||
|
setDynamicFilter,
|
||||||
|
}: Props) => {
|
||||||
|
const shouldShowSelectedAssetsSection = useMemo(
|
||||||
|
() => selectAssetType === SELECT_ASSET_TYPE_MANUAL,
|
||||||
|
[selectAssetType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<LeftSection>
|
<LeftSection>
|
||||||
<SelectAssetsSection
|
<SelectAssetsSection
|
||||||
|
selectAssetType={selectAssetType}
|
||||||
|
setSelectAssetType={setSelectAssetType}
|
||||||
selectedAssetUrns={selectedAssetUrns}
|
selectedAssetUrns={selectedAssetUrns}
|
||||||
setSelectedAssetUrns={setSelectedAssetUrns}
|
setSelectedAssetUrns={setSelectedAssetUrns}
|
||||||
|
dynamicFilter={dynamicFilter}
|
||||||
|
setDynamicFilter={setDynamicFilter}
|
||||||
/>
|
/>
|
||||||
</LeftSection>
|
</LeftSection>
|
||||||
<VerticalDivider type="vertical" />
|
<VerticalDivider type="vertical" />
|
||||||
|
{shouldShowSelectedAssetsSection && (
|
||||||
<RightSection>
|
<RightSection>
|
||||||
<SelectedAssetsSection
|
<SelectedAssetsSection
|
||||||
selectedAssetUrns={selectedAssetUrns}
|
selectedAssetUrns={selectedAssetUrns}
|
||||||
setSelectedAssetUrns={setSelectedAssetUrns}
|
setSelectedAssetUrns={setSelectedAssetUrns}
|
||||||
/>
|
/>
|
||||||
</RightSection>
|
</RightSection>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import LogicalFiltersBuilder from '@app/sharedV2/queryBuilder/LogicalFiltersBuilder';
|
||||||
|
import { LogicalOperatorType, LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
|
import { properties } from '@app/sharedV2/queryBuilder/properties';
|
||||||
|
|
||||||
|
const EMPTY_FILTER = {
|
||||||
|
operator: LogicalOperatorType.AND,
|
||||||
|
operands: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dynamicFilter: LogicalPredicate | null | undefined;
|
||||||
|
setDynamicFilter: (newDynamicFilter: LogicalPredicate | null | undefined) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DynamicSelectAssetsTab = ({ dynamicFilter, setDynamicFilter }: Props) => {
|
||||||
|
return (
|
||||||
|
<LogicalFiltersBuilder
|
||||||
|
filters={dynamicFilter ?? EMPTY_FILTER}
|
||||||
|
onChangeFilters={setDynamicFilter}
|
||||||
|
properties={properties}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicSelectAssetsTab;
|
@ -0,0 +1,107 @@
|
|||||||
|
import { Checkbox, Loader, SearchBar, Text } from '@components';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import EntityItem from '@app/homeV3/module/components/EntityItem';
|
||||||
|
import AssetFilters from '@app/homeV3/modules/assetCollection/AssetFilters';
|
||||||
|
import EmptySection from '@app/homeV3/modules/assetCollection/EmptySection';
|
||||||
|
import useGetAssetResults from '@app/homeV3/modules/assetCollection/useGetAssetResults';
|
||||||
|
import { LoaderContainer } from '@app/homeV3/styledComponents';
|
||||||
|
import { getEntityDisplayType } from '@app/searchV2/autoCompleteV2/utils';
|
||||||
|
import useAppliedFilters from '@app/searchV2/filtersV2/context/useAppliedFilters';
|
||||||
|
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
|
||||||
|
|
||||||
|
import { DataHubPageModuleType, Entity } from '@types';
|
||||||
|
|
||||||
|
const ItemDetailsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResultsContainer = styled.div`
|
||||||
|
margin: 0 -16px 0 -8px;
|
||||||
|
position: relative;
|
||||||
|
max-height: 300px;
|
||||||
|
padding-right: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ScrollableResultsContainer = styled.div`
|
||||||
|
max-height: inherit;
|
||||||
|
overflow-y: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedAssetUrns: string[];
|
||||||
|
setSelectedAssetUrns: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManualSelectAssetsTab = ({ selectedAssetUrns, setSelectedAssetUrns }: Props) => {
|
||||||
|
const entityRegistry = useEntityRegistryV2();
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string | undefined>();
|
||||||
|
const { appliedFilters, updateFieldFilters } = useAppliedFilters();
|
||||||
|
const { entities, loading } = useGetAssetResults({ searchQuery, appliedFilters });
|
||||||
|
|
||||||
|
const handleSearchChange = (value: string) => {
|
||||||
|
setSearchQuery(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckboxChange = (urn: string) => {
|
||||||
|
setSelectedAssetUrns((prev) => (prev.includes(urn) ? prev.filter((u) => u !== urn) : [...prev, urn]));
|
||||||
|
};
|
||||||
|
|
||||||
|
const customDetailsRenderer = (entity: Entity) => {
|
||||||
|
const displayType = getEntityDisplayType(entity, entityRegistry);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemDetailsContainer>
|
||||||
|
<Text color="gray" size="sm">
|
||||||
|
{displayType}
|
||||||
|
</Text>
|
||||||
|
<Checkbox
|
||||||
|
size="xs"
|
||||||
|
isChecked={selectedAssetUrns?.includes(entity.urn)}
|
||||||
|
onCheckboxChange={() => handleCheckboxChange(entity.urn)}
|
||||||
|
/>
|
||||||
|
</ItemDetailsContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (loading) {
|
||||||
|
content = (
|
||||||
|
<LoaderContainer>
|
||||||
|
<Loader />
|
||||||
|
</LoaderContainer>
|
||||||
|
);
|
||||||
|
} else if (entities && entities.length > 0) {
|
||||||
|
content = entities?.map((entity) => (
|
||||||
|
<EntityItem
|
||||||
|
entity={entity}
|
||||||
|
key={entity.urn}
|
||||||
|
customDetailsRenderer={customDetailsRenderer}
|
||||||
|
moduleType={DataHubPageModuleType.AssetCollection}
|
||||||
|
padding="8px 0 8px 8px"
|
||||||
|
navigateOnlyOnNameClick
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
content = <EmptySection />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SearchBar value={searchQuery} onChange={handleSearchChange} />
|
||||||
|
<AssetFilters
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
appliedFilters={appliedFilters}
|
||||||
|
updateFieldFilters={updateFieldFilters}
|
||||||
|
/>
|
||||||
|
<ResultsContainer>
|
||||||
|
<ScrollableResultsContainer>{content}</ScrollableResultsContainer>
|
||||||
|
</ResultsContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManualSelectAssetsTab;
|
@ -1,17 +1,13 @@
|
|||||||
import { Checkbox, Loader, SearchBar, Text } from '@components';
|
import { Text } from '@components';
|
||||||
import React, { useState } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import EntityItem from '@app/homeV3/module/components/EntityItem';
|
import DynamicSelectAssetsTab from '@app/homeV3/modules/assetCollection/DynamicSelectAssetsTab';
|
||||||
import AssetFilters from '@app/homeV3/modules/assetCollection/AssetFilters';
|
import ManualSelectAssetsTab from '@app/homeV3/modules/assetCollection/ManualSelectAssetsTab';
|
||||||
import EmptySection from '@app/homeV3/modules/assetCollection/EmptySection';
|
import { SELECT_ASSET_TYPE_DYNAMIC, SELECT_ASSET_TYPE_MANUAL } from '@app/homeV3/modules/assetCollection/constants';
|
||||||
import useGetAssetResults from '@app/homeV3/modules/assetCollection/useGetAssetResults';
|
import ButtonTabs from '@app/homeV3/modules/shared/ButtonTabs/ButtonTabs';
|
||||||
import { LoaderContainer } from '@app/homeV3/styledComponents';
|
import { Tab } from '@app/homeV3/modules/shared/ButtonTabs/types';
|
||||||
import { getEntityDisplayType } from '@app/searchV2/autoCompleteV2/utils';
|
import { LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
import useAppliedFilters from '@app/searchV2/filtersV2/context/useAppliedFilters';
|
|
||||||
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
|
|
||||||
|
|
||||||
import { DataHubPageModuleType, Entity } from '@types';
|
|
||||||
|
|
||||||
const AssetsSection = styled.div`
|
const AssetsSection = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -19,96 +15,56 @@ const AssetsSection = styled.div`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ItemDetailsContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ResultsContainer = styled.div`
|
|
||||||
margin: 0 -16px 0 -8px;
|
|
||||||
position: relative;
|
|
||||||
max-height: 300px;
|
|
||||||
padding-right: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ScrollableResultsContainer = styled.div`
|
|
||||||
max-height: inherit;
|
|
||||||
overflow-y: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
selectAssetType: string;
|
||||||
|
setSelectAssetType: (newSelectAssetType: string) => void;
|
||||||
selectedAssetUrns: string[];
|
selectedAssetUrns: string[];
|
||||||
setSelectedAssetUrns: React.Dispatch<React.SetStateAction<string[]>>;
|
setSelectedAssetUrns: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
dynamicFilter: LogicalPredicate | null | undefined;
|
||||||
|
setDynamicFilter: (newDynamicFilter: LogicalPredicate | null | undefined) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SelectAssetsSection = ({ selectedAssetUrns, setSelectedAssetUrns }: Props) => {
|
const SelectAssetsSection = ({
|
||||||
const entityRegistry = useEntityRegistryV2();
|
selectAssetType,
|
||||||
|
setSelectAssetType,
|
||||||
const [searchQuery, setSearchQuery] = useState<string | undefined>();
|
selectedAssetUrns,
|
||||||
const { appliedFilters, updateFieldFilters } = useAppliedFilters();
|
setSelectedAssetUrns,
|
||||||
const { entities, loading } = useGetAssetResults({ searchQuery, appliedFilters });
|
dynamicFilter,
|
||||||
|
setDynamicFilter,
|
||||||
const handleSearchChange = (value: string) => {
|
}: Props) => {
|
||||||
setSearchQuery(value);
|
const tabs: Tab[] = [
|
||||||
};
|
{
|
||||||
|
key: SELECT_ASSET_TYPE_MANUAL,
|
||||||
const handleCheckboxChange = (urn: string) => {
|
label: 'Select Assets',
|
||||||
setSelectedAssetUrns((prev) => (prev.includes(urn) ? prev.filter((u) => u !== urn) : [...prev, urn]));
|
content: (
|
||||||
};
|
<ManualSelectAssetsTab
|
||||||
|
selectedAssetUrns={selectedAssetUrns}
|
||||||
const customDetailsRenderer = (entity: Entity) => {
|
setSelectedAssetUrns={setSelectedAssetUrns}
|
||||||
const displayType = getEntityDisplayType(entity, entityRegistry);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ItemDetailsContainer>
|
|
||||||
<Text color="gray" size="sm">
|
|
||||||
{displayType}
|
|
||||||
</Text>
|
|
||||||
<Checkbox
|
|
||||||
size="xs"
|
|
||||||
isChecked={selectedAssetUrns?.includes(entity.urn)}
|
|
||||||
onCheckboxChange={() => handleCheckboxChange(entity.urn)}
|
|
||||||
/>
|
/>
|
||||||
</ItemDetailsContainer>
|
),
|
||||||
);
|
},
|
||||||
};
|
{
|
||||||
|
key: SELECT_ASSET_TYPE_DYNAMIC,
|
||||||
|
label: 'Dynamic Filter',
|
||||||
|
content: <DynamicSelectAssetsTab dynamicFilter={dynamicFilter} setDynamicFilter={setDynamicFilter} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let content;
|
const onTabChanged = useCallback(
|
||||||
if (loading) {
|
(newActiveTabKey: string) => {
|
||||||
content = (
|
if (newActiveTabKey === SELECT_ASSET_TYPE_MANUAL || newActiveTabKey === SELECT_ASSET_TYPE_DYNAMIC) {
|
||||||
<LoaderContainer>
|
setSelectAssetType?.(newActiveTabKey);
|
||||||
<Loader />
|
|
||||||
</LoaderContainer>
|
|
||||||
);
|
|
||||||
} else if (entities && entities.length > 0) {
|
|
||||||
content = entities?.map((entity) => (
|
|
||||||
<EntityItem
|
|
||||||
entity={entity}
|
|
||||||
key={entity.urn}
|
|
||||||
customDetailsRenderer={customDetailsRenderer}
|
|
||||||
moduleType={DataHubPageModuleType.AssetCollection}
|
|
||||||
padding="8px 0 8px 8px"
|
|
||||||
navigateOnlyOnNameClick
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
content = <EmptySection />;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[setSelectAssetType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AssetsSection>
|
<AssetsSection>
|
||||||
<Text color="gray" weight="bold">
|
<Text color="gray" weight="bold">
|
||||||
Search and Select Assets
|
Search and Select Assets
|
||||||
</Text>
|
</Text>
|
||||||
<SearchBar value={searchQuery} onChange={handleSearchChange} />
|
<ButtonTabs tabs={tabs} onTabClick={onTabChanged} defaultKey={selectAssetType} />
|
||||||
<AssetFilters
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
appliedFilters={appliedFilters}
|
|
||||||
updateFieldFilters={updateFieldFilters}
|
|
||||||
/>
|
|
||||||
<ResultsContainer>
|
|
||||||
<ScrollableResultsContainer>{content}</ScrollableResultsContainer>
|
|
||||||
</ResultsContainer>
|
|
||||||
</AssetsSection>
|
</AssetsSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
export const SELECT_ASSET_TYPE_MANUAL = 'manual';
|
||||||
|
export const SELECT_ASSET_TYPE_DYNAMIC = 'dynamic';
|
@ -2,9 +2,9 @@ import { Input } from '@components';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import FormItem from '@app/homeV3/modules/hierarchyViewModule/components/form/components/FormItem';
|
|
||||||
import RelatedEntitiesSection from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/relatedEntities/RelatedEntitiesSection';
|
import RelatedEntitiesSection from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/relatedEntities/RelatedEntitiesSection';
|
||||||
import SelectAssetsSection from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/SelectAssetsSection';
|
import SelectAssetsSection from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/SelectAssetsSection';
|
||||||
|
import FormItem from '@app/homeV3/modules/shared/Form/FormItem';
|
||||||
|
|
||||||
const FormWrapper = styled.div`
|
const FormWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -2,13 +2,13 @@ import { Form } from 'antd';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { useHierarchyFormContext } from '@app/homeV3/modules/hierarchyViewModule/components/form/HierarchyFormContext';
|
import { useHierarchyFormContext } from '@app/homeV3/modules/hierarchyViewModule/components/form/HierarchyFormContext';
|
||||||
import FormItem from '@app/homeV3/modules/hierarchyViewModule/components/form/components/FormItem';
|
|
||||||
import {
|
import {
|
||||||
FORM_FIELD_RELATED_ENTITIES_FILTER,
|
FORM_FIELD_RELATED_ENTITIES_FILTER,
|
||||||
FORM_FIELD_SHOW_RELATED_ENTITIES,
|
FORM_FIELD_SHOW_RELATED_ENTITIES,
|
||||||
} from '@app/homeV3/modules/hierarchyViewModule/components/form/constants';
|
} from '@app/homeV3/modules/hierarchyViewModule/components/form/constants';
|
||||||
import ShowRelatedEntitiesSwitch from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/relatedEntities/components/ShowRelatedEntitiesToggler';
|
import ShowRelatedEntitiesSwitch from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/relatedEntities/components/ShowRelatedEntitiesToggler';
|
||||||
import { HierarchyForm } from '@app/homeV3/modules/hierarchyViewModule/components/form/types';
|
import { HierarchyForm } from '@app/homeV3/modules/hierarchyViewModule/components/form/types';
|
||||||
|
import FormItem from '@app/homeV3/modules/shared/Form/FormItem';
|
||||||
import LogicalFiltersBuilder from '@app/sharedV2/queryBuilder/LogicalFiltersBuilder';
|
import LogicalFiltersBuilder from '@app/sharedV2/queryBuilder/LogicalFiltersBuilder';
|
||||||
import { LogicalOperatorType, LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
import { LogicalOperatorType, LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
import { properties } from '@app/sharedV2/queryBuilder/properties';
|
import { properties } from '@app/sharedV2/queryBuilder/properties';
|
||||||
|
@ -5,11 +5,11 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import DomainsSelectableTreeView from '@app/homeV3/modules/hierarchyViewModule/components/domains/DomainsSelectableTreeView';
|
import DomainsSelectableTreeView from '@app/homeV3/modules/hierarchyViewModule/components/domains/DomainsSelectableTreeView';
|
||||||
import { useHierarchyFormContext } from '@app/homeV3/modules/hierarchyViewModule/components/form/HierarchyFormContext';
|
import { useHierarchyFormContext } from '@app/homeV3/modules/hierarchyViewModule/components/form/HierarchyFormContext';
|
||||||
import FormItem from '@app/homeV3/modules/hierarchyViewModule/components/form/components/FormItem';
|
|
||||||
import { FORM_FIELD_ASSET_TYPE } from '@app/homeV3/modules/hierarchyViewModule/components/form/constants';
|
import { FORM_FIELD_ASSET_TYPE } from '@app/homeV3/modules/hierarchyViewModule/components/form/constants';
|
||||||
import EntityTypeTabs from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/assetTypeTabs/AssetTypeTabs';
|
|
||||||
import GlossarySelectableTreeView from '@app/homeV3/modules/hierarchyViewModule/components/glossary/GlossarySelectableTreeView';
|
import GlossarySelectableTreeView from '@app/homeV3/modules/hierarchyViewModule/components/glossary/GlossarySelectableTreeView';
|
||||||
import { ASSET_TYPE_DOMAINS, ASSET_TYPE_GLOSSARY } from '@app/homeV3/modules/hierarchyViewModule/constants';
|
import { ASSET_TYPE_DOMAINS, ASSET_TYPE_GLOSSARY } from '@app/homeV3/modules/hierarchyViewModule/constants';
|
||||||
|
import ButtonTabs from '@app/homeV3/modules/shared/ButtonTabs/ButtonTabs';
|
||||||
|
import FormItem from '@app/homeV3/modules/shared/Form/FormItem';
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export default function SelectAssetsSection() {
|
|||||||
Search and Select Assets
|
Search and Select Assets
|
||||||
</Text>
|
</Text>
|
||||||
<FormItem name={FORM_FIELD_ASSET_TYPE}>
|
<FormItem name={FORM_FIELD_ASSET_TYPE}>
|
||||||
<EntityTypeTabs tabs={tabs} onTabClick={onTabClick} defaultKey={assetType ?? defaultAssetsType} />
|
<ButtonTabs tabs={tabs} onTabClick={onTabClick} defaultKey={assetType ?? defaultAssetsType} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { TabButtons } from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/assetTypeTabs/TabButtons';
|
import { TabButtons } from '@app/homeV3/modules/shared/ButtonTabs/TabButtons';
|
||||||
import { Tab } from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/assetTypeTabs/types';
|
import { Tab } from '@app/homeV3/modules/shared/ButtonTabs/types';
|
||||||
|
|
||||||
const TabContentWrapper = styled.div<{ $visible?: boolean }>`
|
const TabContentWrapper = styled.div<{ $visible?: boolean }>`
|
||||||
${(props) => !props.$visible && 'display: none;'}
|
${(props) => !props.$visible && 'display: none;'}
|
||||||
@ -11,10 +11,10 @@ const TabContentWrapper = styled.div<{ $visible?: boolean }>`
|
|||||||
interface Props {
|
interface Props {
|
||||||
tabs: Tab[];
|
tabs: Tab[];
|
||||||
defaultKey?: string;
|
defaultKey?: string;
|
||||||
onTabClick: (key: string) => void;
|
onTabClick?: (key: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EntityTypeTabs({ tabs, defaultKey, onTabClick }: Props) {
|
export default function ButtonTabs({ tabs, defaultKey, onTabClick }: Props) {
|
||||||
const [activeKey, setActiveKey] = useState<string | undefined>(defaultKey ?? tabs?.[0]?.key);
|
const [activeKey, setActiveKey] = useState<string | undefined>(defaultKey ?? tabs?.[0]?.key);
|
||||||
const [renderedKeys, setRenderedKeys] = useState<string[]>(activeKey ? [activeKey] : []);
|
const [renderedKeys, setRenderedKeys] = useState<string[]>(activeKey ? [activeKey] : []);
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export default function EntityTypeTabs({ tabs, defaultKey, onTabClick }: Props)
|
|||||||
(key: string) => {
|
(key: string) => {
|
||||||
setActiveKey(key);
|
setActiveKey(key);
|
||||||
setRenderedKeys((prev) => [...new Set([...prev, key])]);
|
setRenderedKeys((prev) => [...new Set([...prev, key])]);
|
||||||
onTabClick(key);
|
onTabClick?.(key);
|
||||||
},
|
},
|
||||||
[onTabClick],
|
[onTabClick],
|
||||||
);
|
);
|
@ -2,7 +2,7 @@ import { Button, colors } from '@components';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { Tab } from '@app/homeV3/modules/hierarchyViewModule/components/form/sections/selectAssets/assetTypeTabs/types';
|
import { Tab } from '@app/homeV3/modules/shared/ButtonTabs/types';
|
||||||
|
|
||||||
const StyledButton = styled(Button)<{ $active?: boolean }>`
|
const StyledButton = styled(Button)<{ $active?: boolean }>`
|
||||||
width: 100%;
|
width: 100%;
|
@ -1,5 +1,9 @@
|
|||||||
import { QuickFilterField } from '@app/searchV2/autoComplete/quickFilters/utils';
|
import { QuickFilterField } from '@app/searchV2/autoComplete/quickFilters/utils';
|
||||||
import { getAutoCompleteInputFromQuickFilter, getFiltersWithQuickFilter } from '@app/searchV2/utils/filterUtils';
|
import {
|
||||||
|
excludeEmptyAndFilters,
|
||||||
|
getAutoCompleteInputFromQuickFilter,
|
||||||
|
getFiltersWithQuickFilter,
|
||||||
|
} from '@app/searchV2/utils/filterUtils';
|
||||||
|
|
||||||
describe('getAutoCompleteInputFromQuickFilter', () => {
|
describe('getAutoCompleteInputFromQuickFilter', () => {
|
||||||
it('should create a platform filter if the selected quick filter is a platform', () => {
|
it('should create a platform filter if the selected quick filter is a platform', () => {
|
||||||
@ -43,3 +47,41 @@ describe('getFiltersWithQuickFilter', () => {
|
|||||||
expect(filterResult).toMatchObject([]);
|
expect(filterResult).toMatchObject([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('excludeEmptyAndFilters', () => {
|
||||||
|
it('should handle filter out empty filters', () => {
|
||||||
|
const result = excludeEmptyAndFilters(undefined);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty array', () => {
|
||||||
|
const result = excludeEmptyAndFilters([]);
|
||||||
|
|
||||||
|
expect(result).toMatchObject([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle array of filled filters', () => {
|
||||||
|
const result = excludeEmptyAndFilters([
|
||||||
|
{ and: [{ field: 'test', values: ['test'] }] },
|
||||||
|
{ and: [{ field: 'test2', values: ['test2'] }] },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result).toMatchObject([
|
||||||
|
{ and: [{ field: 'test', values: ['test'] }] },
|
||||||
|
{ and: [{ field: 'test2', values: ['test2'] }] },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed empty and filled filters', () => {
|
||||||
|
const result = excludeEmptyAndFilters([{ and: [] }, { and: [{ field: 'test', values: ['test'] }] }]);
|
||||||
|
|
||||||
|
expect(result).toMatchObject([{ and: [{ field: 'test', values: ['test'] }] }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle array of empty filters', () => {
|
||||||
|
const result = excludeEmptyAndFilters([{ and: [] }, { and: [] }]);
|
||||||
|
|
||||||
|
expect(result).toMatchObject([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -214,3 +214,7 @@ export function combineOrFilters(orFilter1: AndFilterInput[], orFilter2: AndFilt
|
|||||||
|
|
||||||
return mergedFilter;
|
return mergedFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function excludeEmptyAndFilters(filters: AndFilterInput[] | undefined): AndFilterInput[] | undefined {
|
||||||
|
return filters?.filter((filter) => filter.and?.length);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { LogicalOperatorType } from '@app/sharedV2/queryBuilder/builder/types';
|
import { LogicalOperatorType, LogicalPredicate } from '@app/sharedV2/queryBuilder/builder/types';
|
||||||
import { convertLogicalPredicateToOrFilters, isLogicalPredicate } from '@app/sharedV2/queryBuilder/builder/utils';
|
import {
|
||||||
|
convertLogicalPredicateToOrFilters,
|
||||||
|
isEmptyLogicalPredicate,
|
||||||
|
isLogicalPredicate,
|
||||||
|
} from '@app/sharedV2/queryBuilder/builder/utils';
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
describe('isLogicalPredicate', () => {
|
describe('isLogicalPredicate', () => {
|
||||||
@ -205,6 +209,10 @@ describe('utils', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const EMPTY_LOGICAL_PREDICATE = {};
|
const EMPTY_LOGICAL_PREDICATE = {};
|
||||||
|
const LOGICAL_PREDICATE_WITH_EMPTY_OPERANDS = {
|
||||||
|
operator: LogicalOperatorType.AND,
|
||||||
|
operands: [],
|
||||||
|
};
|
||||||
|
|
||||||
const LOGICAL_PREDICATE_WITH_UNKNOWN_OPERATION = {
|
const LOGICAL_PREDICATE_WITH_UNKNOWN_OPERATION = {
|
||||||
operator: 'UNKNOWN',
|
operator: 'UNKNOWN',
|
||||||
@ -235,4 +243,16 @@ describe('utils', () => {
|
|||||||
expect(convertLogicalPredicateToOrFilters(LOGICAL_PREDICATE_WITH_UNKNOWN_OPERATION)).toEqual(undefined);
|
expect(convertLogicalPredicateToOrFilters(LOGICAL_PREDICATE_WITH_UNKNOWN_OPERATION)).toEqual(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isEmptyLogicalPredicate', () => {
|
||||||
|
it('should handle not empty logical predicate', () => {
|
||||||
|
expect(isEmptyLogicalPredicate(BASIC_AND_LOGICAL_PREDICATE)).toBeFalsy();
|
||||||
|
});
|
||||||
|
it('should handle empty logical predicate', () => {
|
||||||
|
expect(isEmptyLogicalPredicate(EMPTY_LOGICAL_PREDICATE as LogicalPredicate)).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should handle logical predicate with empty', () => {
|
||||||
|
expect(isEmptyLogicalPredicate(LOGICAL_PREDICATE_WITH_EMPTY_OPERANDS)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -109,3 +109,7 @@ export const convertToLogicalPredicate = (predicate: LogicalPredicate | Property
|
|||||||
// Already is a logical predicate.
|
// Already is a logical predicate.
|
||||||
return predicate as LogicalPredicate;
|
return predicate as LogicalPredicate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isEmptyLogicalPredicate(predicate: LogicalPredicate | null | undefined) {
|
||||||
|
return !predicate?.operands?.length;
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ fragment PageModule on DataHubPageModule {
|
|||||||
}
|
}
|
||||||
assetCollectionParams {
|
assetCollectionParams {
|
||||||
assetUrns
|
assetUrns
|
||||||
|
dynamicFilterJson
|
||||||
}
|
}
|
||||||
linkParams {
|
linkParams {
|
||||||
linkUrl
|
linkUrl
|
||||||
|
@ -7,4 +7,13 @@ import com.linkedin.common.Urn
|
|||||||
*/
|
*/
|
||||||
record AssetCollectionModuleParams {
|
record AssetCollectionModuleParams {
|
||||||
assetUrns: array[Urn]
|
assetUrns: array[Urn]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional dynamic filters
|
||||||
|
*
|
||||||
|
* The stringified json representing the logical predicate built in the UI to select assets.
|
||||||
|
* This predicate is turned into orFilters to send through graphql since graphql doesn't support
|
||||||
|
* arbitrary nesting. This string is used to restore the UI for this logical predicate.
|
||||||
|
*/
|
||||||
|
dynamicFilterJson: optional string
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user