mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-06 14:26:28 +00:00
persist filters in url and selection
This commit is contained in:
parent
7c71be1d07
commit
fe11aa22d1
@ -50,6 +50,7 @@ const DataProductListPage = () => {
|
||||
// Use the simplified data product filters configuration
|
||||
const { quickFilters, defaultFilters } = useDataProductFilters({
|
||||
aggregations: dataProductListing.aggregations || undefined,
|
||||
parsedFilters: dataProductListing.parsedFilters,
|
||||
onFilterChange: dataProductListing.handleFilterChange,
|
||||
});
|
||||
|
||||
@ -57,6 +58,7 @@ const DataProductListPage = () => {
|
||||
const { filterSelectionDisplay } = useFilterSelection({
|
||||
urlState: dataProductListing.urlState,
|
||||
filterConfigs: defaultFilters,
|
||||
parsedFilters: dataProductListing.parsedFilters,
|
||||
onFilterChange: dataProductListing.handleFilterChange,
|
||||
});
|
||||
|
||||
|
@ -13,7 +13,10 @@
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { TABLE_CARD_PAGE_SIZE } from '../../../constants/constants';
|
||||
import { DATAPRODUCT_DEFAULT_QUICK_FILTERS } from '../../../constants/DataProduct.constants';
|
||||
import {
|
||||
DATAPRODUCT_DEFAULT_QUICK_FILTERS,
|
||||
DATAPRODUCT_FILTERS,
|
||||
} from '../../../constants/DataProduct.constants';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
||||
import { TagSource } from '../../../generated/type/tagLabel';
|
||||
@ -26,6 +29,7 @@ import {
|
||||
|
||||
export const useDataProductListingData = (): ListingData<DataProduct> => {
|
||||
const filterKeys = useMemo(() => DATAPRODUCT_DEFAULT_QUICK_FILTERS, []);
|
||||
const filterConfigs = useMemo(() => DATAPRODUCT_FILTERS, []);
|
||||
|
||||
const getGlossaryTags = useCallback(
|
||||
(dataProduct: DataProduct) =>
|
||||
@ -70,6 +74,7 @@ export const useDataProductListingData = (): ListingData<DataProduct> => {
|
||||
baseFilter: '', // No parent filter for data products
|
||||
pageSize: TABLE_CARD_PAGE_SIZE,
|
||||
filterKeys,
|
||||
filterConfigs,
|
||||
columns,
|
||||
renderers,
|
||||
basePath: '/dataProduct',
|
||||
|
@ -88,7 +88,6 @@ import {
|
||||
getDomainDetailsPath,
|
||||
getDomainPath,
|
||||
getDomainVersionsPath,
|
||||
getEntityDetailsPath,
|
||||
} from '../../../utils/RouterUtils';
|
||||
import {
|
||||
escapeESReservedCharacters,
|
||||
@ -139,69 +138,71 @@ const DomainDetailsPage = ({
|
||||
const [subDomainForm] = Form.useForm();
|
||||
const [isSubDomainLoading, setIsSubDomainLoading] = useState(false);
|
||||
|
||||
const {
|
||||
formDrawer: subDomainDrawer,
|
||||
openDrawer: openSubDomainDrawer,
|
||||
closeDrawer: closeSubDomainDrawer,
|
||||
} = useFormDrawerWithRef({
|
||||
title: t('label.add-entity', { entity: t('label.sub-domain') }),
|
||||
anchor: 'right',
|
||||
width: 670,
|
||||
closeOnEscape: false,
|
||||
form: (
|
||||
<AddDomainForm
|
||||
isFormInDialog
|
||||
formRef={subDomainForm}
|
||||
loading={isSubDomainLoading}
|
||||
type={DomainFormType.SUBDOMAIN}
|
||||
onCancel={() => {
|
||||
// No-op: Drawer close is handled by useFormDrawerWithRef
|
||||
}}
|
||||
onSubmit={async (formData: any) => {
|
||||
setIsSubDomainLoading(true);
|
||||
try {
|
||||
formData.parent = domain.fullyQualifiedName;
|
||||
await addDomains(formData);
|
||||
showSuccessToast(
|
||||
t('server.create-entity-success', {
|
||||
entity: t('label.sub-domain'),
|
||||
})
|
||||
);
|
||||
fetchSubDomainsCount();
|
||||
// Navigate to the subdomains tab
|
||||
handleTabChange(EntityTabs.SUBDOMAINS);
|
||||
closeSubDomainDrawer();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
getIsErrorMatch(error as AxiosError, ERROR_MESSAGE.alreadyExist)
|
||||
? t('server.entity-already-exist', {
|
||||
entity: t('label.sub-domain'),
|
||||
entityPlural: 'sub-domains',
|
||||
name: formData.name,
|
||||
})
|
||||
: (error as AxiosError),
|
||||
t('server.add-entity-error', {
|
||||
entity: t('label.sub-domain').toLowerCase(),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsSubDomainLoading(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
formRef: subDomainForm,
|
||||
onSubmit: () => {
|
||||
// This is called by the drawer button, but actual submission
|
||||
// happens via formRef.submit() which triggers form.onFinish
|
||||
},
|
||||
loading: isSubDomainLoading,
|
||||
});
|
||||
|
||||
// Data product drawer implementation
|
||||
const [dataProductForm] = Form.useForm();
|
||||
const [isDataProductLoading, setIsDataProductLoading] = useState(false);
|
||||
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState<boolean>(false);
|
||||
const [isNameEditing, setIsNameEditing] = useState<boolean>(false);
|
||||
const [isStyleEditing, setIsStyleEditing] = useState(false);
|
||||
const [previewAsset, setPreviewAsset] =
|
||||
useState<EntityDetailsObjectInterface>();
|
||||
const [assetCount, setAssetCount] = useState<number>(0);
|
||||
const [dataProductsCount, setDataProductsCount] = useState<number>(0);
|
||||
const [subDomainsCount, setSubDomainsCount] = useState<number>(0);
|
||||
const encodedFqn = getEncodedFqn(
|
||||
escapeESReservedCharacters(domain.fullyQualifiedName)
|
||||
);
|
||||
const { customizedPage, isLoading } = useCustomPages(PageType.Domain);
|
||||
const [isTabExpanded, setIsTabExpanded] = useState(false);
|
||||
const isSubDomain = useMemo(() => !isEmpty(domain.parent), [domain]);
|
||||
|
||||
const queryFilter = useMemo(() => {
|
||||
return getQueryFilterForDomain(domainFqn);
|
||||
}, [domainFqn]);
|
||||
|
||||
const isOwner = useMemo(
|
||||
() => domain.owners?.some((owner) => isEqual(owner.id, currentUser?.id)),
|
||||
[domain, currentUser]
|
||||
);
|
||||
|
||||
const fetchDomainAssets = async () => {
|
||||
if (domainFqn && !isVersionsView) {
|
||||
try {
|
||||
const res = await searchQuery({
|
||||
query: '',
|
||||
pageNumber: 0,
|
||||
pageSize: 0,
|
||||
queryFilter,
|
||||
searchIndex: SearchIndex.ALL,
|
||||
filters: '',
|
||||
});
|
||||
|
||||
const totalCount = res?.hits?.total.value ?? 0;
|
||||
setAssetCount(totalCount);
|
||||
} catch (error) {
|
||||
setAssetCount(0);
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-fetch-error', {
|
||||
entity: t('label.asset-plural-lowercase'),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (activeKey: string) => {
|
||||
if (activeKey === 'assets') {
|
||||
// refresh domain count when assets tab is selected
|
||||
fetchDomainAssets();
|
||||
}
|
||||
if (activeKey !== activeTab) {
|
||||
navigate(getDomainDetailsPath(domainFqn, activeKey));
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
formDrawer: dataProductDrawer,
|
||||
openDrawer: openDataProductDrawer,
|
||||
@ -221,11 +222,13 @@ const DomainDetailsPage = ({
|
||||
onCancel={() => {
|
||||
// No-op: Drawer close is handled by useFormDrawerWithRef
|
||||
}}
|
||||
onSubmit={async (formData: any) => {
|
||||
onSubmit={async (formData: CreateDomain | CreateDataProduct) => {
|
||||
setIsDataProductLoading(true);
|
||||
try {
|
||||
formData.domains = [domain.fullyQualifiedName];
|
||||
await addDataProducts(formData);
|
||||
(formData as CreateDataProduct).domains = [
|
||||
domain.fullyQualifiedName ?? '',
|
||||
];
|
||||
await addDataProducts(formData as CreateDataProduct);
|
||||
showSuccessToast(
|
||||
t('server.create-entity-success', {
|
||||
entity: t('label.data-product'),
|
||||
@ -262,26 +265,6 @@ const DomainDetailsPage = ({
|
||||
},
|
||||
loading: isDataProductLoading,
|
||||
});
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState<boolean>(false);
|
||||
const [isNameEditing, setIsNameEditing] = useState<boolean>(false);
|
||||
const [isStyleEditing, setIsStyleEditing] = useState(false);
|
||||
const [previewAsset, setPreviewAsset] =
|
||||
useState<EntityDetailsObjectInterface>();
|
||||
const [assetCount, setAssetCount] = useState<number>(0);
|
||||
const [dataProductsCount, setDataProductsCount] = useState<number>(0);
|
||||
const [subDomainsCount, setSubDomainsCount] = useState<number>(0);
|
||||
const encodedFqn = getEncodedFqn(
|
||||
escapeESReservedCharacters(domain.fullyQualifiedName)
|
||||
);
|
||||
const { customizedPage, isLoading } = useCustomPages(PageType.Domain);
|
||||
const [isTabExpanded, setIsTabExpanded] = useState(false);
|
||||
const isSubDomain = useMemo(() => !isEmpty(domain.parent), [domain]);
|
||||
|
||||
const isOwner = useMemo(
|
||||
() => domain.owners?.some((owner) => isEqual(owner.id, currentUser?.id)),
|
||||
[domain, currentUser]
|
||||
);
|
||||
|
||||
const breadcrumbs = useMemo(() => {
|
||||
if (!domainFqn) {
|
||||
@ -328,6 +311,65 @@ const DomainDetailsPage = ({
|
||||
}
|
||||
}, [domain, isVersionsView]);
|
||||
|
||||
const {
|
||||
formDrawer: subDomainDrawer,
|
||||
openDrawer: openSubDomainDrawer,
|
||||
closeDrawer: closeSubDomainDrawer,
|
||||
} = useFormDrawerWithRef({
|
||||
title: t('label.add-entity', { entity: t('label.sub-domain') }),
|
||||
anchor: 'right',
|
||||
width: 670,
|
||||
closeOnEscape: false,
|
||||
form: (
|
||||
<AddDomainForm
|
||||
isFormInDialog
|
||||
formRef={subDomainForm}
|
||||
loading={isSubDomainLoading}
|
||||
type={DomainFormType.SUBDOMAIN}
|
||||
onCancel={() => {
|
||||
// No-op: Drawer close is handled by useFormDrawerWithRef
|
||||
}}
|
||||
onSubmit={async (formData: CreateDomain | CreateDataProduct) => {
|
||||
setIsSubDomainLoading(true);
|
||||
try {
|
||||
(formData as CreateDomain).parent = domain.fullyQualifiedName;
|
||||
await addDomains(formData as CreateDomain);
|
||||
showSuccessToast(
|
||||
t('server.create-entity-success', {
|
||||
entity: t('label.sub-domain'),
|
||||
})
|
||||
);
|
||||
fetchSubDomainsCount();
|
||||
// Navigate to the subdomains tab
|
||||
handleTabChange(EntityTabs.SUBDOMAINS);
|
||||
closeSubDomainDrawer();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
getIsErrorMatch(error as AxiosError, ERROR_MESSAGE.alreadyExist)
|
||||
? t('server.entity-already-exist', {
|
||||
entity: t('label.sub-domain'),
|
||||
entityPlural: 'sub-domains',
|
||||
name: formData.name,
|
||||
})
|
||||
: (error as AxiosError),
|
||||
t('server.add-entity-error', {
|
||||
entity: t('label.sub-domain').toLowerCase(),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsSubDomainLoading(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
formRef: subDomainForm,
|
||||
onSubmit: () => {
|
||||
// This is called by the drawer button, but actual submission
|
||||
// happens via formRef.submit() which triggers form.onFinish
|
||||
},
|
||||
loading: isSubDomainLoading,
|
||||
});
|
||||
|
||||
const editDisplayNamePermission = useMemo(() => {
|
||||
return getPrioritizedEditPermission(
|
||||
domainPermission,
|
||||
@ -420,45 +462,6 @@ const DomainDetailsPage = ({
|
||||
[domain, fetchSubDomainsCount]
|
||||
);
|
||||
|
||||
const addDataProduct = useCallback(
|
||||
async (formData: CreateDataProduct | CreateDomain) => {
|
||||
if (!domain.fullyQualifiedName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
...formData,
|
||||
domains: [domain.fullyQualifiedName],
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await addDataProducts(data);
|
||||
navigate(
|
||||
getEntityDetailsPath(
|
||||
EntityType.DATA_PRODUCT,
|
||||
res.fullyQualifiedName ?? ''
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
getIsErrorMatch(error as AxiosError, ERROR_MESSAGE.alreadyExist)
|
||||
? t('server.entity-already-exist', {
|
||||
entity: t('label.sub-domain'),
|
||||
entityPlural: t('label.sub-domain-lowercase-plural'),
|
||||
name: data.name,
|
||||
})
|
||||
: (error as AxiosError),
|
||||
t('server.add-entity-error', {
|
||||
entity: t('label.sub-domain-lowercase'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
closeDataProductDrawer();
|
||||
}
|
||||
},
|
||||
[domain]
|
||||
);
|
||||
|
||||
const handleVersionClick = async () => {
|
||||
const path = isVersionsView
|
||||
? getDomainPath(domainFqn)
|
||||
@ -493,32 +496,6 @@ const DomainDetailsPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDomainAssets = async () => {
|
||||
if (domainFqn && !isVersionsView) {
|
||||
try {
|
||||
const res = await searchQuery({
|
||||
query: '',
|
||||
pageNumber: 0,
|
||||
pageSize: 0,
|
||||
queryFilter,
|
||||
searchIndex: SearchIndex.ALL,
|
||||
filters: '',
|
||||
});
|
||||
|
||||
const totalCount = res?.hits?.total.value ?? 0;
|
||||
setAssetCount(totalCount);
|
||||
} catch (error) {
|
||||
setAssetCount(0);
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-fetch-error', {
|
||||
entity: t('label.asset-plural-lowercase'),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDomainPermission = async () => {
|
||||
try {
|
||||
const response = await getEntityPermission(
|
||||
@ -531,16 +508,6 @@ const DomainDetailsPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (activeKey: string) => {
|
||||
if (activeKey === 'assets') {
|
||||
// refresh domain count when assets tab is selected
|
||||
fetchDomainAssets();
|
||||
}
|
||||
if (activeKey !== activeTab) {
|
||||
navigate(getDomainDetailsPath(domainFqn, activeKey));
|
||||
}
|
||||
};
|
||||
|
||||
const onAddDataProduct = useCallback(() => {
|
||||
openDataProductDrawer();
|
||||
}, [openDataProductDrawer]);
|
||||
@ -658,10 +625,6 @@ const DomainDetailsPage = ({
|
||||
: []),
|
||||
];
|
||||
|
||||
const queryFilter = useMemo(() => {
|
||||
return getQueryFilterForDomain(domainFqn);
|
||||
}, [domainFqn]);
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
const tabLabelMap = getTabLabelMapFromTabs(customizedPage?.tabs);
|
||||
|
||||
|
@ -27,11 +27,7 @@ import { useDataTable } from '../../common/atoms/table/useDataTable';
|
||||
import { useSubdomainListingData } from './hooks/useSubdomainListingData';
|
||||
import { SubDomainsTableProps } from './SubDomainsTable.interface';
|
||||
|
||||
const SubDomainsTable = ({
|
||||
domainFqn,
|
||||
permissions,
|
||||
onAddSubDomain,
|
||||
}: SubDomainsTableProps) => {
|
||||
const SubDomainsTable = ({ domainFqn }: SubDomainsTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const subdomainListing = useSubdomainListingData({
|
||||
@ -42,6 +38,7 @@ const SubDomainsTable = ({
|
||||
const { quickFilters, defaultFilters } = useDomainFilters({
|
||||
isSubDomain: true,
|
||||
aggregations: subdomainListing.aggregations || undefined,
|
||||
parsedFilters: subdomainListing.parsedFilters,
|
||||
onFilterChange: subdomainListing.handleFilterChange,
|
||||
});
|
||||
|
||||
@ -49,6 +46,7 @@ const SubDomainsTable = ({
|
||||
const { filterSelectionDisplay } = useFilterSelection({
|
||||
urlState: subdomainListing.urlState,
|
||||
filterConfigs: defaultFilters,
|
||||
parsedFilters: subdomainListing.parsedFilters,
|
||||
onFilterChange: subdomainListing.handleFilterChange,
|
||||
});
|
||||
|
||||
|
@ -39,5 +39,6 @@ export const useSubdomainListingData = ({
|
||||
return useDomainListing({
|
||||
baseFilter: JSON.stringify(baseFilter),
|
||||
nameLabelKey: 'label.sub-domain',
|
||||
isSubDomain: true,
|
||||
});
|
||||
};
|
||||
|
@ -50,6 +50,7 @@ const DomainListPage = () => {
|
||||
// Use the simplified domain filters configuration
|
||||
const { quickFilters, defaultFilters } = useDomainFilters({
|
||||
aggregations: domainListing.aggregations || undefined,
|
||||
parsedFilters: domainListing.parsedFilters,
|
||||
onFilterChange: domainListing.handleFilterChange,
|
||||
});
|
||||
|
||||
@ -57,6 +58,7 @@ const DomainListPage = () => {
|
||||
const { filterSelectionDisplay } = useFilterSelection({
|
||||
urlState: domainListing.urlState,
|
||||
filterConfigs: defaultFilters,
|
||||
parsedFilters: domainListing.parsedFilters,
|
||||
onFilterChange: domainListing.handleFilterChange,
|
||||
});
|
||||
|
||||
|
@ -27,6 +27,7 @@ interface UseListingDataProps<T> {
|
||||
baseFilter?: string;
|
||||
pageSize?: number;
|
||||
filterKeys: string[];
|
||||
filterConfigs?: ExploreQuickFilterField[];
|
||||
columns: ColumnConfig<T>[];
|
||||
renderers?: CellRenderer<T>;
|
||||
basePath?: string;
|
||||
@ -46,6 +47,7 @@ export const useListingData = <
|
||||
baseFilter = '',
|
||||
pageSize = 10,
|
||||
filterKeys,
|
||||
filterConfigs,
|
||||
columns,
|
||||
renderers = {},
|
||||
basePath,
|
||||
@ -55,8 +57,14 @@ export const useListingData = <
|
||||
onCustomAddClick,
|
||||
} = props;
|
||||
|
||||
const urlStateHook = useUrlState({ filterKeys });
|
||||
const { urlState, setSearchQuery, setFilters, setCurrentPage } = urlStateHook;
|
||||
const urlStateHook = useUrlState({ filterKeys, filterConfigs });
|
||||
const {
|
||||
urlState,
|
||||
parsedFilters,
|
||||
setSearchQuery,
|
||||
setFilters,
|
||||
setCurrentPage,
|
||||
} = urlStateHook;
|
||||
|
||||
const dataFetching = useDataFetching<T>({
|
||||
searchIndex,
|
||||
@ -159,6 +167,7 @@ export const useListingData = <
|
||||
isSelected: selectionState.isSelected,
|
||||
clearSelection: selectionState.clearSelection,
|
||||
urlState,
|
||||
parsedFilters,
|
||||
actionHandlers,
|
||||
filterOptions: {},
|
||||
aggregations: getAggregations(dataFetching.aggregations || {}),
|
||||
|
@ -16,10 +16,17 @@ import { useSearchParams } from 'react-router-dom';
|
||||
import { ExploreQuickFilterField } from '../../../Explore/ExplorePage.interface';
|
||||
import { UrlState, UrlStateConfig, UrlStateHook } from '../shared/types';
|
||||
|
||||
export const useUrlState = (config: UrlStateConfig): UrlStateHook => {
|
||||
export const useUrlState = (
|
||||
config: UrlStateConfig & { filterConfigs?: ExploreQuickFilterField[] }
|
||||
): UrlStateHook => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const { searchKey = 'q', filterKeys, pageKey = 'page' } = config;
|
||||
const {
|
||||
searchKey = 'q',
|
||||
filterKeys,
|
||||
pageKey = 'page',
|
||||
filterConfigs,
|
||||
} = config;
|
||||
|
||||
const urlState: UrlState = useMemo(() => {
|
||||
const searchQuery = searchParams.get(searchKey) || '';
|
||||
@ -38,6 +45,21 @@ export const useUrlState = (config: UrlStateConfig): UrlStateHook => {
|
||||
};
|
||||
}, [searchParams, searchKey, pageKey, filterKeys]);
|
||||
|
||||
const parsedFilters: ExploreQuickFilterField[] = useMemo(() => {
|
||||
if (!filterConfigs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filterConfigs.map((filterConfig) => {
|
||||
const values = urlState.filters[filterConfig.key] || [];
|
||||
|
||||
return {
|
||||
...filterConfig,
|
||||
value: values.map((v) => ({ key: v, label: v })),
|
||||
};
|
||||
});
|
||||
}, [urlState.filters, filterConfigs]);
|
||||
|
||||
const updateUrlParams = useCallback(
|
||||
(updates: Record<string, string | null>) => {
|
||||
setSearchParams((current) => {
|
||||
@ -125,6 +147,7 @@ export const useUrlState = (config: UrlStateConfig): UrlStateHook => {
|
||||
|
||||
return {
|
||||
urlState,
|
||||
parsedFilters,
|
||||
setSearchQuery,
|
||||
setFilters,
|
||||
setCurrentPage,
|
||||
|
@ -13,7 +13,12 @@
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { TABLE_CARD_PAGE_SIZE } from '../../../../../constants/constants';
|
||||
import { DOMAIN_DEFAULT_QUICK_FILTERS } from '../../../../../constants/Domain.constants';
|
||||
import {
|
||||
DOMAIN_DEFAULT_QUICK_FILTERS,
|
||||
DOMAIN_FILTERS,
|
||||
SUBDOMAIN_DEFAULT_QUICK_FILTERS,
|
||||
SUB_DOMAIN_FILTERS,
|
||||
} from '../../../../../constants/Domain.constants';
|
||||
import { SearchIndex } from '../../../../../enums/search.enum';
|
||||
import { Domain } from '../../../../../generated/entity/domains/domain';
|
||||
import { useListingData } from '../../compositions/useListingData';
|
||||
@ -32,6 +37,7 @@ interface UseDomainListingConfig {
|
||||
includeClassificationTags?: boolean;
|
||||
customColumns?: ColumnConfig<Domain>[];
|
||||
customRenderers?: CellRenderer<Domain>;
|
||||
isSubDomain?: boolean;
|
||||
}
|
||||
|
||||
export const useDomainListing = (
|
||||
@ -48,6 +54,7 @@ export const useDomainListing = (
|
||||
includeClassificationTags = true,
|
||||
customColumns = [],
|
||||
customRenderers = {},
|
||||
isSubDomain = false,
|
||||
} = config;
|
||||
|
||||
// Memoize domain atom configurations to ensure stability (like DataProduct pattern)
|
||||
@ -82,7 +89,17 @@ export const useDomainListing = (
|
||||
const { columns } = useDomainColumns(domainColumnsConfig);
|
||||
const { renderers } = useDomainRenderers(domainRenderersConfig);
|
||||
// Define filterKeys for domain filters
|
||||
const filterKeys = useMemo(() => DOMAIN_DEFAULT_QUICK_FILTERS, []);
|
||||
const filterKeys = useMemo(
|
||||
() =>
|
||||
isSubDomain
|
||||
? SUBDOMAIN_DEFAULT_QUICK_FILTERS
|
||||
: DOMAIN_DEFAULT_QUICK_FILTERS,
|
||||
[isSubDomain]
|
||||
);
|
||||
const filterConfigs = useMemo(
|
||||
() => (isSubDomain ? SUB_DOMAIN_FILTERS : DOMAIN_FILTERS),
|
||||
[isSubDomain]
|
||||
);
|
||||
|
||||
// Use generic listing composition with domain-specific configuration
|
||||
const listingData = useListingData<Domain>({
|
||||
@ -90,6 +107,7 @@ export const useDomainListing = (
|
||||
baseFilter,
|
||||
pageSize,
|
||||
filterKeys,
|
||||
filterConfigs,
|
||||
columns,
|
||||
renderers,
|
||||
basePath,
|
||||
|
@ -20,6 +20,7 @@ import { useQuickFiltersWithComponent } from '../../filters/useQuickFiltersWithC
|
||||
|
||||
interface UseDataProductFiltersConfig {
|
||||
aggregations?: Aggregations;
|
||||
parsedFilters?: ExploreQuickFilterField[];
|
||||
onFilterChange: (filters: ExploreQuickFilterField[]) => void;
|
||||
}
|
||||
|
||||
@ -27,6 +28,7 @@ export const useDataProductFilters = (config: UseDataProductFiltersConfig) => {
|
||||
const { quickFilters, selectedFilters } = useQuickFiltersWithComponent({
|
||||
defaultFilters: DATAPRODUCT_FILTERS,
|
||||
aggregations: config.aggregations,
|
||||
parsedFilters: config.parsedFilters,
|
||||
searchIndex: SearchIndex.DATA_PRODUCT,
|
||||
assetType: AssetsOfEntity.DATA_PRODUCT,
|
||||
onFilterChange: config.onFilterChange,
|
||||
|
@ -25,11 +25,12 @@ import { useQuickFiltersWithComponent } from '../../filters/useQuickFiltersWithC
|
||||
interface UseDomainFiltersConfig {
|
||||
isSubDomain?: boolean;
|
||||
aggregations?: Aggregations;
|
||||
parsedFilters?: ExploreQuickFilterField[];
|
||||
onFilterChange: (filters: ExploreQuickFilterField[]) => void;
|
||||
}
|
||||
|
||||
export const useDomainFilters = (config: UseDomainFiltersConfig) => {
|
||||
const { isSubDomain = false } = config;
|
||||
const { isSubDomain = false, parsedFilters } = config;
|
||||
|
||||
const defaultFilters = useMemo(
|
||||
() => (isSubDomain ? SUB_DOMAIN_FILTERS : DOMAIN_FILTERS),
|
||||
@ -39,6 +40,7 @@ export const useDomainFilters = (config: UseDomainFiltersConfig) => {
|
||||
const { quickFilters, selectedFilters } = useQuickFiltersWithComponent({
|
||||
defaultFilters,
|
||||
aggregations: config.aggregations,
|
||||
parsedFilters,
|
||||
searchIndex: SearchIndex.DOMAIN,
|
||||
assetType: AssetsOfEntity.DOMAIN,
|
||||
onFilterChange: config.onFilterChange,
|
||||
|
@ -27,6 +27,7 @@ interface FilterConfig {
|
||||
interface FilterSelectionConfig {
|
||||
urlState: UrlState;
|
||||
filterConfigs: FilterConfig[];
|
||||
parsedFilters?: ExploreQuickFilterField[];
|
||||
onFilterChange: (filters: ExploreQuickFilterField[]) => void;
|
||||
}
|
||||
|
||||
@ -38,26 +39,40 @@ interface FilterSelectionItem {
|
||||
|
||||
export const useFilterSelection = (config: FilterSelectionConfig) => {
|
||||
const { t } = useTranslation();
|
||||
const { urlState, filterConfigs, onFilterChange } = config;
|
||||
const { urlState, filterConfigs, parsedFilters, onFilterChange } = config;
|
||||
|
||||
const selectedFilters = useMemo<FilterSelectionItem[]>(() => {
|
||||
const filters: FilterSelectionItem[] = [];
|
||||
|
||||
Object.entries(urlState.filters).forEach(([filterKey, values]) => {
|
||||
if (values && values.length > 0) {
|
||||
const filterConfig = filterConfigs.find((fc) => fc.key === filterKey);
|
||||
if (filterConfig) {
|
||||
// Use parsedFilters if available for consistent values
|
||||
if (parsedFilters) {
|
||||
parsedFilters.forEach((filter) => {
|
||||
if (filter.value && filter.value.length > 0) {
|
||||
filters.push({
|
||||
key: filterKey,
|
||||
label: filterConfig.label,
|
||||
values,
|
||||
key: filter.key,
|
||||
label: filter.label,
|
||||
values: filter.value.map((v) => v.label || v.key),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Fallback to urlState.filters
|
||||
Object.entries(urlState.filters).forEach(([filterKey, values]) => {
|
||||
if (values && values.length > 0) {
|
||||
const filterConfig = filterConfigs.find((fc) => fc.key === filterKey);
|
||||
if (filterConfig) {
|
||||
filters.push({
|
||||
key: filterKey,
|
||||
label: filterConfig.label,
|
||||
values,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [urlState.filters, filterConfigs]);
|
||||
}, [urlState.filters, filterConfigs, parsedFilters]);
|
||||
|
||||
const handleRemoveFilter = useCallback(
|
||||
(filterKey: string) => {
|
||||
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Aggregations } from '../../../../interface/search.interface';
|
||||
import { ExploreQuickFilterField } from '../../../Explore/ExplorePage.interface';
|
||||
import ExploreQuickFilters from '../../../Explore/ExploreQuickFilters';
|
||||
import { FilterConfig, FilterField } from '../shared/types';
|
||||
|
||||
interface QuickFiltersConfig {
|
||||
filterFields: FilterField[];
|
||||
filterConfigs: FilterConfig[];
|
||||
queryConfig: Record<string, string>;
|
||||
onFilterChange: (filterKey: string, values: string[]) => void;
|
||||
aggregations: Aggregations;
|
||||
searchIndex: SearchIndex;
|
||||
independent?: boolean;
|
||||
}
|
||||
|
||||
export const useQuickFilters = (config: QuickFiltersConfig) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() =>
|
||||
config.filterFields.map((field) => {
|
||||
const filterConfig = config.filterConfigs.find(
|
||||
(fc) => fc.key === field.key
|
||||
);
|
||||
|
||||
return {
|
||||
key: field.aggregationField,
|
||||
label: filterConfig ? t(filterConfig.labelKey) : field.key,
|
||||
value: [],
|
||||
};
|
||||
}),
|
||||
[config.filterFields, config.filterConfigs, t]
|
||||
);
|
||||
|
||||
const [selectedQuickFilters, setSelectedQuickFilters] =
|
||||
useState<ExploreQuickFilterField[]>(initialFilters);
|
||||
|
||||
const getFilterKeyFromField = useCallback(
|
||||
(fieldKey: string): string => {
|
||||
// Reverse lookup from aggregationField to filter key
|
||||
const entry = Object.entries(config.queryConfig).find(
|
||||
([, value]) => value === fieldKey
|
||||
);
|
||||
|
||||
return entry ? entry[0] : fieldKey;
|
||||
},
|
||||
[config.queryConfig]
|
||||
);
|
||||
|
||||
const handleQuickFiltersValueSelect = useCallback(
|
||||
(field: ExploreQuickFilterField) => {
|
||||
setSelectedQuickFilters((prev) => {
|
||||
const data = prev.map((prevField) => {
|
||||
if (prevField.key === field.key) {
|
||||
return field;
|
||||
}
|
||||
|
||||
return prevField;
|
||||
});
|
||||
|
||||
const filterKey = getFilterKeyFromField(field.key);
|
||||
config.onFilterChange(filterKey, field.value?.map((v) => v.key) || []);
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
[config.onFilterChange, getFilterKeyFromField]
|
||||
);
|
||||
|
||||
const quickFilters = useMemo(
|
||||
() => (
|
||||
<ExploreQuickFilters
|
||||
aggregations={config.aggregations}
|
||||
fields={selectedQuickFilters}
|
||||
independent={config.independent}
|
||||
index={config.searchIndex}
|
||||
onFieldValueSelect={handleQuickFiltersValueSelect}
|
||||
/>
|
||||
),
|
||||
[
|
||||
config.independent,
|
||||
config.aggregations,
|
||||
config.searchIndex,
|
||||
selectedQuickFilters,
|
||||
handleQuickFiltersValueSelect,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
quickFilters,
|
||||
selectedQuickFilters,
|
||||
handleQuickFiltersValueSelect,
|
||||
};
|
||||
};
|
@ -21,6 +21,7 @@ import { AssetsOfEntity } from '../../../Glossary/GlossaryTerms/tabs/AssetsTabs.
|
||||
interface QuickFiltersWithComponentConfig {
|
||||
defaultFilters: ExploreQuickFilterField[];
|
||||
aggregations?: Aggregations;
|
||||
parsedFilters?: ExploreQuickFilterField[];
|
||||
searchIndex: SearchIndex;
|
||||
assetType: AssetsOfEntity;
|
||||
onFilterChange: (filters: ExploreQuickFilterField[]) => void;
|
||||
@ -34,23 +35,25 @@ export const useQuickFiltersWithComponent = (
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedQuickFilters(config.defaultFilters);
|
||||
}, [config.defaultFilters]);
|
||||
// Use parsedFilters if available (from URL), otherwise use defaultFilters
|
||||
if (config.parsedFilters && config.parsedFilters.length > 0) {
|
||||
// Merge parsedFilters with defaultFilters to maintain structure
|
||||
const mergedFilters = config.defaultFilters.map((defaultFilter) => {
|
||||
const parsedFilter = config.parsedFilters?.find(
|
||||
(pf) => pf.key === defaultFilter.key
|
||||
);
|
||||
|
||||
return parsedFilter || defaultFilter;
|
||||
});
|
||||
setSelectedQuickFilters(mergedFilters);
|
||||
} else {
|
||||
setSelectedQuickFilters(config.defaultFilters);
|
||||
}
|
||||
}, [config.defaultFilters, config.parsedFilters]);
|
||||
|
||||
const handleQuickFiltersChange = useCallback(
|
||||
(data: ExploreQuickFilterField[]) => {
|
||||
config.onFilterChange(data);
|
||||
|
||||
// data.forEach((filter) => {
|
||||
// if (filter.value && filter.value.length > 0) {
|
||||
// config.onFilterChange(
|
||||
// filter.key,
|
||||
// filter.value as unknown as string[]
|
||||
// );
|
||||
// } else {
|
||||
// config.onFilterChange(filter.key, []);
|
||||
// }
|
||||
// });
|
||||
},
|
||||
[config.onFilterChange]
|
||||
);
|
||||
@ -77,7 +80,6 @@ export const useQuickFiltersWithComponent = (
|
||||
const quickFilters = useMemo(
|
||||
() => (
|
||||
<ExploreQuickFilters
|
||||
independent
|
||||
showSelectedCounts
|
||||
aggregations={config.aggregations || {}}
|
||||
fields={selectedQuickFilters}
|
||||
|
@ -29,6 +29,7 @@ export interface UrlState {
|
||||
|
||||
export interface UrlStateHook {
|
||||
urlState: UrlState;
|
||||
parsedFilters: ExploreQuickFilterField[];
|
||||
setSearchQuery: (query: string) => void;
|
||||
setFilters: (filters: ExploreQuickFilterField[]) => void;
|
||||
setCurrentPage: (page: number) => void;
|
||||
@ -123,6 +124,7 @@ export interface ListingData<T> {
|
||||
isSelected: (id: string) => boolean;
|
||||
clearSelection: () => void;
|
||||
urlState: UrlState;
|
||||
parsedFilters: ExploreQuickFilterField[];
|
||||
actionHandlers: ActionHandlers<T>;
|
||||
filterOptions?: FilterOptions;
|
||||
aggregations?: Aggregations | null;
|
||||
|
@ -85,6 +85,7 @@ export const SUBDOMAIN_DEFAULT_QUICK_FILTERS = [
|
||||
EntityFields.OWNERS,
|
||||
EntityFields.CLASSIFICATION_TAGS,
|
||||
EntityFields.GLOSSARY_TERMS,
|
||||
EntityFields.DOMAIN_TYPE,
|
||||
];
|
||||
|
||||
export const DOMAIN_FILTERS = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user