mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-08 00:18:36 +00:00
UI: Refector explore page (#925)
* UI: refactor explore page * rename index to exlore.component for mock file and test file
This commit is contained in:
parent
42c4840f06
commit
6f46ebbfef
@ -16,21 +16,16 @@
|
||||
*/
|
||||
|
||||
import { findAllByTestId, findByTestId, render } from '@testing-library/react';
|
||||
import { SearchResponse } from 'Models';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import ExplorePage from './index';
|
||||
import { mockResponse } from './index.mock';
|
||||
import { mockResponse } from './exlore.mock';
|
||||
import Explore from './Explore.component';
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn(),
|
||||
useParams: jest.fn().mockImplementation(() => ({ searchQuery: '' })),
|
||||
useLocation: jest.fn().mockImplementation(() => ({ search: '' })),
|
||||
}));
|
||||
|
||||
jest.mock('../../axiosAPIs/miscAPI', () => ({
|
||||
searchData: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ data: mockResponse })),
|
||||
}));
|
||||
jest.mock('../../utils/FilterUtils', () => ({
|
||||
getFilterString: jest.fn().mockImplementation(() => 'user.address'),
|
||||
}));
|
||||
@ -45,15 +40,47 @@ jest.mock('../../components/searched-data/SearchedData', () => {
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../hooks/useToastContext', () => {
|
||||
return () => jest.fn();
|
||||
});
|
||||
const handleSearchText = jest.fn();
|
||||
const updateTableCount = jest.fn();
|
||||
const updateTopicCount = jest.fn();
|
||||
const updateDashboardCount = jest.fn();
|
||||
const updatePipelineCount = jest.fn();
|
||||
const fetchData = jest.fn();
|
||||
|
||||
describe('Test Explore page', () => {
|
||||
const mockSearchResult = {
|
||||
resSearchResults: mockResponse as unknown as SearchResponse,
|
||||
resAggServiceType: mockResponse as unknown as SearchResponse,
|
||||
resAggTier: mockResponse as unknown as SearchResponse,
|
||||
resAggTag: mockResponse as unknown as SearchResponse,
|
||||
};
|
||||
|
||||
describe('Test Explore component', () => {
|
||||
it('Component should render', async () => {
|
||||
const { container } = render(<ExplorePage />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
const { container } = render(
|
||||
<Explore
|
||||
error=""
|
||||
fetchData={fetchData}
|
||||
handleSearchText={handleSearchText}
|
||||
isLoading={false}
|
||||
searchQuery=""
|
||||
searchResult={mockSearchResult}
|
||||
searchText=""
|
||||
tab=""
|
||||
tabCounts={{
|
||||
table: 15,
|
||||
topic: 2,
|
||||
dashboard: 8,
|
||||
pipeline: 5,
|
||||
}}
|
||||
updateDashboardCount={updateDashboardCount}
|
||||
updatePipelineCount={updatePipelineCount}
|
||||
updateTableCount={updateTableCount}
|
||||
updateTopicCount={updateTopicCount}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
const pageContainer = await findByTestId(container, 'fluid-container');
|
||||
const searchData = await findByTestId(container, 'search-data');
|
||||
const wrappedContent = await findByTestId(container, 'wrapped-content');
|
@ -15,20 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
AggregationType,
|
||||
Bucket,
|
||||
FilterObject,
|
||||
FormatedTableData,
|
||||
SearchResponse,
|
||||
} from 'Models';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import AppState from '../../AppState';
|
||||
import { searchData } from '../../axiosAPIs/miscAPI';
|
||||
import { Button } from '../../components/buttons/Button/Button';
|
||||
import ErrorPlaceHolderES from '../../components/common/error-with-placeholder/ErrorPlaceHolderES';
|
||||
import FacetFilter from '../../components/common/facetfilter/FacetFilter';
|
||||
@ -36,7 +33,6 @@ import PageContainer from '../../components/containers/PageContainer';
|
||||
import DropDownList from '../../components/dropdown/DropDownList';
|
||||
import SearchedData from '../../components/searched-data/SearchedData';
|
||||
import {
|
||||
ERROR500,
|
||||
getExplorePathWithSearch,
|
||||
PAGE_SIZE,
|
||||
tableSortingFields,
|
||||
@ -44,16 +40,14 @@ import {
|
||||
} from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { usePrevious } from '../../hooks/usePrevious';
|
||||
import useToastContext from '../../hooks/useToastContext';
|
||||
import { getAggregationList } from '../../utils/AggregationUtils';
|
||||
import { formatDataResponse } from '../../utils/APIUtils';
|
||||
import { getCountBadge } from '../../utils/CommonUtils';
|
||||
import { getFilterString } from '../../utils/FilterUtils';
|
||||
import { getTotalEntityCountByService } from '../../utils/ServiceUtils';
|
||||
import { dropdownIcon as DropDownIcon } from '../../utils/svgconstant';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { getAggrWithDefaultValue, tabsInfo } from './explore.constants';
|
||||
import { Params } from './explore.interface';
|
||||
import { ExploreProps } from './explore.interface';
|
||||
|
||||
const getQueryParam = (urlSearchQuery = ''): FilterObject => {
|
||||
const arrSearchQuery = urlSearchQuery
|
||||
@ -125,36 +119,43 @@ const getCurrentIndex = (tab: string) => {
|
||||
return currentIndex;
|
||||
};
|
||||
|
||||
const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
const Explore: React.FC<ExploreProps> = ({
|
||||
tabCounts,
|
||||
searchText,
|
||||
tab,
|
||||
searchQuery,
|
||||
searchResult,
|
||||
error,
|
||||
isLoading,
|
||||
handleSearchText,
|
||||
fetchData,
|
||||
updateTableCount,
|
||||
updateTopicCount,
|
||||
updateDashboardCount,
|
||||
updatePipelineCount,
|
||||
}: ExploreProps) => {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const filterObject: FilterObject = {
|
||||
...{ tags: [], service: [], tier: [] },
|
||||
...getQueryParam(location.search),
|
||||
};
|
||||
const showToast = useToastContext();
|
||||
const { searchQuery, tab } = useParams<Params>();
|
||||
const [searchText, setSearchText] = useState<string>(searchQuery || '');
|
||||
const [data, setData] = useState<Array<FormatedTableData>>([]);
|
||||
const [filters, setFilters] = useState<FilterObject>(filterObject);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
const [totalNumberOfValue, setTotalNumberOfValues] = useState<number>(0);
|
||||
const [aggregations, setAggregations] = useState<Array<AggregationType>>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [searchTag, setSearchTag] = useState<string>(location.search);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const [fieldListVisible, setFieldListVisible] = useState<boolean>(false);
|
||||
const [sortField, setSortField] = useState<string>('');
|
||||
const [sortOrder, setSortOrder] = useState<string>('desc');
|
||||
const [searchIndex, setSearchIndex] = useState<string>(getCurrentIndex(tab));
|
||||
const [currentTab, setCurrentTab] = useState<number>(getCurrentTab(tab));
|
||||
const [tableCount, setTableCount] = useState<number>(0);
|
||||
const [topicCount, setTopicCount] = useState<number>(0);
|
||||
const [dashboardCount, setDashboardCount] = useState<number>(0);
|
||||
const [pipelineCount, setPipelineCount] = useState<number>(0);
|
||||
const [fieldList, setFieldList] =
|
||||
useState<Array<{ name: string; value: string }>>(tableSortingFields);
|
||||
const isMounting = useRef(true);
|
||||
const forceSetAgg = useRef(false);
|
||||
const previsouIndex = usePrevious(searchIndex);
|
||||
|
||||
const handleSelectedFilter = (
|
||||
@ -238,19 +239,19 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
const setCount = (count = 0) => {
|
||||
switch (searchIndex) {
|
||||
case SearchIndex.TABLE:
|
||||
setTableCount(count);
|
||||
updateTableCount(count);
|
||||
|
||||
break;
|
||||
case SearchIndex.DASHBOARD:
|
||||
setDashboardCount(count);
|
||||
updateDashboardCount(count);
|
||||
|
||||
break;
|
||||
case SearchIndex.TOPIC:
|
||||
setTopicCount(count);
|
||||
updateTopicCount(count);
|
||||
|
||||
break;
|
||||
case SearchIndex.PIPELINE:
|
||||
setPipelineCount(count);
|
||||
updatePipelineCount(count);
|
||||
|
||||
break;
|
||||
default:
|
||||
@ -258,176 +259,47 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCounts = () => {
|
||||
const emptyValue = '';
|
||||
const tableCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.TABLE
|
||||
);
|
||||
const topicCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.TOPIC
|
||||
);
|
||||
const dashboardCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.DASHBOARD
|
||||
);
|
||||
const pipelineCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.PIPELINE
|
||||
);
|
||||
Promise.allSettled([
|
||||
tableCount,
|
||||
topicCount,
|
||||
dashboardCount,
|
||||
pipelineCount,
|
||||
]).then(
|
||||
([
|
||||
table,
|
||||
topic,
|
||||
dashboard,
|
||||
pipeline,
|
||||
]: PromiseSettledResult<SearchResponse>[]) => {
|
||||
setTableCount(
|
||||
table.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
table.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setTopicCount(
|
||||
topic.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
topic.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setDashboardCount(
|
||||
dashboard.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
dashboard.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setPipelineCount(
|
||||
pipeline.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
pipeline.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
const fetchTableData = () => {
|
||||
const fetchParams = [
|
||||
{
|
||||
queryString: searchText,
|
||||
from: currentPage,
|
||||
size: PAGE_SIZE,
|
||||
filters: getFilterString(filters),
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
searchIndex: searchIndex,
|
||||
},
|
||||
{
|
||||
queryString: searchText,
|
||||
from: currentPage,
|
||||
size: 0,
|
||||
filters: getFilterString(filters, ['service']),
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
searchIndex: searchIndex,
|
||||
},
|
||||
{
|
||||
queryString: searchText,
|
||||
from: currentPage,
|
||||
size: 0,
|
||||
filters: getFilterString(filters, ['tier']),
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
searchIndex: searchIndex,
|
||||
},
|
||||
{
|
||||
queryString: searchText,
|
||||
from: currentPage,
|
||||
size: 0,
|
||||
filters: getFilterString(filters, ['tags']),
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
searchIndex: searchIndex,
|
||||
},
|
||||
];
|
||||
|
||||
const fetchTableData = (forceSetAgg: boolean) => {
|
||||
setIsLoading(true);
|
||||
|
||||
const searchResults = searchData(
|
||||
searchText,
|
||||
currentPage,
|
||||
PAGE_SIZE,
|
||||
getFilterString(filters),
|
||||
sortField,
|
||||
sortOrder,
|
||||
searchIndex
|
||||
);
|
||||
const serviceTypeAgg = searchData(
|
||||
searchText,
|
||||
currentPage,
|
||||
0,
|
||||
getFilterString(filters, ['service']),
|
||||
sortField,
|
||||
sortOrder,
|
||||
searchIndex
|
||||
);
|
||||
const tierAgg = searchData(
|
||||
searchText,
|
||||
currentPage,
|
||||
0,
|
||||
getFilterString(filters, ['tier']),
|
||||
sortField,
|
||||
sortOrder,
|
||||
searchIndex
|
||||
);
|
||||
const tagAgg = searchData(
|
||||
searchText,
|
||||
currentPage,
|
||||
0,
|
||||
getFilterString(filters, ['tags']),
|
||||
sortField,
|
||||
sortOrder,
|
||||
searchIndex
|
||||
);
|
||||
|
||||
Promise.all([searchResults, serviceTypeAgg, tierAgg, tagAgg])
|
||||
.then(
|
||||
([
|
||||
resSearchResults,
|
||||
resAggServiceType,
|
||||
resAggTier,
|
||||
resAggTag,
|
||||
]: Array<SearchResponse>) => {
|
||||
updateSearchResults(resSearchResults);
|
||||
setCount(resSearchResults.data.hits.total.value);
|
||||
if (forceSetAgg) {
|
||||
setAggregations(
|
||||
resSearchResults.data.hits.hits.length > 0
|
||||
? getAggregationList(resSearchResults.data.aggregations)
|
||||
: []
|
||||
);
|
||||
} else {
|
||||
const aggServiceType = getAggregationList(
|
||||
resAggServiceType.data.aggregations,
|
||||
'service'
|
||||
);
|
||||
const aggTier = getAggregationList(
|
||||
resAggTier.data.aggregations,
|
||||
'tier'
|
||||
);
|
||||
const aggTag = getAggregationList(
|
||||
resAggTag.data.aggregations,
|
||||
'tags'
|
||||
);
|
||||
|
||||
updateAggregationCount([...aggServiceType, ...aggTier, ...aggTag]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
)
|
||||
.catch((err: AxiosError) => {
|
||||
setError(err.response?.data?.responseMessage);
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: err.response?.data?.responseMessage ?? ERROR500,
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
});
|
||||
fetchData(fetchParams);
|
||||
};
|
||||
|
||||
const getFacetedFilter = () => {
|
||||
@ -515,13 +387,13 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
const getTabCount = (index: string) => {
|
||||
switch (index) {
|
||||
case SearchIndex.TABLE:
|
||||
return getCountBadge(tableCount);
|
||||
return getCountBadge(tabCounts.table);
|
||||
case SearchIndex.TOPIC:
|
||||
return getCountBadge(topicCount);
|
||||
return getCountBadge(tabCounts.topic);
|
||||
case SearchIndex.DASHBOARD:
|
||||
return getCountBadge(dashboardCount);
|
||||
return getCountBadge(tabCounts.dashboard);
|
||||
case SearchIndex.PIPELINE:
|
||||
return getCountBadge(pipelineCount);
|
||||
return getCountBadge(tabCounts.pipeline);
|
||||
default:
|
||||
return getCountBadge();
|
||||
}
|
||||
@ -543,23 +415,23 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
<div className="tw-mb-3 tw--mt-4">
|
||||
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4 tw-justify-between">
|
||||
<div>
|
||||
{tabsInfo.map((tab, index) => (
|
||||
{tabsInfo.map((tabDetail, index) => (
|
||||
<button
|
||||
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(
|
||||
tab.tab
|
||||
tabDetail.tab
|
||||
)}`}
|
||||
data-testid="tab"
|
||||
key={index}
|
||||
onClick={() => {
|
||||
onTabChange(tab.tab);
|
||||
onTabChange(tabDetail.tab);
|
||||
}}>
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="tw-h-4 tw-w-4 tw-mr-2"
|
||||
icon={tab.icon}
|
||||
icon={tabDetail.icon}
|
||||
/>
|
||||
{tab.label}
|
||||
{getTabCount(tab.index)}
|
||||
{tabDetail.label}
|
||||
{getTabCount(tabDetail.index)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@ -570,7 +442,7 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSearchText(searchQuery || '');
|
||||
handleSearchText(searchQuery || '');
|
||||
setCurrentPage(1);
|
||||
}, [searchQuery]);
|
||||
|
||||
@ -579,7 +451,6 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
setFieldList(tabsInfo[getCurrentTab(tab) - 1].sortingFields);
|
||||
setSortField(tabsInfo[getCurrentTab(tab) - 1].sortField);
|
||||
setSortOrder('desc');
|
||||
setError('');
|
||||
setCurrentTab(getCurrentTab(tab));
|
||||
setSearchIndex(getCurrentIndex(tab));
|
||||
setCurrentPage(1);
|
||||
@ -592,18 +463,47 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
}, [searchText, filters]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTableData(true);
|
||||
forceSetAgg.current = true;
|
||||
fetchTableData();
|
||||
}, [searchText, searchIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounting.current && previsouIndex === getCurrentIndex(tab)) {
|
||||
fetchTableData(false);
|
||||
if (searchResult) {
|
||||
updateSearchResults(searchResult.resSearchResults);
|
||||
setCount(searchResult.resSearchResults.data.hits.total.value);
|
||||
if (forceSetAgg.current) {
|
||||
setAggregations(
|
||||
searchResult.resSearchResults.data.hits.hits.length > 0
|
||||
? getAggregationList(
|
||||
searchResult.resSearchResults.data.aggregations
|
||||
)
|
||||
: []
|
||||
);
|
||||
} else {
|
||||
const aggServiceType = getAggregationList(
|
||||
searchResult.resAggServiceType.data.aggregations,
|
||||
'service'
|
||||
);
|
||||
const aggTier = getAggregationList(
|
||||
searchResult.resAggTier.data.aggregations,
|
||||
'tier'
|
||||
);
|
||||
const aggTag = getAggregationList(
|
||||
searchResult.resAggTag.data.aggregations,
|
||||
'tags'
|
||||
);
|
||||
|
||||
updateAggregationCount([...aggServiceType, ...aggTier, ...aggTag]);
|
||||
}
|
||||
}
|
||||
}, [currentPage, filters, sortField, sortOrder]);
|
||||
}, [searchResult]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCounts();
|
||||
}, [searchText]);
|
||||
if (!isMounting.current && previsouIndex === getCurrentIndex(tab)) {
|
||||
forceSetAgg.current = false;
|
||||
fetchTableData();
|
||||
}
|
||||
}, [currentPage, filters, sortField, sortOrder]);
|
||||
|
||||
// alwyas Keep this useEffect at the end...
|
||||
useEffect(() => {
|
||||
@ -647,4 +547,4 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ExplorePage;
|
||||
export default Explore;
|
@ -14,6 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
const todayData = [
|
||||
{
|
||||
@ -209,17 +210,75 @@ export const entitiesData = [
|
||||
|
||||
export const tiers = ['Tier1', 'Tier2', 'Tier3', 'Tier4', 'Tier5'];
|
||||
export const mockResponse = {
|
||||
hits: {
|
||||
data: {
|
||||
hits: {
|
||||
tableId: '09ac866c-a18d-4470-abc8-52deed3d90d6',
|
||||
database: 'dwh',
|
||||
tableName: 'fact_sale',
|
||||
serviceName: 'MYSQL',
|
||||
description: 'this is the table to hold data on fact_sale',
|
||||
tableType: 'null',
|
||||
total: {
|
||||
value: 15,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: 5,
|
||||
hits: [
|
||||
{
|
||||
_index: 'table_search_index',
|
||||
_type: '_doc',
|
||||
_id: 'b5860f51-a197-48c8-9506-ee67da190d83',
|
||||
_score: 5,
|
||||
_source: {
|
||||
table_id: 'b5860f51-a197-48c8-9506-ee67da190d83',
|
||||
database: 'bigquery.shopify',
|
||||
service: 'bigquery',
|
||||
service_type: 'BigQuery',
|
||||
table_name: 'dim_address',
|
||||
suggest: [
|
||||
{
|
||||
input: ['bigquery.shopify.dim_address'],
|
||||
weight: 5,
|
||||
},
|
||||
{
|
||||
input: ['dim_address'],
|
||||
weight: 10,
|
||||
},
|
||||
],
|
||||
description:
|
||||
'This dimension table contains the billing and shipping addresses of customers. You can join this table.',
|
||||
table_type: 'Regular',
|
||||
last_updated_timestamp: 1634886627,
|
||||
column_names: ['address_id', 'shop_id'],
|
||||
column_descriptions: [
|
||||
'Unique identifier for the address.',
|
||||
'The ID of the store. This column is a foreign key reference to the shop_id column in the dim_shop table.',
|
||||
],
|
||||
tags: [],
|
||||
fqdn: 'bigquery.shopify.dim_address',
|
||||
tier: null,
|
||||
schema_description: null,
|
||||
owner: '',
|
||||
followers: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
total: {
|
||||
value: 128,
|
||||
aggregations: {
|
||||
'sterms#Tier': {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [],
|
||||
},
|
||||
'sterms#Service': {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'BigQuery',
|
||||
doc_count: 15,
|
||||
},
|
||||
],
|
||||
},
|
||||
'sterms#Tags': {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 { SearchResponse } from 'Models';
|
||||
|
||||
export type Params = {
|
||||
searchQuery: string;
|
||||
tab: string;
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
collection: {
|
||||
name: string;
|
||||
documentation: string;
|
||||
href: string;
|
||||
};
|
||||
};
|
||||
export type Team = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export type ExploreSearchData = {
|
||||
resSearchResults: SearchResponse;
|
||||
resAggServiceType: SearchResponse;
|
||||
resAggTier: SearchResponse;
|
||||
resAggTag: SearchResponse;
|
||||
};
|
||||
|
||||
export interface FetchData {
|
||||
queryString: string;
|
||||
from: number;
|
||||
size: number;
|
||||
filters: string;
|
||||
sortField: string;
|
||||
sortOrder: string;
|
||||
searchIndex: string;
|
||||
}
|
||||
|
||||
export interface ExploreProps {
|
||||
tabCounts: {
|
||||
table: number;
|
||||
topic: number;
|
||||
dashboard: number;
|
||||
pipeline: number;
|
||||
};
|
||||
searchText: string;
|
||||
tab: string;
|
||||
error: string;
|
||||
isLoading: boolean;
|
||||
searchQuery: string;
|
||||
handleSearchText: (text: string) => void;
|
||||
updateTableCount: (count: number) => void;
|
||||
updateTopicCount: (count: number) => void;
|
||||
updateDashboardCount: (count: number) => void;
|
||||
updatePipelineCount: (count: number) => void;
|
||||
fetchData: (value: FetchData[]) => void;
|
||||
searchResult: ExploreSearchData | undefined;
|
||||
}
|
@ -15,22 +15,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type Params = {
|
||||
searchQuery: string;
|
||||
tab: string;
|
||||
};
|
||||
import { findByTestId, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import ExplorePage from './ExplorePage.component';
|
||||
|
||||
export type Service = {
|
||||
collection: {
|
||||
name: string;
|
||||
documentation: string;
|
||||
href: string;
|
||||
};
|
||||
};
|
||||
export type Team = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
href: string;
|
||||
};
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({ searchQuery: '' })),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/Explore/Explore.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>Explore Component</p>);
|
||||
});
|
||||
|
||||
describe('Test Explore page', () => {
|
||||
it('Page Should render', async () => {
|
||||
const { container } = render(<ExplorePage />);
|
||||
|
||||
const explorePage = await findByTestId(container, 'explore-page');
|
||||
|
||||
expect(explorePage).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 { AxiosError } from 'axios';
|
||||
import { Bucket, SearchResponse } from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { searchData } from '../../axiosAPIs/miscAPI';
|
||||
import Explore from '../../components/Explore/Explore.component';
|
||||
import {
|
||||
ExploreSearchData,
|
||||
FetchData,
|
||||
Params,
|
||||
} from '../../components/Explore/explore.interface';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import { ERROR500 } from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import useToastContext from '../../hooks/useToastContext';
|
||||
import { getTotalEntityCountByService } from '../../utils/ServiceUtils';
|
||||
|
||||
const ExplorePage: FunctionComponent = () => {
|
||||
const showToast = useToastContext();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
const { searchQuery, tab } = useParams<Params>();
|
||||
const [searchText, setSearchText] = useState<string>(searchQuery || '');
|
||||
const [tableCount, setTableCount] = useState<number>(0);
|
||||
const [topicCount, setTopicCount] = useState<number>(0);
|
||||
const [dashboardCount, setDashboardCount] = useState<number>(0);
|
||||
const [pipelineCount, setPipelineCount] = useState<number>(0);
|
||||
const [searchResult, setSearchResult] = useState<ExploreSearchData>();
|
||||
|
||||
const handleSearchText = (text: string) => {
|
||||
setSearchText(text);
|
||||
};
|
||||
|
||||
const handleTableCount = (count: number) => {
|
||||
setTableCount(count);
|
||||
};
|
||||
|
||||
const handleTopicCount = (count: number) => {
|
||||
setTopicCount(count);
|
||||
};
|
||||
|
||||
const handleDashboardCount = (count: number) => {
|
||||
setDashboardCount(count);
|
||||
};
|
||||
|
||||
const handlePipelineCount = (count: number) => {
|
||||
setPipelineCount(count);
|
||||
};
|
||||
|
||||
const fetchCounts = () => {
|
||||
const emptyValue = '';
|
||||
const tableCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.TABLE
|
||||
);
|
||||
const topicCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.TOPIC
|
||||
);
|
||||
const dashboardCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.DASHBOARD
|
||||
);
|
||||
const pipelineCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.PIPELINE
|
||||
);
|
||||
|
||||
Promise.allSettled([
|
||||
tableCount,
|
||||
topicCount,
|
||||
dashboardCount,
|
||||
pipelineCount,
|
||||
]).then(
|
||||
([
|
||||
table,
|
||||
topic,
|
||||
dashboard,
|
||||
pipeline,
|
||||
]: PromiseSettledResult<SearchResponse>[]) => {
|
||||
setTableCount(
|
||||
table.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
table.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setTopicCount(
|
||||
topic.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
topic.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setDashboardCount(
|
||||
dashboard.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
dashboard.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
setPipelineCount(
|
||||
pipeline.status === 'fulfilled'
|
||||
? getTotalEntityCountByService(
|
||||
pipeline.value.data.aggregations?.['sterms#Service']
|
||||
?.buckets as Bucket[]
|
||||
)
|
||||
: 0
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const fetchData = (value: FetchData[]) => {
|
||||
setIsLoading(true);
|
||||
const promiseValue = value.map((d) => {
|
||||
return searchData(
|
||||
d.queryString,
|
||||
d.from,
|
||||
d.size,
|
||||
d.filters,
|
||||
d.sortField,
|
||||
d.sortOrder,
|
||||
d.searchIndex
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(promiseValue)
|
||||
.then(
|
||||
([
|
||||
resSearchResults,
|
||||
resAggServiceType,
|
||||
resAggTier,
|
||||
resAggTag,
|
||||
]: Array<SearchResponse>) => {
|
||||
setSearchResult({
|
||||
resSearchResults,
|
||||
resAggServiceType,
|
||||
resAggTier,
|
||||
resAggTag,
|
||||
});
|
||||
}
|
||||
)
|
||||
.catch((err: AxiosError) => {
|
||||
setError(err.response?.data?.responseMessage);
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: err.response?.data?.responseMessage ?? ERROR500,
|
||||
});
|
||||
});
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCounts();
|
||||
}, [searchText]);
|
||||
|
||||
return (
|
||||
<div data-testid="explore-page">
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<Explore
|
||||
error={error}
|
||||
fetchData={fetchData}
|
||||
handleSearchText={handleSearchText}
|
||||
isLoading={isLoading}
|
||||
searchQuery={searchQuery}
|
||||
searchResult={searchResult}
|
||||
searchText={searchText}
|
||||
tab={tab}
|
||||
tabCounts={{
|
||||
table: tableCount,
|
||||
topic: topicCount,
|
||||
dashboard: dashboardCount,
|
||||
pipeline: pipelineCount,
|
||||
}}
|
||||
updateDashboardCount={handleDashboardCount}
|
||||
updatePipelineCount={handlePipelineCount}
|
||||
updateTableCount={handleTableCount}
|
||||
updateTopicCount={handleTopicCount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExplorePage;
|
@ -24,7 +24,7 @@ import { ROUTES } from '../constants/constants';
|
||||
import MyDashBoardPage from '../pages/dashboard-details';
|
||||
import DatabaseDetails from '../pages/database-details/index';
|
||||
import DatasetDetailsPage from '../pages/DatasetDetailsPage/DatasetDetailsPage.component';
|
||||
import ExplorePage from '../pages/explore';
|
||||
import ExplorePage from '../pages/explore/ExplorePage.component';
|
||||
import MyDataPage from '../pages/my-data';
|
||||
import MyPipelinePage from '../pages/Pipeline-details';
|
||||
import ReportsPage from '../pages/reports';
|
||||
|
Loading…
x
Reference in New Issue
Block a user