Feat(UI): Added EntitySummaryPanel in Explore page (#8591)

* Added EntitySummaryPanel in Explore page

* Worked on comments to optimise code for EntitySummaryPanel

* fixed faling unit test for explore component
This commit is contained in:
Aniket Katkar 2022-11-09 11:11:01 +05:30 committed by GitHub
parent e1806b50a5
commit 7c395a82d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 619 additions and 150 deletions

View File

@ -0,0 +1,87 @@
/*
* Copyright 2022 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 { Divider, Space, Typography } from 'antd';
import { toLower } from 'lodash';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getTagValue } from '../../../utils/CommonUtils';
import SVGIcons from '../../../utils/SvgUtils';
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
import TagsViewer from '../../tags-viewer/tags-viewer';
import { BasicColumnInfo, ColumnSummaryProps } from './ColumnSummary.interface';
const { Text, Paragraph } = Typography;
export default function ColumnSummary({ columns }: ColumnSummaryProps) {
const { t } = useTranslation();
const formattedColumnsData: BasicColumnInfo[] = useMemo(() => {
if (columns) {
return columns.map((column) => ({
name: column.name,
type: column.dataType,
tags: column.tags,
description: column.description,
}));
} else return [];
}, [columns]);
return (
<Space direction="vertical">
{columns &&
formattedColumnsData.map((column) => (
<React.Fragment key={column.name}>
<Space direction="vertical" size={0}>
<Text className="column-name">{column.name}</Text>
<Space className="text-xs" size={4}>
<Space size={4}>
<Text className="text-gray">{`${t('label.type')}:`}</Text>
<Text className="text-semi-bold">{toLower(column.type)}</Text>
</Space>
{column.tags?.length !== 0 && (
<>
<Divider type="vertical" />
<Space size={4}>
<SVGIcons
alt="icon-tag"
icon="icon-tag-grey"
width="12"
/>
<TagsViewer
sizeCap={-1}
tags={(column.tags || []).map((tag) =>
getTagValue(tag)
)}
/>
</Space>
</>
)}
</Space>
<Paragraph className="text-gray">
{column.description ? (
<RichTextEditorPreviewer
markdown={column.description || ''}
/>
) : (
t('label.no-description')
)}
</Paragraph>
</Space>
<Divider />
</React.Fragment>
))}
</Space>
);
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2022 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 { Column, DataType } from '../../../generated/entity/data/table';
import { TagLabel } from './../../../generated/type/tagLabel';
export interface ColumnSummaryProps {
columns: Column[];
}
export interface BasicColumnInfo {
name: string;
type: DataType;
tags?: TagLabel[];
description?: string;
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2022 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 { CloseOutlined } from '@ant-design/icons';
import { Col, Divider, Row, Space, Typography } from 'antd';
import classNames from 'classnames';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { TableType } from '../../../generated/entity/data/table';
import ColumnSummary from '../ColumnSummary/ColumnSummary.component';
import {
BasicTableInfo,
EntitySummaryPanelProps,
} from './EntitySummaryPanel.interface';
import './EntitySummaryPanel.style.less';
export default function EntitySummaryPanel({
entityDetails,
handleClosePanel,
overallSummery,
showPanel,
}: EntitySummaryPanelProps) {
const { t } = useTranslation();
const { tableType, columns, tableQueries } = entityDetails;
const basicTableInfo: BasicTableInfo = {
Type: tableType || TableType.Regular,
Queries: tableQueries?.length ? `${tableQueries?.length}` : '-',
Columns: columns?.length ? `${columns?.length}` : '-',
};
return (
<div
className={classNames(
'summary-panel-container',
showPanel ? 'show-panel' : ''
)}>
<Space
className={classNames('basic-info-container m-md')}
direction="vertical">
<Typography.Title level={5}>{entityDetails.name}</Typography.Title>
<Space className={classNames('w-full')} direction="vertical">
{Object.keys(basicTableInfo).map((fieldName) => (
<Row gutter={16} key={fieldName}>
<Col className="text-gray" span={10}>
{fieldName}
</Col>
<Col span={12}>
{basicTableInfo[fieldName as keyof BasicTableInfo]}
</Col>
</Row>
))}
</Space>
</Space>
<Divider className="m-0" />
<Space className={classNames('m-md')} direction="vertical">
<Typography.Text className="section-header">
{t('label.profiler-amp-data-quality')}
</Typography.Text>
<Row gutter={[16, 16]}>
{overallSummery.map((field) => (
<Col key={field.title} span={10}>
<Space direction="vertical" size={6}>
<Typography.Text className="text-gray">
{field.title}
</Typography.Text>
<Typography.Text
className={classNames(
'tw-text-2xl tw-font-semibold',
field.className
)}>
{field.value}
</Typography.Text>
</Space>
</Col>
))}
</Row>
</Space>
<Divider className="m-0" />
<Space className={classNames('m-md')} direction="vertical">
<Typography.Text className="section-header">
{t('label.schema')}
</Typography.Text>
<ColumnSummary columns={columns} />
</Space>
<CloseOutlined className="close-icon" onClick={handleClosePanel} />
</div>
);
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2022 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 { Table, TableType } from '../../../generated/entity/data/table';
import { OverallTableSummeryType } from '../../TableProfiler/TableProfiler.interface';
export interface EntitySummaryPanelProps {
entityDetails: Table;
handleClosePanel: () => void;
overallSummery: OverallTableSummeryType[];
showPanel: boolean;
}
export interface BasicTableInfo {
Type: TableType | string;
Queries: string;
Columns: string;
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2022 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.
*/
@white: #ffffff;
@summary-panel-offset: 118px; // (topnav height) 64px + (margin) 16px + (tabs height) 38px
@label-color: #37352f99;
@succesColor: #28a745;
@failedColor: #cb2431;
@abortedColor: #efae2f;
.summary-panel-container {
width: 380px;
display: none;
background-color: @white;
height: calc(100vh - @summary-panel-offset);
}
.show-panel {
display: block;
position: fixed;
top: @summary-panel-offset;
right: 0px;
z-index: 1000;
box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.12);
font-size: 14px;
overflow-y: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
.basic-info-container {
width: calc(100% - 32px);
}
.text-gray {
color: @label-color;
}
.section-header {
font-size: 16px;
}
.success {
color: @succesColor;
}
.failed {
color: @failedColor;
}
.aborted {
color: @abortedColor;
}
.column-name {
font-weight: 500;
}
.ant-divider-horizontal {
margin: 8px 0px;
}
div.ant-typography {
margin-top: 16px;
margin-bottom: 0px;
}
.close-icon {
position: absolute;
top: 16px;
right: 16px;
}
}
.show-panel::-webkit-scrollbar {
display: none;
}

View File

@ -16,22 +16,42 @@ import {
faSortAmountUpAlt,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Card, Tabs } from 'antd';
import { Card, Space, Tabs } from 'antd';
import { AxiosError } from 'axios';
import unique from 'fork-ts-checker-webpack-plugin/lib/utils/array/unique';
import { isNil, isNumber, lowerCase, noop, omit, toUpper } from 'lodash';
import { EntityType } from 'Models';
import React, { Fragment, useEffect, useMemo, useRef } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
import { getListTestCase } from '../../axiosAPIs/testAPI';
import FacetFilter from '../../components/common/facetfilter/FacetFilter';
import SearchedData from '../../components/searched-data/SearchedData';
import { ENTITY_PATH } from '../../constants/constants';
import { API_RES_MAX_SIZE, ENTITY_PATH } from '../../constants/constants';
import { tabsInfo } from '../../constants/explore.constants';
import { INITIAL_TEST_RESULT_SUMMARY } from '../../constants/profiler.constant';
import { TabSpecificField } from '../../enums/entity.enum';
import { SearchIndex } from '../../enums/search.enum';
import { getCountBadge } from '../../utils/CommonUtils';
import { Table } from '../../generated/entity/data/table';
import { Include } from '../../generated/type/include';
import {
formatNumberWithComma,
formTwoDigitNmber,
getCountBadge,
} from '../../utils/CommonUtils';
import { updateTestResults } from '../../utils/DataQualityAndProfilerUtils';
import { generateEntityLink } from '../../utils/TableUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import AdvancedSearch from '../AdvancedSearch/AdvancedSearch.component';
import { FacetFilterProps } from '../common/facetfilter/facetFilter.interface';
import PageLayout, { leftPanelAntCardStyle } from '../containers/PageLayout';
import PageLayoutV1 from '../containers/PageLayoutV1';
import Loader from '../Loader/Loader';
import {
OverallTableSummeryType,
TableTestsType,
} from '../TableProfiler/TableProfiler.interface';
import EntitySummaryPanel from './EntitySummaryPanel/EntitySummaryPanel.component';
import {
ExploreProps,
ExploreSearchIndex,
@ -61,6 +81,17 @@ const Explore: React.FC<ExploreProps> = ({
}) => {
const isMounting = useRef(true);
const { tab } = useParams<{ tab: string }>();
const { t } = useTranslation();
const [showSummaryPanel, setShowSummaryPanel] = useState(false);
const [entityDetails, setEntityDetails] = useState<Table>();
const [tableTests, setTableTests] = useState<TableTestsType>({
tests: [],
results: INITIAL_TEST_RESULT_SUMMARY,
});
const handleClosePanel = () => {
setShowSummaryPanel(false);
};
// get entity active tab by URL params
const defaultActiveTab = useMemo(() => {
@ -96,6 +127,99 @@ const Explore: React.FC<ExploreProps> = ({
}
};
const overallSummery: OverallTableSummeryType[] = useMemo(() => {
return [
{
title: 'Row Count',
value: formatNumberWithComma(entityDetails?.profile?.rowCount ?? 0),
},
{
title: 'Column Count',
value: entityDetails?.profile?.columnCount ?? 0,
},
{
title: 'Table Sample %',
value: `${entityDetails?.profile?.profileSample ?? 100}%`,
},
{
title: 'Tests Passed',
value: formTwoDigitNmber(tableTests.results.success),
className: 'success',
},
{
title: 'Tests Aborted',
value: formTwoDigitNmber(tableTests.results.aborted),
className: 'aborted',
},
{
title: 'Tests Failed',
value: formTwoDigitNmber(tableTests.results.failed),
className: 'failed',
},
];
}, [entityDetails, tableTests]);
const fetchProfilerData = async (source: Table) => {
try {
const res = await getTableDetailsByFQN(
encodeURIComponent(source?.fullyQualifiedName || ''),
`${TabSpecificField.TABLE_PROFILE},${TabSpecificField.TABLE_QUERIES}`
);
const { profile, tableQueries } = res;
setEntityDetails((prev) => {
if (prev) {
return { ...prev, profile, tableQueries };
} else {
return {} as Table;
}
});
} catch {
showErrorToast(
t('message.entity-fetch-error', {
entity: `profile details for table ${source?.name || ''}`,
})
);
}
};
const fetchAllTests = async (source: Table) => {
try {
const { data } = await getListTestCase({
fields: 'testCaseResult,entityLink,testDefinition,testSuite',
entityLink: generateEntityLink(source?.fullyQualifiedName || ''),
includeAllTests: true,
limit: API_RES_MAX_SIZE,
include: Include.Deleted,
});
const tableTests: TableTestsType = {
tests: [],
results: { ...INITIAL_TEST_RESULT_SUMMARY },
};
data.forEach((test) => {
if (test.entityFQN === source?.fullyQualifiedName) {
tableTests.tests.push(test);
updateTestResults(
tableTests.results,
test.testCaseResult?.testCaseStatus || ''
);
return;
}
});
setTableTests(tableTests);
} catch (error) {
showErrorToast(error as AxiosError);
}
};
const handleSummaryPanelDisplay = (source: Table) => {
setShowSummaryPanel(true);
fetchAllTests(source);
fetchProfilerData(source);
setEntityDetails(source);
};
const handleFacetFilterClearFilter: FacetFilterProps['onClearFilter'] = (
key
) => onChangePostFilter(omit(postFilter, key));
@ -103,114 +227,134 @@ const Explore: React.FC<ExploreProps> = ({
// alwyas Keep this useEffect at the end...
useEffect(() => {
isMounting.current = false;
const escapeKeyHandler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
handleClosePanel();
}
};
document.addEventListener('keydown', escapeKeyHandler);
return () => {
document.removeEventListener('keydown', escapeKeyHandler);
};
}, []);
return (
<Fragment>
<PageLayout
leftPanel={
<div className="tw-h-full">
<Card
data-testid="data-summary-container"
style={{ ...leftPanelAntCardStyle, marginTop: '16px' }}>
<FacetFilter
aggregations={searchResults?.aggregations}
filters={postFilter}
showDeleted={showDeleted}
onChangeShowDeleted={onChangeShowDeleted}
onClearFilter={handleFacetFilterClearFilter}
onSelectHandler={handleFacetFilterChange}
/>
</Card>
</div>
}>
<Tabs
defaultActiveKey={defaultActiveTab}
size="small"
tabBarExtraContent={
<div className="tw-flex">
<SortingDropDown
fieldList={tabsInfo[searchIndex].sortingFields}
handleFieldDropDown={onChangeSortValue}
sortField={sortValue}
/>
<div className="tw-flex">
{sortOrder === 'asc' ? (
<button
className="tw-mt-2"
onClick={() => onChangeSortOder('desc')}>
<FontAwesomeIcon
className="tw-text-base tw-text-primary"
data-testid="last-updated"
icon={faSortAmountUpAlt}
/>
</button>
) : (
<button
className="tw-mt-2"
onClick={() => onChangeSortOder('asc')}>
<FontAwesomeIcon
className="tw-text-base tw-text-primary"
data-testid="last-updated"
icon={faSortAmountDownAlt}
/>
</button>
)}
</div>
</div>
}
onChange={(tab) => {
tab && onChangeSearchIndex(tab as ExploreSearchIndex);
}}>
{Object.entries(tabsInfo).map(([tabSearchIndex, tabDetail]) => (
<Tabs.TabPane
key={tabSearchIndex}
tab={
<div data-testid={`${lowerCase(tabDetail.label)}-tab`}>
{tabDetail.label}
<span className="p-l-xs ">
{!isNil(tabCounts)
? getCountBadge(
tabCounts[tabSearchIndex as ExploreSearchIndex],
'',
tabSearchIndex === searchIndex
)
: getCountBadge()}
</span>
</div>
}
<PageLayoutV1
leftPanel={
<div className="tw-h-full">
<Card data-testid="data-summary-container">
<FacetFilter
aggregations={searchResults?.aggregations}
filters={postFilter}
showDeleted={showDeleted}
onChangeShowDeleted={onChangeShowDeleted}
onClearFilter={handleFacetFilterClearFilter}
onSelectHandler={handleFacetFilterChange}
/>
))}
</Tabs>
<AdvancedSearch
jsonTree={advancedSearchJsonTree}
searchIndex={searchIndex}
onChangeJsonTree={(nTree) => onChangeAdvancedSearchJsonTree(nTree)}
onChangeQueryFilter={(nQueryFilter) =>
onChangeAdvancedSearchQueryFilter(nQueryFilter)
}
/>
{!loading ? (
<SearchedData
isFilterSelected
showResultCount
currentPage={page}
data={searchResults?.hits.hits ?? []}
paginate={(value) => {
if (isNumber(value)) {
onChangePage(value);
} else if (!isNaN(Number.parseInt(value))) {
onChangePage(Number.parseInt(value));
}
}}
totalValue={searchResults?.hits.total.value ?? 0}
</Card>
</div>
}>
<Tabs
defaultActiveKey={defaultActiveTab}
size="small"
tabBarExtraContent={
<div className="tw-flex">
<SortingDropDown
fieldList={tabsInfo[searchIndex].sortingFields}
handleFieldDropDown={onChangeSortValue}
sortField={sortValue}
/>
<div className="tw-flex">
{sortOrder === 'asc' ? (
<button
className="tw-mt-2"
onClick={() => onChangeSortOder('desc')}>
<FontAwesomeIcon
className="tw-text-base tw-text-primary"
data-testid="last-updated"
icon={faSortAmountUpAlt}
/>
</button>
) : (
<button
className="tw-mt-2"
onClick={() => onChangeSortOder('asc')}>
<FontAwesomeIcon
className="tw-text-base tw-text-primary"
data-testid="last-updated"
icon={faSortAmountDownAlt}
/>
</button>
)}
</div>
</div>
}
onChange={(tab) => {
tab && onChangeSearchIndex(tab as ExploreSearchIndex);
}}>
{Object.entries(tabsInfo).map(([tabSearchIndex, tabDetail]) => (
<Tabs.TabPane
key={tabSearchIndex}
tab={
<div data-testid={`${lowerCase(tabDetail.label)}-tab`}>
{tabDetail.label}
<span className="p-l-xs ">
{!isNil(tabCounts)
? getCountBadge(
tabCounts[tabSearchIndex as ExploreSearchIndex],
'',
tabSearchIndex === searchIndex
)
: getCountBadge()}
</span>
</div>
}
/>
) : (
<Loader />
)}
</PageLayout>
</Fragment>
))}
</Tabs>
<Space>
<div
style={{
marginRight: showSummaryPanel ? '380px' : '',
}}>
<AdvancedSearch
jsonTree={advancedSearchJsonTree}
searchIndex={searchIndex}
onChangeJsonTree={(nTree) => onChangeAdvancedSearchJsonTree(nTree)}
onChangeQueryFilter={(nQueryFilter) =>
onChangeAdvancedSearchQueryFilter(nQueryFilter)
}
/>
{!loading ? (
<SearchedData
isFilterSelected
showResultCount
currentPage={page}
data={searchResults?.hits.hits ?? []}
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
paginate={(value) => {
if (isNumber(value)) {
onChangePage(value);
} else if (!isNaN(Number.parseInt(value))) {
onChangePage(Number.parseInt(value));
}
}}
totalValue={searchResults?.hits.total.value ?? 0}
/>
) : (
<Loader />
)}
</div>
<EntitySummaryPanel
entityDetails={entityDetails || ({} as Table)}
handleClosePanel={handleClosePanel}
overallSummery={overallSummery}
showPanel={showSummaryPanel}
/>
</Space>
</PageLayoutV1>
);
};

View File

@ -59,27 +59,6 @@ jest.mock('../../components/searched-data/SearchedData', () => {
));
});
jest.mock(
'../containers/PageLayout',
() =>
({
children,
leftPanel,
rightPanel,
}: {
children: React.ReactNode;
rightPanel: React.ReactNode;
leftPanel: React.ReactNode;
}) =>
(
<div data-testid="PageLayout">
<div data-testid="left-panel-content">{leftPanel}</div>
<div data-testid="right-panel-content">{rightPanel}</div>
{children}
</div>
)
);
const mockFunction = jest.fn();
describe('Test Explore component', () => {
@ -110,12 +89,10 @@ describe('Test Explore component', () => {
wrapper: MemoryRouter,
}
);
const pageContainer = await findByTestId(container, 'PageLayout');
const searchData = await findByTestId(container, 'search-data');
const wrappedContent = await findByTestId(container, 'wrapped-content');
const tabs = await findAllByTestId(container, /tab/i);
expect(pageContainer).toBeInTheDocument();
expect(searchData).toBeInTheDocument();
expect(wrappedContent).toBeInTheDocument();
expect(tabs.length).toBe(5);

View File

@ -38,10 +38,13 @@ jest.mock('../table-data-card/TableDataCardBody', () => {
return jest.fn().mockReturnValue(<p>TableDataCardBody</p>);
});
const mockHandleSummaryPanelDisplay = jest.fn();
describe('Test TableDataCard Component', () => {
it('Component should render', () => {
const { getByTestId } = render(
<TableDataCardV2
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
id="1"
searchIndex={SearchIndex.TABLE}
source={{
@ -58,6 +61,7 @@ describe('Test TableDataCard Component', () => {
it('Component should render for deleted', () => {
const { getByTestId } = render(
<TableDataCardV2
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
id="1"
searchIndex={SearchIndex.TABLE}
source={{

View File

@ -24,6 +24,7 @@ import { EntityType, FqnPart } from '../../../enums/entity.enum';
import { SearchIndex } from '../../../enums/search.enum';
import { CurrentTourPageType } from '../../../enums/tour.enum';
import { OwnerType } from '../../../enums/user.enum';
import { Table } from '../../../generated/entity/data/table';
import { EntityReference } from '../../../generated/entity/type';
import {
getEntityId,
@ -48,6 +49,7 @@ export interface TableDataCardPropsV2 {
value: number;
}[];
searchIndex: SearchIndex | EntityType;
handleSummaryPanelDisplay: (source: Table) => void;
}
const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
@ -55,6 +57,7 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
source,
matches,
searchIndex,
handleSummaryPanelDisplay,
}) => {
const location = useLocation();
@ -141,7 +144,8 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
return (
<div
className="tw-bg-white tw-p-3 tw-border tw-border-main tw-rounded-md"
data-testid="table-data-card">
data-testid="table-data-card"
onClick={() => handleSummaryPanelDisplay(source as Table)}>
<div>
{'databaseSchema' in source && 'database' in source && (
<span

View File

@ -11,11 +11,11 @@
* limitations under the License.
*/
import { isNil, isString } from 'lodash';
import { isNil } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { FunctionComponent } from 'react';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { TagLabel } from '../../../generated/type/tagLabel';
import { getTagValue } from '../../../utils/CommonUtils';
import SVGIcons from '../../../utils/SvgUtils';
import TagsViewer from '../../tags-viewer/tags-viewer';
import EntitySummaryDetails from '../EntitySummaryDetails/EntitySummaryDetails';
@ -32,21 +32,6 @@ const TableDataCardBody: FunctionComponent<Props> = ({
extraInfo,
tags,
}: Props) => {
const getTagValue = (tag: string | TagLabel): string | TagLabel => {
if (isString(tag)) {
return tag.startsWith(`Tier${FQN_SEPARATOR_CHAR}Tier`)
? tag.split(FQN_SEPARATOR_CHAR)[1]
: tag;
} else {
return {
...tag,
tagFQN: tag.tagFQN.startsWith(`Tier${FQN_SEPARATOR_CHAR}Tier`)
? tag.tagFQN.split(FQN_SEPARATOR_CHAR)[1]
: tag.tagFQN,
};
}
};
return (
<div data-testid="table-body">
<div className="tw-mb-4 tw-flex tw-items-center tw-flex-wrap tw-text-xs">

View File

@ -12,6 +12,7 @@
*/
import { ReactNode } from 'react';
import { Table } from '../../generated/entity/data/table';
import { EntityReference } from '../../generated/entity/type';
import { TagLabel } from '../../generated/type/tagLabel';
import {
@ -67,4 +68,5 @@ export interface SearchedDataProps {
showOnboardingTemplate?: boolean;
showOnlyChildren?: boolean;
isFilterSelected: boolean;
handleSummaryPanelDisplay: (source: Table) => void;
}

View File

@ -64,6 +64,7 @@ const mockData: SearchedDataProps['data'] = [
];
const mockPaginate = jest.fn();
const mockHandleSummaryPanelDisplay = jest.fn();
jest.mock('../common/table-data-card/TableDataCard', () => {
return jest
@ -90,6 +91,7 @@ describe('Test SearchedData Component', () => {
isFilterSelected
currentPage={0}
data={mockData}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={10}
/>,
@ -109,6 +111,7 @@ describe('Test SearchedData Component', () => {
isFilterSelected
currentPage={0}
data={mockData}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={10}
/>,
@ -128,6 +131,7 @@ describe('Test SearchedData Component', () => {
isFilterSelected
currentPage={0}
data={mockData}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={10}>
<p>hello world</p>
@ -146,6 +150,7 @@ describe('Test SearchedData Component', () => {
isFilterSelected
currentPage={0}
data={mockData}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={11}>
<p>hello world</p>
@ -165,6 +170,7 @@ describe('Test SearchedData Component', () => {
showOnboardingTemplate
currentPage={0}
data={[]}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={0}
/>,
@ -182,6 +188,7 @@ describe('Test SearchedData Component', () => {
isFilterSelected
currentPage={0}
data={[]}
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
paginate={mockPaginate}
totalValue={0}
/>,

View File

@ -44,6 +44,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
totalValue,
isFilterSelected,
searchText,
handleSummaryPanelDisplay,
}) => {
const highlightSearchResult = () => {
return data.map(({ _source: table, highlight, _index }, index) => {
@ -89,6 +90,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
return (
<div className="tw-mb-3" key={index}>
<TableDataCardV2
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
id={`tabledatacard${index}`}
matches={matches}
searchIndex={_index}

View File

@ -207,7 +207,9 @@
"select-resource": "Select Resources",
"select-rule-effect": "Select Rule Effect",
"field-required": "{{field}} is required",
"field-required-plural": "{{field}} are required"
"field-required-plural": "{{field}} are required",
"profiler-amp-data-quality": "Profiler & Data Quality",
"schema": "Schema"
},
"message": {
"service-email-required": "Service account Email is required",

View File

@ -24,6 +24,7 @@ import {
isEqual,
isNil,
isNull,
isString,
isUndefined,
uniqueId,
} from 'lodash';
@ -80,6 +81,7 @@ import { Role } from '../generated/entity/teams/role';
import { Team } from '../generated/entity/teams/team';
import { EntityReference, User } from '../generated/entity/teams/user';
import { Paging } from '../generated/type/paging';
import { TagLabel } from '../generated/type/tagLabel';
import { ServicesType } from '../interface/service.interface';
import jsonData from '../jsons/en';
import { getEntityFeedLink, getTitleCase } from './EntityUtils';
@ -1010,3 +1012,18 @@ export const getTierFromEntityInfo = (entity: FormattedTableData) => {
getTierFromSearchTableTags((entity.tags || []).map((tag) => tag.tagFQN))
)?.split(FQN_SEPARATOR_CHAR)[1];
};
export const getTagValue = (tag: string | TagLabel): string | TagLabel => {
if (isString(tag)) {
return tag.startsWith(`Tier${FQN_SEPARATOR_CHAR}Tier`)
? tag.split(FQN_SEPARATOR_CHAR)[1]
: tag;
} else {
return {
...tag,
tagFQN: tag.tagFQN.startsWith(`Tier${FQN_SEPARATOR_CHAR}Tier`)
? tag.tagFQN.split(FQN_SEPARATOR_CHAR)[1]
: tag.tagFQN,
};
}
};