mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-26 17:34:41 +00:00
add explore filter
This commit is contained in:
parent
2381de0722
commit
202c644acc
@ -46,7 +46,7 @@ const DomainRouter = () => {
|
||||
index
|
||||
element={
|
||||
<AdminProtectedRoute hasPermission={domainPermission}>
|
||||
<DomainsListPage />
|
||||
<DomainsListPage pageTitle={i18n.t('label.domain-plural')} />
|
||||
</AdminProtectedRoute>
|
||||
}
|
||||
path="/"
|
||||
@ -54,7 +54,7 @@ const DomainRouter = () => {
|
||||
<Route
|
||||
element={
|
||||
<AdminProtectedRoute hasPermission={domainPermission}>
|
||||
<DomainDetailPage />
|
||||
<DomainDetailPage pageTitle={i18n.t('label.domain-plural')} />
|
||||
</AdminProtectedRoute>
|
||||
}
|
||||
path={ROUTES.DOMAIN_DETAILS.replace(ROUTES.DOMAIN, '')}
|
||||
@ -62,7 +62,7 @@ const DomainRouter = () => {
|
||||
<Route
|
||||
element={
|
||||
<AdminProtectedRoute hasPermission={domainPermission}>
|
||||
<DomainDetailPage />
|
||||
<DomainDetailPage pageTitle={i18n.t('label.domain-plural')} />
|
||||
</AdminProtectedRoute>
|
||||
}
|
||||
path={ROUTES.DOMAIN_DETAILS_WITH_TAB.replace(ROUTES.DOMAIN, '')}
|
||||
|
@ -17,36 +17,51 @@ import { AxiosError } from 'axios';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ERROR_MESSAGE } from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { CreateDataProduct } from '../../generated/api/domains/createDataProduct';
|
||||
import { withPageLayout } from '../../hoc/withPageLayout';
|
||||
import { addDataProducts } from '../../rest/dataProductAPI';
|
||||
import { getIsErrorMatch } from '../../utils/CommonUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import { useDelete } from '../common/atoms/actions/useDelete';
|
||||
import { useDataProductFilters } from '../common/atoms/domain/ui/useDataProductFilters';
|
||||
import { useDomainCardTemplates } from '../common/atoms/domain/ui/useDomainCardTemplates';
|
||||
import { useFormDrawerWithRef } from '../common/atoms/drawer';
|
||||
import { useFilterConfig } from '../common/atoms/filters/useFilterConfig';
|
||||
import { useFilterDropdowns } from '../common/atoms/filters/useFilterDropdowns';
|
||||
import { useQuickFilters } from '../common/atoms/filters/useQuickFilters';
|
||||
import { useBreadcrumbs } from '../common/atoms/navigation/useBreadcrumbs';
|
||||
import { usePageHeader } from '../common/atoms/navigation/usePageHeader';
|
||||
import { useSearch } from '../common/atoms/navigation/useSearch';
|
||||
import { useTitleAndCount } from '../common/atoms/navigation/useTitleAndCount';
|
||||
import { useViewToggle } from '../common/atoms/navigation/useViewToggle';
|
||||
import { usePaginationControls } from '../common/atoms/pagination/usePaginationControls';
|
||||
import { DATA_PRODUCT_FILTER_CONFIGS } from '../common/atoms/shared/utils/commonFilterConfigs';
|
||||
import { useCardView } from '../common/atoms/table/useCardView';
|
||||
import { useDataTable } from '../common/atoms/table/useDataTable';
|
||||
import AddDomainForm from '../Domain/AddDomainForm/AddDomainForm.component';
|
||||
import { DomainFormType } from '../Domain/DomainPage.interface';
|
||||
import { useDataProductListingData } from './hooks/useDataProductListingData';
|
||||
|
||||
const DataProductListPage = () => {
|
||||
const DataProductListPage = ({ pageTitle }: { pageTitle: string }) => {
|
||||
const dataProductListing = useDataProductListingData();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [form] = useForm();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Use data product-specific filters configuration
|
||||
const dataProductFilters = useDataProductFilters({
|
||||
enabledFilters: ['owner', 'expert', 'tags', 'glossary'],
|
||||
});
|
||||
|
||||
const { quickFilters } = useQuickFilters({
|
||||
filterFields: dataProductFilters.filterFields,
|
||||
filterConfigs: dataProductFilters.filterConfigs,
|
||||
queryConfig: dataProductFilters.queryConfig,
|
||||
onFilterChange: dataProductListing.handleFilterChange,
|
||||
aggregations: dataProductListing.aggregations || {},
|
||||
searchIndex: SearchIndex.DATA_PRODUCT,
|
||||
independent: true,
|
||||
});
|
||||
|
||||
const { formDrawer, openDrawer, closeDrawer } = useFormDrawerWithRef({
|
||||
title: t('label.add-entity', { entity: t('label.data-product') }),
|
||||
anchor: 'right',
|
||||
@ -101,14 +116,6 @@ const DataProductListPage = () => {
|
||||
loading: isLoading,
|
||||
});
|
||||
|
||||
const { dropdownConfigs } = useFilterConfig({
|
||||
filterConfigs: DATA_PRODUCT_FILTER_CONFIGS,
|
||||
filterOptions: dataProductListing.filterOptions || {},
|
||||
selectedFilters: dataProductListing.urlState.filters,
|
||||
onFilterChange: dataProductListing.handleFilterChange,
|
||||
onFilterSearch: dataProductListing.searchFilterOptions,
|
||||
});
|
||||
|
||||
// Composable hooks for each UI component
|
||||
const { breadcrumbs } = useBreadcrumbs({
|
||||
entityLabelKey: 'label.data-product',
|
||||
@ -135,10 +142,6 @@ const DataProductListPage = () => {
|
||||
initialSearchQuery: dataProductListing.urlState.searchQuery,
|
||||
});
|
||||
|
||||
const { filterDropdowns } = useFilterDropdowns({
|
||||
filters: dropdownConfigs,
|
||||
});
|
||||
|
||||
const { view, viewToggle } = useViewToggle();
|
||||
const { dataProductCardTemplate } = useDomainCardTemplates();
|
||||
|
||||
@ -190,7 +193,7 @@ const DataProductListPage = () => {
|
||||
}}>
|
||||
{titleAndCount}
|
||||
{search}
|
||||
{filterDropdowns}
|
||||
{quickFilters}
|
||||
<Box ml="auto" />
|
||||
{viewToggle}
|
||||
{deleteIconButton}
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MOCK_DOMAINS } from '../../../mocks/Domains.mock';
|
||||
import { DOMAINS_LIST } from '../../../mocks/Domains.mock';
|
||||
import { getDomainByName } from '../../../rest/domainAPI';
|
||||
import DomainDetailPage from './DomainDetailPage.component';
|
||||
|
||||
@ -38,7 +38,7 @@ const mockGetDomainByName = getDomainByName as jest.MockedFunction<
|
||||
|
||||
describe('DomainDetailPage', () => {
|
||||
beforeEach(() => {
|
||||
mockGetDomainByName.mockResolvedValue(MOCK_DOMAINS[0]);
|
||||
mockGetDomainByName.mockResolvedValue(DOMAINS_LIST[0]);
|
||||
});
|
||||
|
||||
it('should render domain detail page', async () => {
|
||||
|
@ -12,15 +12,15 @@
|
||||
*/
|
||||
|
||||
import { Box, Paper, TableContainer, useTheme } from '@mui/material';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { useDelete } from '../../common/atoms/actions/useDelete';
|
||||
import { useDomainCardTemplates } from '../../common/atoms/domain/ui/useDomainCardTemplates';
|
||||
import { useFilterConfig } from '../../common/atoms/filters/useFilterConfig';
|
||||
import { useFilterDropdowns } from '../../common/atoms/filters/useFilterDropdowns';
|
||||
import { useDomainFilters } from '../../common/atoms/domain/ui/useDomainFilters';
|
||||
import { useQuickFilters } from '../../common/atoms/filters/useQuickFilters';
|
||||
import { useSearch } from '../../common/atoms/navigation/useSearch';
|
||||
import { useTitleAndCount } from '../../common/atoms/navigation/useTitleAndCount';
|
||||
import { useViewToggle } from '../../common/atoms/navigation/useViewToggle';
|
||||
import { usePaginationControls } from '../../common/atoms/pagination/usePaginationControls';
|
||||
import { SUBDOMAIN_FILTER_CONFIGS } from '../../common/atoms/shared/utils/commonFilterConfigs';
|
||||
import { useCardView } from '../../common/atoms/table/useCardView';
|
||||
import { useDataTable } from '../../common/atoms/table/useDataTable';
|
||||
import { useSubdomainListingData } from './hooks/useSubdomainListingData';
|
||||
@ -36,12 +36,19 @@ const SubDomainsTable = ({
|
||||
parentDomainFqn: domainFqn,
|
||||
});
|
||||
|
||||
const { dropdownConfigs } = useFilterConfig({
|
||||
filterConfigs: SUBDOMAIN_FILTER_CONFIGS,
|
||||
filterOptions: subdomainListing.filterOptions || {},
|
||||
selectedFilters: subdomainListing.urlState.filters,
|
||||
// Use the same domain filters configuration
|
||||
const domainFilters = useDomainFilters({
|
||||
enabledFilters: ['owner', 'tags', 'glossary', 'domainType'],
|
||||
});
|
||||
|
||||
const { quickFilters } = useQuickFilters({
|
||||
filterFields: domainFilters.filterFields,
|
||||
filterConfigs: domainFilters.filterConfigs,
|
||||
queryConfig: domainFilters.queryConfig,
|
||||
onFilterChange: subdomainListing.handleFilterChange,
|
||||
onFilterSearch: subdomainListing.searchFilterOptions,
|
||||
aggregations: subdomainListing.aggregations || {},
|
||||
searchIndex: SearchIndex.DOMAIN,
|
||||
independent: true,
|
||||
});
|
||||
|
||||
const { titleAndCount } = useTitleAndCount({
|
||||
@ -51,15 +58,11 @@ const SubDomainsTable = ({
|
||||
});
|
||||
|
||||
const { search } = useSearch({
|
||||
searchPlaceholder: 'Search subdomains',
|
||||
searchPlaceholderKey: 'label.search-subdomain',
|
||||
onSearchChange: subdomainListing.handleSearchChange,
|
||||
initialSearchQuery: subdomainListing.urlState.searchQuery,
|
||||
});
|
||||
|
||||
const { filterDropdowns } = useFilterDropdowns({
|
||||
filters: dropdownConfigs,
|
||||
});
|
||||
|
||||
const { view, viewToggle } = useViewToggle();
|
||||
const { domainCardTemplate } = useDomainCardTemplates();
|
||||
|
||||
@ -108,7 +111,7 @@ const SubDomainsTable = ({
|
||||
}}>
|
||||
{titleAndCount}
|
||||
{search}
|
||||
{filterDropdowns}
|
||||
{quickFilters}
|
||||
<Box ml="auto" />
|
||||
{viewToggle}
|
||||
{deleteIconButton}
|
||||
|
@ -17,22 +17,22 @@ import { AxiosError } from 'axios';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ERROR_MESSAGE } from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { withPageLayout } from '../../hoc/withPageLayout';
|
||||
import { addDomains } from '../../rest/domainAPI';
|
||||
import { getIsErrorMatch } from '../../utils/CommonUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import { useDelete } from '../common/atoms/actions/useDelete';
|
||||
import { useDomainCardTemplates } from '../common/atoms/domain/ui/useDomainCardTemplates';
|
||||
import { useDomainFilters } from '../common/atoms/domain/ui/useDomainFilters';
|
||||
import { useFormDrawerWithRef } from '../common/atoms/drawer';
|
||||
import { useFilterConfig } from '../common/atoms/filters/useFilterConfig';
|
||||
import { useFilterDropdowns } from '../common/atoms/filters/useFilterDropdowns';
|
||||
import { useQuickFilters } from '../common/atoms/filters/useQuickFilters';
|
||||
import { useBreadcrumbs } from '../common/atoms/navigation/useBreadcrumbs';
|
||||
import { usePageHeader } from '../common/atoms/navigation/usePageHeader';
|
||||
import { useSearch } from '../common/atoms/navigation/useSearch';
|
||||
import { useTitleAndCount } from '../common/atoms/navigation/useTitleAndCount';
|
||||
import { useViewToggle } from '../common/atoms/navigation/useViewToggle';
|
||||
import { usePaginationControls } from '../common/atoms/pagination/usePaginationControls';
|
||||
import { DOMAIN_FILTER_CONFIGS } from '../common/atoms/shared/utils/commonFilterConfigs';
|
||||
import { useCardView } from '../common/atoms/table/useCardView';
|
||||
import { useDataTable } from '../common/atoms/table/useDataTable';
|
||||
import AddDomainForm from '../Domain/AddDomainForm/AddDomainForm.component';
|
||||
@ -46,6 +46,20 @@ const DomainListPage = () => {
|
||||
const [form] = useForm();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Use the existing domain filters configuration
|
||||
const domainFilters = useDomainFilters({
|
||||
enabledFilters: ['owner', 'tags', 'glossary', 'domainType'],
|
||||
});
|
||||
|
||||
const { quickFilters } = useQuickFilters({
|
||||
filterFields: domainFilters.filterFields,
|
||||
filterConfigs: domainFilters.filterConfigs,
|
||||
queryConfig: domainFilters.queryConfig,
|
||||
onFilterChange: domainListing.handleFilterChange,
|
||||
aggregations: domainListing.aggregations || {},
|
||||
searchIndex: SearchIndex.DOMAIN,
|
||||
});
|
||||
|
||||
const { formDrawer, openDrawer, closeDrawer } = useFormDrawerWithRef({
|
||||
title: t('label.add-entity', { entity: t('label.domain') }),
|
||||
anchor: 'right',
|
||||
@ -99,14 +113,6 @@ const DomainListPage = () => {
|
||||
loading: isLoading,
|
||||
});
|
||||
|
||||
const { dropdownConfigs } = useFilterConfig({
|
||||
filterConfigs: DOMAIN_FILTER_CONFIGS,
|
||||
filterOptions: domainListing.filterOptions || {},
|
||||
selectedFilters: domainListing.urlState.filters,
|
||||
onFilterChange: domainListing.handleFilterChange,
|
||||
onFilterSearch: domainListing.searchFilterOptions,
|
||||
});
|
||||
|
||||
// Composable hooks for each UI component
|
||||
const { breadcrumbs } = useBreadcrumbs({
|
||||
entityLabelKey: 'label.domain',
|
||||
@ -133,10 +139,6 @@ const DomainListPage = () => {
|
||||
initialSearchQuery: domainListing.urlState.searchQuery,
|
||||
});
|
||||
|
||||
const { filterDropdowns } = useFilterDropdowns({
|
||||
filters: dropdownConfigs,
|
||||
});
|
||||
|
||||
const { view, viewToggle } = useViewToggle();
|
||||
const { domainCardTemplate } = useDomainCardTemplates();
|
||||
|
||||
@ -188,7 +190,7 @@ const DomainListPage = () => {
|
||||
}}>
|
||||
{titleAndCount}
|
||||
{search}
|
||||
{filterDropdowns}
|
||||
{quickFilters}
|
||||
<Box ml="auto" />
|
||||
{viewToggle}
|
||||
{deleteIconButton}
|
||||
|
@ -13,8 +13,8 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { getAggregations } from '../../../../utils/ExploreUtils';
|
||||
import { useDataFetching } from '../data/useDataFetching';
|
||||
import { useFilterOptions } from '../data/useFilterOptions';
|
||||
import { useSelectionState } from '../data/useSelectionState';
|
||||
import { useUrlState } from '../data/useUrlState';
|
||||
import { usePaginationState } from '../pagination/usePaginationState';
|
||||
@ -80,11 +80,6 @@ export const useListingData = <T extends { id: string }>(
|
||||
|
||||
const selectionState = useSelectionState(dataFetching.entities);
|
||||
|
||||
const filterOptionsHook = useFilterOptions({
|
||||
searchIndex,
|
||||
filterFields,
|
||||
});
|
||||
|
||||
const actionHandlers = useActionHandlers<T>({
|
||||
basePath,
|
||||
getEntityPath,
|
||||
@ -176,11 +171,12 @@ export const useListingData = <T extends { id: string }>(
|
||||
clearSelection: selectionState.clearSelection,
|
||||
urlState,
|
||||
actionHandlers,
|
||||
filterOptions: filterOptionsHook.filterOptions,
|
||||
filterOptions: {},
|
||||
aggregations: getAggregations(dataFetching.aggregations || {}),
|
||||
handleSearchChange,
|
||||
handleFilterChange,
|
||||
handlePageChange,
|
||||
searchFilterOptions: filterOptionsHook.searchFilterOptions,
|
||||
searchFilterOptions: () => Promise.resolve([]),
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
@ -1,51 +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 } from 'react';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { getAggregationOptions } from '../../../../utils/ExploreUtils';
|
||||
import { FilterField } from '../types';
|
||||
|
||||
/**
|
||||
* Fetches aggregation data from API for filter dropdowns
|
||||
*
|
||||
* @description
|
||||
* Pure API fetching hook that calls the aggregation endpoint to get
|
||||
* filter option data. Handles both initial fetch and search-within-filter.
|
||||
* Does not manage state - only makes API calls.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const fetchAggregation = useAggregationFetcher();
|
||||
* const result = await fetchAggregation(SearchIndex.DOMAIN, field, searchTerm);
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - No external dependencies, pure function
|
||||
* @complexity Low - Single API call responsibility
|
||||
*/
|
||||
export const useAggregationFetcher = () => {
|
||||
const fetchAggregation = useCallback(
|
||||
async (searchIndex: SearchIndex, field: FilterField, searchTerm = '') => {
|
||||
return await getAggregationOptions(
|
||||
searchIndex,
|
||||
field.aggregationField,
|
||||
searchTerm,
|
||||
JSON.stringify({}),
|
||||
false
|
||||
);
|
||||
},
|
||||
[] // No dependencies - pure API call
|
||||
);
|
||||
|
||||
return fetchAggregation;
|
||||
};
|
@ -1,68 +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 } from 'react';
|
||||
|
||||
/**
|
||||
* Processes raw aggregation API response into SearchDropdownOption format
|
||||
*
|
||||
* @description
|
||||
* This hook converts the raw response from Elasticsearch aggregation APIs
|
||||
* into a standardized format that can be used by SearchDropdown components.
|
||||
* Handles both custom processors and default bucket processing.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const processResult = useAggregationProcessor();
|
||||
* const options = processResult(apiResponse, customProcessor);
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - Uses empty dependency array for maximum stability
|
||||
* @complexity Low - Single transformation responsibility
|
||||
*/
|
||||
export const useAggregationProcessor = () => {
|
||||
const processAggregationResult = useCallback(
|
||||
(
|
||||
result: unknown,
|
||||
processor?: (result: unknown) => unknown[]
|
||||
): unknown[] => {
|
||||
if ((result as any)?.status !== 'fulfilled') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
return processor(result);
|
||||
}
|
||||
|
||||
// Default processing for simple aggregations
|
||||
const buckets = Object.values(
|
||||
(result as any).value?.data?.aggregations || {}
|
||||
)[0] as unknown;
|
||||
|
||||
if (!(buckets as any)?.buckets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (
|
||||
(buckets as any)?.buckets?.map((bucket: any) => ({
|
||||
key: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.doc_count,
|
||||
})) || []
|
||||
);
|
||||
},
|
||||
[] // Empty dependency array ensures maximum stability
|
||||
);
|
||||
|
||||
return processAggregationResult;
|
||||
};
|
@ -14,6 +14,7 @@
|
||||
import { AxiosError } from 'axios';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Aggregations } from '../../../../interface/search.interface';
|
||||
import { searchData } from '../../../../rest/miscAPI';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
|
||||
@ -30,6 +31,7 @@ export interface DataFetchingResult<T> {
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
totalEntities: number;
|
||||
aggregations: Aggregations | null;
|
||||
refetch: () => void;
|
||||
searchEntities: (
|
||||
page: number,
|
||||
@ -45,6 +47,7 @@ export const useDataFetching = <T extends { id: string }>(
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [totalEntities, setTotalEntities] = useState(0);
|
||||
const [aggregations, setAggregations] = useState<Aggregations | null>(null);
|
||||
|
||||
const {
|
||||
searchIndex,
|
||||
@ -112,15 +115,18 @@ export const useDataFetching = <T extends { id: string }>(
|
||||
// Process response
|
||||
const transformedEntities = transformData(response.data);
|
||||
const total = response.data?.hits?.total?.value || 0;
|
||||
const responseAggregations = response.data?.aggregations || null;
|
||||
|
||||
// Update state
|
||||
setEntities(transformedEntities);
|
||||
setTotalEntities(total);
|
||||
setAggregations(responseAggregations);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error('Search failed'));
|
||||
setEntities([]);
|
||||
setTotalEntities(0);
|
||||
setAggregations(null);
|
||||
showErrorToast(err as AxiosError);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -139,6 +145,7 @@ export const useDataFetching = <T extends { id: string }>(
|
||||
loading,
|
||||
error,
|
||||
totalEntities,
|
||||
aggregations,
|
||||
refetch,
|
||||
searchEntities,
|
||||
};
|
||||
|
@ -1,52 +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, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Manages loading state for filter operations
|
||||
*
|
||||
* @description
|
||||
* Simple loading state manager specifically for filter-related operations.
|
||||
* Provides start/stop loading functions and current loading state.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { loading, startLoading, stopLoading } = useFilterLoadingState();
|
||||
*
|
||||
* // In async operation:
|
||||
* startLoading();
|
||||
* await fetchData();
|
||||
* stopLoading();
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - No external dependencies
|
||||
* @complexity Very Low - Simple state management only
|
||||
*/
|
||||
export const useFilterLoadingState = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const startLoading = useCallback(() => {
|
||||
setLoading(true);
|
||||
}, []);
|
||||
|
||||
const stopLoading = useCallback(() => {
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
};
|
||||
};
|
@ -1,60 +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 { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { FilterField } from '../types';
|
||||
import { useFilterOptionsComposition } from './useFilterOptionsComposition';
|
||||
|
||||
interface UseFilterOptionsProps {
|
||||
searchIndex: SearchIndex;
|
||||
filterFields: FilterField[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete filter options system for dropdown filters
|
||||
*
|
||||
* @description
|
||||
* Provides complete filter options functionality by composing multiple
|
||||
* micro hooks. This is the main interface for filter options that
|
||||
* components should use.
|
||||
*
|
||||
* Internally composed of:
|
||||
* - useAggregationProcessor: Data transformation
|
||||
* - useAggregationFetcher: API calls
|
||||
* - useFilterLoadingState: Loading management
|
||||
* - useFilterOptionsState: State management
|
||||
* - useFilterOptionsComposition: Orchestration
|
||||
*
|
||||
* @param config.searchIndex - Elasticsearch index to fetch aggregations from
|
||||
* @param config.filterFields - Array of fields to create filter options for
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { filterOptions, loading, searchFilterOptions } = useFilterOptions({
|
||||
* searchIndex: SearchIndex.DOMAIN,
|
||||
* filterFields: [COMMON_FILTER_FIELDS.owners]
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - Composes stable micro hooks
|
||||
* @complexity Low - Simple composition interface
|
||||
*/
|
||||
export const useFilterOptions = ({
|
||||
searchIndex,
|
||||
filterFields,
|
||||
}: UseFilterOptionsProps) => {
|
||||
return useFilterOptionsComposition({
|
||||
searchIndex,
|
||||
filterFields,
|
||||
});
|
||||
};
|
@ -1,147 +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, useEffect } from 'react';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { FilterField } from '../types';
|
||||
import { useAggregationFetcher } from './useAggregationFetcher';
|
||||
import { useAggregationProcessor } from './useAggregationProcessor';
|
||||
import { useFilterLoadingState } from './useFilterLoadingState';
|
||||
import { useFilterOptionsState } from './useFilterOptionsState';
|
||||
|
||||
interface UseFilterOptionsCompositionProps {
|
||||
searchIndex: SearchIndex;
|
||||
filterFields: FilterField[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes filter option micro hooks into complete filter options system
|
||||
*
|
||||
* @description
|
||||
* Combines multiple micro hooks to provide complete filter options functionality:
|
||||
* - State management (useFilterOptionsState)
|
||||
* - API fetching (useAggregationFetcher)
|
||||
* - Data processing (useAggregationProcessor)
|
||||
* - Loading management (useFilterLoadingState)
|
||||
*
|
||||
* Each micro hook has a single responsibility, making this composition
|
||||
* easy to understand, test, and debug.
|
||||
*
|
||||
* @param config.searchIndex - Elasticsearch index to fetch aggregations from
|
||||
* @param config.filterFields - Array of fields to create filter options for
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { filterOptions, loading, searchFilterOptions } = useFilterOptionsComposition({
|
||||
* searchIndex: SearchIndex.DOMAIN,
|
||||
* filterFields: [COMMON_FILTER_FIELDS.owners, COMMON_FILTER_FIELDS.tags]
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - Composes stable micro hooks
|
||||
* @complexity Low - Simple composition pattern
|
||||
*/
|
||||
export const useFilterOptionsComposition = ({
|
||||
searchIndex,
|
||||
filterFields,
|
||||
}: UseFilterOptionsCompositionProps) => {
|
||||
// Micro hooks - each with single responsibility
|
||||
const processResult = useAggregationProcessor();
|
||||
const fetchAggregation = useAggregationFetcher();
|
||||
const { loading, startLoading, stopLoading } = useFilterLoadingState();
|
||||
const { filterOptions, setFilterOptions, updateFilterField } =
|
||||
useFilterOptionsState();
|
||||
|
||||
// Fetch all filter options (initial load)
|
||||
const fetchAllFilterOptions = useCallback(async () => {
|
||||
startLoading();
|
||||
try {
|
||||
const results = await Promise.allSettled(
|
||||
filterFields.map((field) => fetchAggregation(searchIndex, field))
|
||||
);
|
||||
|
||||
const newFilterOptions = filterFields.reduce((acc, field, index) => {
|
||||
acc[field.key] = processResult(results[index], field.processor);
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, unknown[]>);
|
||||
|
||||
setFilterOptions(newFilterOptions);
|
||||
} catch (error) {
|
||||
// Reset to empty options on error
|
||||
setFilterOptions({});
|
||||
} finally {
|
||||
stopLoading();
|
||||
}
|
||||
}, [
|
||||
searchIndex,
|
||||
filterFields,
|
||||
fetchAggregation,
|
||||
processResult,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
setFilterOptions,
|
||||
]);
|
||||
|
||||
// Search within specific filter field
|
||||
const searchFilterOptions = useCallback(
|
||||
async (fieldKey: string, searchTerm: string) => {
|
||||
const field = filterFields.find((f) => f.key === fieldKey);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!searchTerm.trim()) {
|
||||
fetchAllFilterOptions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
startLoading();
|
||||
try {
|
||||
const result = await fetchAggregation(searchIndex, field, searchTerm);
|
||||
const processedOptions = processResult(
|
||||
{ status: 'fulfilled', value: result },
|
||||
field.processor
|
||||
);
|
||||
updateFilterField(fieldKey, processedOptions);
|
||||
} catch (error) {
|
||||
// Keep existing options on search error
|
||||
} finally {
|
||||
stopLoading();
|
||||
}
|
||||
},
|
||||
[
|
||||
filterFields,
|
||||
searchIndex,
|
||||
fetchAggregation,
|
||||
processResult,
|
||||
updateFilterField,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
fetchAllFilterOptions,
|
||||
]
|
||||
);
|
||||
|
||||
// Load filter options on mount
|
||||
useEffect(() => {
|
||||
fetchAllFilterOptions();
|
||||
}, [fetchAllFilterOptions]);
|
||||
|
||||
return {
|
||||
filterOptions,
|
||||
loading,
|
||||
refetch: fetchAllFilterOptions,
|
||||
searchFilterOptions,
|
||||
};
|
||||
};
|
@ -1,62 +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, useState } from 'react';
|
||||
import { FilterOptions } from '../types';
|
||||
|
||||
/**
|
||||
* Manages filter options state (data storage only)
|
||||
*
|
||||
* @description
|
||||
* Simple state manager for filter dropdown options. Stores the options
|
||||
* object and provides functions to update specific filter fields.
|
||||
* Does not fetch data - only manages state.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { filterOptions, setFilterOptions, updateFilterField } = useFilterOptionsState();
|
||||
*
|
||||
* // Set all options
|
||||
* setFilterOptions({ owners: [...], tags: [...] });
|
||||
*
|
||||
* // Update specific field
|
||||
* updateFilterField('owners', newOwnerOptions);
|
||||
* ```
|
||||
*
|
||||
* @stability Stable - No external dependencies
|
||||
* @complexity Very Low - Basic state management only
|
||||
*/
|
||||
export const useFilterOptionsState = () => {
|
||||
const [filterOptions, setFilterOptions] = useState<FilterOptions>({});
|
||||
|
||||
const updateFilterField = useCallback(
|
||||
(fieldKey: string, options: unknown[]) => {
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
[fieldKey]: options,
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const clearFilterOptions = useCallback(() => {
|
||||
setFilterOptions({});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
filterOptions,
|
||||
setFilterOptions,
|
||||
updateFilterField,
|
||||
clearFilterOptions,
|
||||
};
|
||||
};
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { FilterConfig, FilterField } from '../../shared/types';
|
||||
import { COMMON_FILTER_FIELDS } from '../../shared/utils/commonFilterConfigs';
|
||||
|
||||
interface UseDataProductFiltersConfig {
|
||||
enabledFilters?: string[];
|
||||
customFilterFields?: FilterField[];
|
||||
customFilterConfigs?: FilterConfig[];
|
||||
}
|
||||
|
||||
export const useDataProductFilters = (
|
||||
config: UseDataProductFiltersConfig = {}
|
||||
) => {
|
||||
const { enabledFilters = ['owner', 'expert', 'tags', 'glossary'] } = config;
|
||||
|
||||
const filterKeys = useMemo(() => enabledFilters, []);
|
||||
|
||||
const queryConfig = useMemo(
|
||||
() => ({
|
||||
owner: 'owners.displayName.keyword',
|
||||
expert: 'experts.displayName.keyword',
|
||||
tags: 'classificationTags',
|
||||
glossary: 'glossaryTags',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const filterFields = useMemo(
|
||||
() => [
|
||||
COMMON_FILTER_FIELDS.owners,
|
||||
COMMON_FILTER_FIELDS.experts,
|
||||
COMMON_FILTER_FIELDS.tags,
|
||||
COMMON_FILTER_FIELDS.glossary,
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const filterConfigs = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'owners',
|
||||
labelKey: 'label.owner',
|
||||
searchKey: 'owner.displayName',
|
||||
optionsKey: 'owners',
|
||||
selectedKey: 'owner',
|
||||
},
|
||||
{
|
||||
key: 'experts',
|
||||
labelKey: 'label.expert-plural',
|
||||
searchKey: 'expert.displayName',
|
||||
optionsKey: 'experts',
|
||||
selectedKey: 'expert',
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
labelKey: 'label.tag-plural',
|
||||
searchKey: 'tags.tagFQN',
|
||||
optionsKey: 'tags',
|
||||
selectedKey: 'tags',
|
||||
},
|
||||
{
|
||||
key: 'glossary',
|
||||
labelKey: 'label.glossary-term-plural',
|
||||
searchKey: 'glossaryTerms',
|
||||
optionsKey: 'glossary',
|
||||
selectedKey: 'glossary',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
filterKeys,
|
||||
queryConfig,
|
||||
filterFields,
|
||||
filterConfigs,
|
||||
};
|
||||
};
|
@ -22,11 +22,8 @@ interface UseDomainFiltersConfig {
|
||||
}
|
||||
|
||||
export const useDomainFilters = (config: UseDomainFiltersConfig = {}) => {
|
||||
const {
|
||||
enabledFilters = ['owner', 'tags', 'glossary'], // Temporarily disabled domainType due to API errors
|
||||
customFilterFields = [],
|
||||
customFilterConfigs = [],
|
||||
} = config;
|
||||
const { enabledFilters = ['owner', 'tags', 'glossary', 'domainType'] } =
|
||||
config;
|
||||
|
||||
const filterKeys = useMemo(() => enabledFilters, []);
|
||||
|
||||
@ -35,7 +32,7 @@ export const useDomainFilters = (config: UseDomainFiltersConfig = {}) => {
|
||||
owner: 'owners.displayName.keyword',
|
||||
tags: 'classificationTags',
|
||||
glossary: 'glossaryTags',
|
||||
domainType: 'domainType',
|
||||
domainType: 'domainType.keyword',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
@ -45,7 +42,7 @@ export const useDomainFilters = (config: UseDomainFiltersConfig = {}) => {
|
||||
COMMON_FILTER_FIELDS.owners,
|
||||
COMMON_FILTER_FIELDS.tags,
|
||||
COMMON_FILTER_FIELDS.glossary,
|
||||
// COMMON_FILTER_FIELDS.domainTypes, // Temporarily disabled
|
||||
COMMON_FILTER_FIELDS.domainTypes, // Temporarily disabled
|
||||
],
|
||||
[]
|
||||
);
|
||||
@ -73,7 +70,13 @@ export const useDomainFilters = (config: UseDomainFiltersConfig = {}) => {
|
||||
optionsKey: 'glossary',
|
||||
selectedKey: 'glossary',
|
||||
},
|
||||
// domainType temporarily disabled
|
||||
{
|
||||
key: 'domainTypes',
|
||||
labelKey: 'label.domain-type',
|
||||
searchKey: 'domainType',
|
||||
optionsKey: 'domainTypes',
|
||||
selectedKey: 'domainType',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
@ -1,67 +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 { Box } from '@mui/material';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SearchDropdown from '../../../SearchDropdown/SearchDropdown';
|
||||
import { SearchDropdownOption } from '../../../SearchDropdown/SearchDropdown.interface';
|
||||
|
||||
interface FilterConfig {
|
||||
key: string;
|
||||
labelKey: string;
|
||||
searchKey: string;
|
||||
options: SearchDropdownOption[];
|
||||
selectedOptions: SearchDropdownOption[];
|
||||
onChange: (values: string[]) => void;
|
||||
onSearch: (searchTerm: string) => void;
|
||||
}
|
||||
|
||||
interface EntityFiltersGenericConfig {
|
||||
filters: FilterConfig[];
|
||||
}
|
||||
|
||||
export const useEntityFiltersGeneric = (config: EntityFiltersGenericConfig) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Inline implementation copying exact EntityFiltersGeneric functionality
|
||||
const entityFiltersGeneric = useMemo(
|
||||
() => (
|
||||
<Box sx={{ display: 'flex', gap: 4 }}>
|
||||
{config.filters.map((filter) => (
|
||||
<SearchDropdown
|
||||
key={filter.key}
|
||||
label={t(filter.labelKey)}
|
||||
options={filter.options}
|
||||
searchKey={filter.searchKey}
|
||||
selectedKeys={filter.selectedOptions}
|
||||
onChange={(values) => {
|
||||
filter.onChange(values.map((v) => v.key));
|
||||
}}
|
||||
onSearch={filter.onSearch}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
),
|
||||
[config, t]
|
||||
);
|
||||
|
||||
return {
|
||||
entityFiltersGeneric,
|
||||
hasActiveFilters: config.filters.some(
|
||||
(filter) => filter.selectedOptions.length > 0
|
||||
),
|
||||
clearAllFilters: () =>
|
||||
config.filters.forEach((filter) => filter.onChange([])),
|
||||
};
|
||||
};
|
@ -1,55 +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 { useMemo } from 'react';
|
||||
import { DropdownConfig, FilterConfig, FilterOptions } from '../types';
|
||||
|
||||
interface UseFilterConfigProps {
|
||||
filterConfigs: FilterConfig[];
|
||||
filterOptions: FilterOptions;
|
||||
selectedFilters: Record<string, string[]>;
|
||||
onFilterChange: (key: string, values: string[]) => void;
|
||||
onFilterSearch: (key: string, term: string) => void;
|
||||
}
|
||||
|
||||
export const useFilterConfig = (props: UseFilterConfigProps) => {
|
||||
const {
|
||||
filterConfigs,
|
||||
filterOptions,
|
||||
selectedFilters,
|
||||
onFilterChange,
|
||||
onFilterSearch,
|
||||
} = props;
|
||||
|
||||
const dropdownConfigs: DropdownConfig[] = useMemo(() => {
|
||||
return filterConfigs.map((config) => ({
|
||||
key: config.key,
|
||||
labelKey: config.labelKey,
|
||||
searchKey: config.searchKey,
|
||||
options: filterOptions[config.optionsKey] || [],
|
||||
selectedOptions: selectedFilters[config.selectedKey] || [],
|
||||
onChange: (values: string[]) => onFilterChange(config.key, values),
|
||||
onSearch: (term: string) => onFilterSearch(config.optionsKey, term),
|
||||
}));
|
||||
}, [
|
||||
filterConfigs,
|
||||
filterOptions,
|
||||
selectedFilters,
|
||||
onFilterChange,
|
||||
onFilterSearch,
|
||||
]);
|
||||
|
||||
return {
|
||||
dropdownConfigs,
|
||||
};
|
||||
};
|
@ -1,71 +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 { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SearchDropdownOption } from '../../../SearchDropdown/SearchDropdown.interface';
|
||||
import { useEntityFiltersGeneric } from './useEntityFiltersGeneric';
|
||||
|
||||
interface FilterDropdownsConfig {
|
||||
filters: Array<{
|
||||
key: string;
|
||||
labelKey: string;
|
||||
searchKey: string;
|
||||
options: any[];
|
||||
selectedOptions: any[];
|
||||
onChange: (values: string[]) => void;
|
||||
onSearch: (term: string) => void;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const useFilterDropdowns = (config: FilterDropdownsConfig) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Convert our filter format to the format expected by useEntityFiltersGeneric
|
||||
const convertedFilters = useMemo(() => {
|
||||
return config.filters.map((filter) => ({
|
||||
key: filter.key,
|
||||
labelKey: filter.labelKey,
|
||||
searchKey: filter.searchKey,
|
||||
options: filter.options.map(
|
||||
(option): SearchDropdownOption => ({
|
||||
key: option.key || option.label || option,
|
||||
label: option.label || option.key || option,
|
||||
count: option.count || 0,
|
||||
})
|
||||
),
|
||||
selectedOptions: filter.selectedOptions.map(
|
||||
(option): SearchDropdownOption => ({
|
||||
key: typeof option === 'string' ? option : option.key || option.label,
|
||||
label:
|
||||
typeof option === 'string' ? option : option.label || option.key,
|
||||
count: typeof option === 'object' ? option.count || 0 : 0,
|
||||
})
|
||||
),
|
||||
onChange: filter.onChange,
|
||||
onSearch: filter.onSearch,
|
||||
}));
|
||||
}, [config.filters]);
|
||||
|
||||
const { entityFiltersGeneric, hasActiveFilters, clearAllFilters } =
|
||||
useEntityFiltersGeneric({
|
||||
filters: convertedFilters,
|
||||
});
|
||||
|
||||
return {
|
||||
filterDropdowns: entityFiltersGeneric,
|
||||
clearAllFilters,
|
||||
hasActiveFilters,
|
||||
filterCount: config.filters.length,
|
||||
};
|
||||
};
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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,
|
||||
};
|
||||
};
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { Aggregations } from '../../../../../interface/search.interface';
|
||||
|
||||
export interface UrlStateConfig {
|
||||
searchKey?: string;
|
||||
@ -123,6 +124,7 @@ export interface ListingData<T> {
|
||||
urlState: UrlState;
|
||||
actionHandlers: ActionHandlers<T>;
|
||||
filterOptions?: FilterOptions;
|
||||
aggregations?: Aggregations | null;
|
||||
handleSearchChange: (query: string) => void;
|
||||
handleFilterChange: (key: string, values: string[]) => void;
|
||||
handlePageChange: (page: number) => void;
|
||||
|
@ -11,19 +11,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FilterConfig, FilterField } from '../types';
|
||||
import { processOwnerOptions } from './entityFilterProcessors';
|
||||
import { FilterField } from '../types';
|
||||
|
||||
export const COMMON_FILTER_FIELDS: Record<string, FilterField> = {
|
||||
owners: {
|
||||
key: 'owners',
|
||||
aggregationField: 'owners.displayName.keyword',
|
||||
processor: processOwnerOptions,
|
||||
},
|
||||
experts: {
|
||||
key: 'experts',
|
||||
aggregationField: 'experts.displayName.keyword',
|
||||
processor: processOwnerOptions,
|
||||
},
|
||||
tags: {
|
||||
key: 'tags',
|
||||
@ -35,99 +32,6 @@ export const COMMON_FILTER_FIELDS: Record<string, FilterField> = {
|
||||
},
|
||||
domainTypes: {
|
||||
key: 'domainTypes',
|
||||
aggregationField: 'domainType',
|
||||
aggregationField: 'domainType.keyword',
|
||||
},
|
||||
};
|
||||
|
||||
export const DOMAIN_FILTER_CONFIGS: FilterConfig[] = [
|
||||
{
|
||||
key: 'owner',
|
||||
labelKey: 'label.owner',
|
||||
searchKey: 'owner.displayName',
|
||||
optionsKey: 'owners',
|
||||
selectedKey: 'owner',
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
labelKey: 'label.tag-plural',
|
||||
searchKey: 'tags.tagFQN',
|
||||
optionsKey: 'tags',
|
||||
selectedKey: 'tags',
|
||||
},
|
||||
{
|
||||
key: 'glossary',
|
||||
labelKey: 'label.glossary-term-plural',
|
||||
searchKey: 'glossaryTerms',
|
||||
optionsKey: 'glossary',
|
||||
selectedKey: 'glossary',
|
||||
},
|
||||
{
|
||||
key: 'domainType',
|
||||
labelKey: 'label.domain-type',
|
||||
searchKey: 'domainType',
|
||||
optionsKey: 'domainTypes',
|
||||
selectedKey: 'domainType',
|
||||
},
|
||||
];
|
||||
|
||||
export const DATA_PRODUCT_FILTER_CONFIGS: FilterConfig[] = [
|
||||
{
|
||||
key: 'owner',
|
||||
labelKey: 'label.owner',
|
||||
searchKey: 'owner.displayName',
|
||||
optionsKey: 'owners',
|
||||
selectedKey: 'owner',
|
||||
},
|
||||
{
|
||||
key: 'expert',
|
||||
labelKey: 'label.expert',
|
||||
searchKey: 'expert.displayName',
|
||||
optionsKey: 'experts',
|
||||
selectedKey: 'expert',
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
labelKey: 'label.tag-plural',
|
||||
searchKey: 'tags.tagFQN',
|
||||
optionsKey: 'tags',
|
||||
selectedKey: 'tags',
|
||||
},
|
||||
{
|
||||
key: 'glossary',
|
||||
labelKey: 'label.glossary-term-plural',
|
||||
searchKey: 'glossaryTerms',
|
||||
optionsKey: 'glossary',
|
||||
selectedKey: 'glossary',
|
||||
},
|
||||
];
|
||||
|
||||
export const SUBDOMAIN_FILTER_CONFIGS: FilterConfig[] = [
|
||||
{
|
||||
key: 'owner',
|
||||
labelKey: 'label.owner',
|
||||
searchKey: 'owner.displayName',
|
||||
optionsKey: 'owners',
|
||||
selectedKey: 'owner',
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
labelKey: 'label.tag-plural',
|
||||
searchKey: 'tags.tagFQN',
|
||||
optionsKey: 'tags',
|
||||
selectedKey: 'tags',
|
||||
},
|
||||
{
|
||||
key: 'glossary',
|
||||
labelKey: 'label.glossary-term-plural',
|
||||
searchKey: 'glossaryTerms',
|
||||
optionsKey: 'glossary',
|
||||
selectedKey: 'glossary',
|
||||
},
|
||||
{
|
||||
key: 'domainType',
|
||||
labelKey: 'label.domain-type',
|
||||
searchKey: 'domainType',
|
||||
optionsKey: 'domainTypes',
|
||||
selectedKey: 'domainType',
|
||||
},
|
||||
];
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Senden des Dateninsight-Berichts fehlgeschlagen.",
|
||||
"data-insight-report-send-success-message": "Dateninsight-Bericht erfolgreich gesendet.",
|
||||
"data-insight-subtitle": "Erhalten Sie einen einzigen Überblick über die Gesundheit aller Ihrer Datenvermögenswerte im Laufe der Zeit.",
|
||||
"data-products-no-data-message": "Es sieht so aus, als hätten Sie noch keine Datenprodukte hinzugefügt, erstellen Sie eines!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Es sieht so aus, als hätten Sie noch keine Datenprodukte hinzugefügt, erstellen Sie eines!",
|
||||
"data-quality-test-contract-title": "Datenqualitätstests und -überprüfungen",
|
||||
"database-service-name-message": "Fügen Sie die Namen der Datenbankdienste hinzu, um die Lineage zu erstellen.",
|
||||
"dbt-catalog-file-extract-path": " Pfad zur dbt-Katalogdatei, um dbt-Modelle mit ihren Spaltenschemata zu extrahieren.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Error al enviar el informe de información de datos.",
|
||||
"data-insight-report-send-success-message": "Informe de Información de Datos enviado correctamente.",
|
||||
"data-insight-subtitle": "Obtenga una vista global de la salud de todos sus activos de datos a lo largo del tiempo.",
|
||||
"data-products-no-data-message": "Parece que no has agregado ningún producto de datos, ¡crea uno!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Parece que no has agregado ningún producto de datos, ¡crea uno!",
|
||||
"data-quality-test-contract-title": "Pruebas de calidad de datos y aserciones",
|
||||
"database-service-name-message": "Agregue los nombres de servicio de la base de datos para crear la dependencia del linaje.",
|
||||
"dbt-catalog-file-extract-path": "Ruta del archivo de catálogo de dbt para extraer modelos de dbt con sus esquemas de columnas.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Rapport de l'Aperçu des Données envoyé avec échec.",
|
||||
"data-insight-report-send-success-message": "Rapport de l'Aperçu des Données envoyé avec succès.",
|
||||
"data-insight-subtitle": "Obtenir une vue à 360 de la santé de vos actifs de données au fil du temps.",
|
||||
"data-products-no-data-message": "Il semble que vous n'ayez ajouté aucun produit de données, créez-en un !",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Il semble que vous n'ayez ajouté aucun produit de données, créez-en un !",
|
||||
"data-quality-test-contract-title": "Tests et assertions de qualité des données",
|
||||
"database-service-name-message": "Ajouter les noms de service de base de données pour créer la traçabilité.",
|
||||
"dbt-catalog-file-extract-path": "fichier catalog.yml de dbt pour extraire les modèles dbt avec leurs schémas de colonnes.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Fallou o envío do informe de insights de datos.",
|
||||
"data-insight-report-send-success-message": "Informe de insights de datos enviado correctamente.",
|
||||
"data-insight-subtitle": "Obtén unha vista única da saúde de todos os teus activos de datos ao longo do tempo.",
|
||||
"data-products-no-data-message": "Parece que non engadiches ningún produto de datos, crea un!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Parece que non engadiches ningún produto de datos, crea un!",
|
||||
"data-quality-test-contract-title": "Probas de calidade de datos e asercións",
|
||||
"database-service-name-message": "Engade os nomes dos servizos da base de datos para crear liñaxe.",
|
||||
"dbt-catalog-file-extract-path": "Ficheiro de catálogo dbt para extraer modelos dbt coas súas esquemas de columnas.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "נכשל בשליחת דוח תובנות נתונים.",
|
||||
"data-insight-report-send-success-message": "דוח תובנות נתונים נשלח בהצלחה.",
|
||||
"data-insight-subtitle": "קבל תצוגה מרוכזת של הבריאות של כל הנכסים שלך מעל לאורך זמן.",
|
||||
"data-products-no-data-message": "נראה שלא הוספת מוצרי נתונים, צור אחד!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "נראה שלא הוספת מוצרי נתונים, צור אחד!",
|
||||
"data-quality-test-contract-title": "בדיקות איכות נתונים ואישורים",
|
||||
"database-service-name-message": "הוסף את שמות שירותי מסדי הנתונים ליצירת הקשריות.",
|
||||
"dbt-catalog-file-extract-path": " נתיב קובץ קטלוג dbt לחשיפת מודלי dbt עם סכמות עמודותיהם.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "データインサイトレポートの送信に失敗しました。",
|
||||
"data-insight-report-send-success-message": "データインサイトレポートが正常に送信されました。",
|
||||
"data-insight-subtitle": "すべてのデータアセットの健全性を一目で把握しましょう。",
|
||||
"data-products-no-data-message": "データプロダクトを追加していないようです、作成してください!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "データプロダクトを追加していないようです、作成してください!",
|
||||
"data-quality-test-contract-title": "Data quality tests and assertions",
|
||||
"database-service-name-message": "データベースサービス名を追加してリネージを作成します。",
|
||||
"dbt-catalog-file-extract-path": "dbt カタログファイルのパス(モデルとカラムスキーマを抽出)",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "데이터 인사이트 보고서 전송에 실패했습니다.",
|
||||
"data-insight-report-send-success-message": "데이터 인사이트 보고서가 성공적으로 전송되었습니다.",
|
||||
"data-insight-subtitle": "시간에 따른 모든 데이터 자산의 건강 상태를 단일 창에서 볼 수 있습니다.",
|
||||
"data-products-no-data-message": "데이터 제품을 추가하지 않은 것 같습니다, 하나 만들어보세요!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "데이터 제품을 추가하지 않은 것 같습니다, 하나 만들어보세요!",
|
||||
"data-quality-test-contract-title": "데이터 품질 테스트 및 검증",
|
||||
"database-service-name-message": "계보를 생성하려면 데이터베이스 서비스 이름을 추가하세요.",
|
||||
"dbt-catalog-file-extract-path": "열 스키마와 함께 dbt 모델을 추출하기 위한 dbt 카탈로그 파일입니다.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "डेटा अंतर्दृष्टी अहवाल पाठविण्यात अयशस्वी.",
|
||||
"data-insight-report-send-success-message": "डेटा अंतर्दृष्टी अहवाल यशस्वीरित्या पाठवला.",
|
||||
"data-insight-subtitle": "काळानुसार सर्व डेटा ॲसेटंच्या आरोग्याचे एकल दृश्य मिळवा.",
|
||||
"data-products-no-data-message": "असे दिसते की तुम्ही कोणतेही डेटा उत्पादने जोडली नाहीत, एक तयार करा!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "असे दिसते की तुम्ही कोणतेही डेटा उत्पादने जोडली नाहीत, एक तयार करा!",
|
||||
"data-quality-test-contract-title": "डेटा गुणवत्ता चाचण्या आणि पुष्टीकरणे",
|
||||
"database-service-name-message": "वंश तयार करण्यासाठी डेटाबेस सेवा नावे जोडा.",
|
||||
"dbt-catalog-file-extract-path": "dbt कॅटलॉग फाइल dbt मॉडेल्स त्यांच्या स्तंभ योजना सह काढण्यासाठी.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Kan het rapport voor inzicht data-assets niet verzenden.",
|
||||
"data-insight-report-send-success-message": "Rapport voor inzicht data-assets succesvol verzonden.",
|
||||
"data-insight-subtitle": "Bekijk in één overzicht de statushistorie van al uw data-assets.",
|
||||
"data-products-no-data-message": "Het lijkt erop dat je geen dataproducten hebt toegevoegd, maak er een!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Het lijkt erop dat je geen dataproducten hebt toegevoegd, maak er een!",
|
||||
"data-quality-test-contract-title": "Datakwaliteitstests en -bevestigingen",
|
||||
"database-service-name-message": "Voeg de namen van databaseservices toe om herkomst vast te leggen.",
|
||||
"dbt-catalog-file-extract-path": "Pad naar dbt-catalogusbestand om dbt-modellen met hun kolomschema's te extraheren.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "ارسال گزارش بینش داده شکست خورد.",
|
||||
"data-insight-report-send-success-message": "گزارش بینش داده با موفقیت ارسال شد.",
|
||||
"data-insight-subtitle": "نمایی یکپارچه از سلامت تمام داراییهای دادهای شما در طول زمان.",
|
||||
"data-products-no-data-message": "به نظر میرسد هیچ محصول دادهای اضافه نکردهاید، یکی بسازید!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "به نظر میرسد هیچ محصول دادهای اضافه نکردهاید، یکی بسازید!",
|
||||
"data-quality-test-contract-title": "تستهای کیفیت داده و تأییدها",
|
||||
"database-service-name-message": "نامهای سرویس پایگاه داده را اضافه کنید تا خطمش را ایجاد کنید.",
|
||||
"dbt-catalog-file-extract-path": "فایل کاتالوگ dbt برای استخراج مدلهای dbt با اسکیماهای ستون آنها.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Falha ao enviar relatório de insight de dados.",
|
||||
"data-insight-report-send-success-message": "Relatório de Insight de Dados enviado com sucesso.",
|
||||
"data-insight-subtitle": "Obtenha uma visão única da saúde de todos os seus ativos de dados ao longo do tempo.",
|
||||
"data-products-no-data-message": "Parece que você não adicionou nenhum produto de dados, crie um!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Parece que você não adicionou nenhum produto de dados, crie um!",
|
||||
"data-quality-test-contract-title": "Testes de qualidade de dados e afirmações",
|
||||
"database-service-name-message": "Adicione os nomes dos serviços de banco de dados para criar linhagem.",
|
||||
"dbt-catalog-file-extract-path": "Arquivo de catálogo dbt para extrair modelos dbt com seus esquemas de coluna.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Falha ao enviar relatório de insight de dados.",
|
||||
"data-insight-report-send-success-message": "Relatório de Insight de Dados enviado com sucesso.",
|
||||
"data-insight-subtitle": "Obtenha uma visão única da saúde de todos os seus ativos de dados ao longo do tempo.",
|
||||
"data-products-no-data-message": "Parece que não adicionou nenhum produto de dados, crie um!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Parece que não adicionou nenhum produto de dados, crie um!",
|
||||
"data-quality-test-contract-title": "Testes de qualidade de dados e afirmações",
|
||||
"database-service-name-message": "Adicione os nomes dos serviços de banco de dados para criar linhagem.",
|
||||
"dbt-catalog-file-extract-path": "Arquivo de catálogo dbt para extrair modelos dbt com seus esquemas de coluna.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Не удалось отправить отчет об анализе данных",
|
||||
"data-insight-report-send-success-message": "Отчет о анализе данных успешно отправлен.",
|
||||
"data-insight-subtitle": "Получите единое представление о состоянии всех ваших активов данных с течением времени.",
|
||||
"data-products-no-data-message": "Похоже, вы не добавили никаких продуктов данных, создайте один!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Похоже, вы не добавили никаких продуктов данных, создайте один!",
|
||||
"data-quality-test-contract-title": "Тесты качества данных и утверждения",
|
||||
"database-service-name-message": "Добавьте имена сервиса базы данных, чтобы создать lineage.",
|
||||
"dbt-catalog-file-extract-path": "dbt для извлечения моделей dbt со схемами их столбцов.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "ส่งรายงานข้อมูลเชิงลึกล้มเหลว",
|
||||
"data-insight-report-send-success-message": "ส่งรายงานข้อมูลเชิงลึกสำเร็จ",
|
||||
"data-insight-subtitle": "ดูภาพรวมของสุขภาพสินทรัพย์ข้อมูลทั้งหมดของคุณตลอดเวลา",
|
||||
"data-products-no-data-message": "ดูเหมือนว่าคุณยังไม่ได้เพิ่มผลิตภัณฑ์ข้อมูลใด ๆ สร้างหนึ่งรายการ!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "ดูเหมือนว่าคุณยังไม่ได้เพิ่มผลิตภัณฑ์ข้อมูลใด ๆ สร้างหนึ่งรายการ!",
|
||||
"data-quality-test-contract-title": "Data quality tests and assertions",
|
||||
"database-service-name-message": "เพิ่มชื่อบริการฐานข้อมูลเพื่อสร้างลำดับชั้น",
|
||||
"dbt-catalog-file-extract-path": "ไฟล์ Catalog ของ dbt สำหรับดึงโมเดล dbt พร้อมกับสคีมาคอลัมน์ของพวกเขา",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "Veri analizi raporu gönderilemedi.",
|
||||
"data-insight-report-send-success-message": "Veri Analizi Raporu başarıyla gönderildi.",
|
||||
"data-insight-subtitle": "Tüm veri varlıklarınızın zaman içindeki sağlığının tek bir görünümünü elde edin.",
|
||||
"data-products-no-data-message": "Henüz bir veri ürünü eklememiş gibi görünüyor, bir tane oluşturun!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "Henüz bir veri ürünü eklememiş gibi görünüyor, bir tane oluşturun!",
|
||||
"data-quality-test-contract-title": "Veri kalitesi testleri ve onaylar",
|
||||
"database-service-name-message": "Veri soyu oluşturmak için veritabanı servis adlarını ekleyin.",
|
||||
"dbt-catalog-file-extract-path": " dbt modellerini sütun şemalarıyla birlikte çıkarmak için dbt katalog dosyası.",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "数据洞察报告发送失败。",
|
||||
"data-insight-report-send-success-message": "数据洞察报告发送成功。",
|
||||
"data-insight-subtitle": "查看所有数据资产的健康状况",
|
||||
"data-products-no-data-message": "看起来您还没有添加任何数据产品,创建一个!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "看起来您还没有添加任何数据产品,创建一个!",
|
||||
"data-quality-test-contract-title": "数据质量测试和断言",
|
||||
"database-service-name-message": "添加数据库服务名称以创建血缘关系图",
|
||||
"dbt-catalog-file-extract-path": "dbt 目录文件, 用于提取带有 Column Schema (列模式) 的 dbt 模型",
|
||||
|
@ -1990,8 +1990,8 @@
|
||||
"data-insight-report-send-failed-message": "傳送資料洞察報告失敗。",
|
||||
"data-insight-report-send-success-message": "資料洞察報告傳送成功。",
|
||||
"data-insight-subtitle": "在一個單一窗格中檢視您所有資料資產隨時間變化的健康狀況。",
|
||||
"data-products-no-data-message": "看起來您還沒有新增任何資料產品,建立一個!",
|
||||
"data-product-description": "Manage and organize your data products for better data governance.",
|
||||
"data-products-no-data-message": "看起來您還沒有新增任何資料產品,建立一個!",
|
||||
"data-quality-test-contract-title": "資料品質測試與斷言",
|
||||
"database-service-name-message": "新增資料庫服務名稱以建立血緣。",
|
||||
"dbt-catalog-file-extract-path": "dbt 目錄檔案,用於擷取 dbt 模型及其欄位結構。",
|
||||
|
Loading…
x
Reference in New Issue
Block a user