persist filters in url and selection

This commit is contained in:
karanh37 2025-09-26 13:59:03 +05:30
parent 7c71be1d07
commit fe11aa22d1
16 changed files with 245 additions and 310 deletions

View File

@ -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,
});

View File

@ -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',

View File

@ -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);

View File

@ -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,
});

View File

@ -39,5 +39,6 @@ export const useSubdomainListingData = ({
return useDomainListing({
baseFilter: JSON.stringify(baseFilter),
nameLabelKey: 'label.sub-domain',
isSubDomain: true,
});
};

View File

@ -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,
});

View File

@ -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 || {}),

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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) => {

View File

@ -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,
};
};

View File

@ -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}

View File

@ -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;

View File

@ -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 = [