mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-27 16:55:06 +00:00
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:
parent
e1806b50a5
commit
7c395a82d4
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -16,22 +16,42 @@ import {
|
|||||||
faSortAmountUpAlt,
|
faSortAmountUpAlt,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
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 unique from 'fork-ts-checker-webpack-plugin/lib/utils/array/unique';
|
||||||
import { isNil, isNumber, lowerCase, noop, omit, toUpper } from 'lodash';
|
import { isNil, isNumber, lowerCase, noop, omit, toUpper } from 'lodash';
|
||||||
import { EntityType } from 'Models';
|
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 { useParams } from 'react-router-dom';
|
||||||
|
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
|
||||||
|
import { getListTestCase } from '../../axiosAPIs/testAPI';
|
||||||
import FacetFilter from '../../components/common/facetfilter/FacetFilter';
|
import FacetFilter from '../../components/common/facetfilter/FacetFilter';
|
||||||
import SearchedData from '../../components/searched-data/SearchedData';
|
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 { 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 { 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 AdvancedSearch from '../AdvancedSearch/AdvancedSearch.component';
|
||||||
import { FacetFilterProps } from '../common/facetfilter/facetFilter.interface';
|
import { FacetFilterProps } from '../common/facetfilter/facetFilter.interface';
|
||||||
import PageLayout, { leftPanelAntCardStyle } from '../containers/PageLayout';
|
import PageLayoutV1 from '../containers/PageLayoutV1';
|
||||||
import Loader from '../Loader/Loader';
|
import Loader from '../Loader/Loader';
|
||||||
|
import {
|
||||||
|
OverallTableSummeryType,
|
||||||
|
TableTestsType,
|
||||||
|
} from '../TableProfiler/TableProfiler.interface';
|
||||||
|
import EntitySummaryPanel from './EntitySummaryPanel/EntitySummaryPanel.component';
|
||||||
import {
|
import {
|
||||||
ExploreProps,
|
ExploreProps,
|
||||||
ExploreSearchIndex,
|
ExploreSearchIndex,
|
||||||
@ -61,6 +81,17 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const isMounting = useRef(true);
|
const isMounting = useRef(true);
|
||||||
const { tab } = useParams<{ tab: string }>();
|
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
|
// get entity active tab by URL params
|
||||||
const defaultActiveTab = useMemo(() => {
|
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'] = (
|
const handleFacetFilterClearFilter: FacetFilterProps['onClearFilter'] = (
|
||||||
key
|
key
|
||||||
) => onChangePostFilter(omit(postFilter, key));
|
) => onChangePostFilter(omit(postFilter, key));
|
||||||
@ -103,16 +227,23 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
// alwyas Keep this useEffect at the end...
|
// alwyas Keep this useEffect at the end...
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isMounting.current = false;
|
isMounting.current = false;
|
||||||
|
const escapeKeyHandler = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
handleClosePanel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', escapeKeyHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', escapeKeyHandler);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<PageLayoutV1
|
||||||
<PageLayout
|
|
||||||
leftPanel={
|
leftPanel={
|
||||||
<div className="tw-h-full">
|
<div className="tw-h-full">
|
||||||
<Card
|
<Card data-testid="data-summary-container">
|
||||||
data-testid="data-summary-container"
|
|
||||||
style={{ ...leftPanelAntCardStyle, marginTop: '16px' }}>
|
|
||||||
<FacetFilter
|
<FacetFilter
|
||||||
aggregations={searchResults?.aggregations}
|
aggregations={searchResults?.aggregations}
|
||||||
filters={postFilter}
|
filters={postFilter}
|
||||||
@ -183,6 +314,11 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
<Space>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginRight: showSummaryPanel ? '380px' : '',
|
||||||
|
}}>
|
||||||
<AdvancedSearch
|
<AdvancedSearch
|
||||||
jsonTree={advancedSearchJsonTree}
|
jsonTree={advancedSearchJsonTree}
|
||||||
searchIndex={searchIndex}
|
searchIndex={searchIndex}
|
||||||
@ -197,6 +333,7 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
showResultCount
|
showResultCount
|
||||||
currentPage={page}
|
currentPage={page}
|
||||||
data={searchResults?.hits.hits ?? []}
|
data={searchResults?.hits.hits ?? []}
|
||||||
|
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
|
||||||
paginate={(value) => {
|
paginate={(value) => {
|
||||||
if (isNumber(value)) {
|
if (isNumber(value)) {
|
||||||
onChangePage(value);
|
onChangePage(value);
|
||||||
@ -209,8 +346,15 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<Loader />
|
<Loader />
|
||||||
)}
|
)}
|
||||||
</PageLayout>
|
</div>
|
||||||
</Fragment>
|
<EntitySummaryPanel
|
||||||
|
entityDetails={entityDetails || ({} as Table)}
|
||||||
|
handleClosePanel={handleClosePanel}
|
||||||
|
overallSummery={overallSummery}
|
||||||
|
showPanel={showSummaryPanel}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</PageLayoutV1>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
const mockFunction = jest.fn();
|
||||||
|
|
||||||
describe('Test Explore component', () => {
|
describe('Test Explore component', () => {
|
||||||
@ -110,12 +89,10 @@ describe('Test Explore component', () => {
|
|||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const pageContainer = await findByTestId(container, 'PageLayout');
|
|
||||||
const searchData = await findByTestId(container, 'search-data');
|
const searchData = await findByTestId(container, 'search-data');
|
||||||
const wrappedContent = await findByTestId(container, 'wrapped-content');
|
const wrappedContent = await findByTestId(container, 'wrapped-content');
|
||||||
const tabs = await findAllByTestId(container, /tab/i);
|
const tabs = await findAllByTestId(container, /tab/i);
|
||||||
|
|
||||||
expect(pageContainer).toBeInTheDocument();
|
|
||||||
expect(searchData).toBeInTheDocument();
|
expect(searchData).toBeInTheDocument();
|
||||||
expect(wrappedContent).toBeInTheDocument();
|
expect(wrappedContent).toBeInTheDocument();
|
||||||
expect(tabs.length).toBe(5);
|
expect(tabs.length).toBe(5);
|
||||||
|
|||||||
@ -38,10 +38,13 @@ jest.mock('../table-data-card/TableDataCardBody', () => {
|
|||||||
return jest.fn().mockReturnValue(<p>TableDataCardBody</p>);
|
return jest.fn().mockReturnValue(<p>TableDataCardBody</p>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockHandleSummaryPanelDisplay = jest.fn();
|
||||||
|
|
||||||
describe('Test TableDataCard Component', () => {
|
describe('Test TableDataCard Component', () => {
|
||||||
it('Component should render', () => {
|
it('Component should render', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<TableDataCardV2
|
<TableDataCardV2
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
id="1"
|
id="1"
|
||||||
searchIndex={SearchIndex.TABLE}
|
searchIndex={SearchIndex.TABLE}
|
||||||
source={{
|
source={{
|
||||||
@ -58,6 +61,7 @@ describe('Test TableDataCard Component', () => {
|
|||||||
it('Component should render for deleted', () => {
|
it('Component should render for deleted', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<TableDataCardV2
|
<TableDataCardV2
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
id="1"
|
id="1"
|
||||||
searchIndex={SearchIndex.TABLE}
|
searchIndex={SearchIndex.TABLE}
|
||||||
source={{
|
source={{
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { EntityType, FqnPart } from '../../../enums/entity.enum';
|
|||||||
import { SearchIndex } from '../../../enums/search.enum';
|
import { SearchIndex } from '../../../enums/search.enum';
|
||||||
import { CurrentTourPageType } from '../../../enums/tour.enum';
|
import { CurrentTourPageType } from '../../../enums/tour.enum';
|
||||||
import { OwnerType } from '../../../enums/user.enum';
|
import { OwnerType } from '../../../enums/user.enum';
|
||||||
|
import { Table } from '../../../generated/entity/data/table';
|
||||||
import { EntityReference } from '../../../generated/entity/type';
|
import { EntityReference } from '../../../generated/entity/type';
|
||||||
import {
|
import {
|
||||||
getEntityId,
|
getEntityId,
|
||||||
@ -48,6 +49,7 @@ export interface TableDataCardPropsV2 {
|
|||||||
value: number;
|
value: number;
|
||||||
}[];
|
}[];
|
||||||
searchIndex: SearchIndex | EntityType;
|
searchIndex: SearchIndex | EntityType;
|
||||||
|
handleSummaryPanelDisplay: (source: Table) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
|
const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
|
||||||
@ -55,6 +57,7 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
|
|||||||
source,
|
source,
|
||||||
matches,
|
matches,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
|
handleSummaryPanelDisplay,
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -141,7 +144,8 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="tw-bg-white tw-p-3 tw-border tw-border-main tw-rounded-md"
|
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>
|
<div>
|
||||||
{'databaseSchema' in source && 'database' in source && (
|
{'databaseSchema' in source && 'database' in source && (
|
||||||
<span
|
<span
|
||||||
|
|||||||
@ -11,11 +11,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isNil, isString } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import { ExtraInfo } from 'Models';
|
import { ExtraInfo } from 'Models';
|
||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
|
||||||
import { TagLabel } from '../../../generated/type/tagLabel';
|
import { TagLabel } from '../../../generated/type/tagLabel';
|
||||||
|
import { getTagValue } from '../../../utils/CommonUtils';
|
||||||
import SVGIcons from '../../../utils/SvgUtils';
|
import SVGIcons from '../../../utils/SvgUtils';
|
||||||
import TagsViewer from '../../tags-viewer/tags-viewer';
|
import TagsViewer from '../../tags-viewer/tags-viewer';
|
||||||
import EntitySummaryDetails from '../EntitySummaryDetails/EntitySummaryDetails';
|
import EntitySummaryDetails from '../EntitySummaryDetails/EntitySummaryDetails';
|
||||||
@ -32,21 +32,6 @@ const TableDataCardBody: FunctionComponent<Props> = ({
|
|||||||
extraInfo,
|
extraInfo,
|
||||||
tags,
|
tags,
|
||||||
}: Props) => {
|
}: 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 (
|
return (
|
||||||
<div data-testid="table-body">
|
<div data-testid="table-body">
|
||||||
<div className="tw-mb-4 tw-flex tw-items-center tw-flex-wrap tw-text-xs">
|
<div className="tw-mb-4 tw-flex tw-items-center tw-flex-wrap tw-text-xs">
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import { Table } from '../../generated/entity/data/table';
|
||||||
import { EntityReference } from '../../generated/entity/type';
|
import { EntityReference } from '../../generated/entity/type';
|
||||||
import { TagLabel } from '../../generated/type/tagLabel';
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
import {
|
import {
|
||||||
@ -67,4 +68,5 @@ export interface SearchedDataProps {
|
|||||||
showOnboardingTemplate?: boolean;
|
showOnboardingTemplate?: boolean;
|
||||||
showOnlyChildren?: boolean;
|
showOnlyChildren?: boolean;
|
||||||
isFilterSelected: boolean;
|
isFilterSelected: boolean;
|
||||||
|
handleSummaryPanelDisplay: (source: Table) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,7 @@ const mockData: SearchedDataProps['data'] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const mockPaginate = jest.fn();
|
const mockPaginate = jest.fn();
|
||||||
|
const mockHandleSummaryPanelDisplay = jest.fn();
|
||||||
|
|
||||||
jest.mock('../common/table-data-card/TableDataCard', () => {
|
jest.mock('../common/table-data-card/TableDataCard', () => {
|
||||||
return jest
|
return jest
|
||||||
@ -90,6 +91,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
isFilterSelected
|
isFilterSelected
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={10}
|
totalValue={10}
|
||||||
/>,
|
/>,
|
||||||
@ -109,6 +111,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
isFilterSelected
|
isFilterSelected
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={10}
|
totalValue={10}
|
||||||
/>,
|
/>,
|
||||||
@ -128,6 +131,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
isFilterSelected
|
isFilterSelected
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={10}>
|
totalValue={10}>
|
||||||
<p>hello world</p>
|
<p>hello world</p>
|
||||||
@ -146,6 +150,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
isFilterSelected
|
isFilterSelected
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={11}>
|
totalValue={11}>
|
||||||
<p>hello world</p>
|
<p>hello world</p>
|
||||||
@ -165,6 +170,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
showOnboardingTemplate
|
showOnboardingTemplate
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={[]}
|
data={[]}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={0}
|
totalValue={0}
|
||||||
/>,
|
/>,
|
||||||
@ -182,6 +188,7 @@ describe('Test SearchedData Component', () => {
|
|||||||
isFilterSelected
|
isFilterSelected
|
||||||
currentPage={0}
|
currentPage={0}
|
||||||
data={[]}
|
data={[]}
|
||||||
|
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||||
paginate={mockPaginate}
|
paginate={mockPaginate}
|
||||||
totalValue={0}
|
totalValue={0}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@ -44,6 +44,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
|
|||||||
totalValue,
|
totalValue,
|
||||||
isFilterSelected,
|
isFilterSelected,
|
||||||
searchText,
|
searchText,
|
||||||
|
handleSummaryPanelDisplay,
|
||||||
}) => {
|
}) => {
|
||||||
const highlightSearchResult = () => {
|
const highlightSearchResult = () => {
|
||||||
return data.map(({ _source: table, highlight, _index }, index) => {
|
return data.map(({ _source: table, highlight, _index }, index) => {
|
||||||
@ -89,6 +90,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="tw-mb-3" key={index}>
|
<div className="tw-mb-3" key={index}>
|
||||||
<TableDataCardV2
|
<TableDataCardV2
|
||||||
|
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
|
||||||
id={`tabledatacard${index}`}
|
id={`tabledatacard${index}`}
|
||||||
matches={matches}
|
matches={matches}
|
||||||
searchIndex={_index}
|
searchIndex={_index}
|
||||||
|
|||||||
@ -207,7 +207,9 @@
|
|||||||
"select-resource": "Select Resources",
|
"select-resource": "Select Resources",
|
||||||
"select-rule-effect": "Select Rule Effect",
|
"select-rule-effect": "Select Rule Effect",
|
||||||
"field-required": "{{field}} is required",
|
"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": {
|
"message": {
|
||||||
"service-email-required": "Service account Email is required",
|
"service-email-required": "Service account Email is required",
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
isEqual,
|
isEqual,
|
||||||
isNil,
|
isNil,
|
||||||
isNull,
|
isNull,
|
||||||
|
isString,
|
||||||
isUndefined,
|
isUndefined,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
@ -80,6 +81,7 @@ import { Role } from '../generated/entity/teams/role';
|
|||||||
import { Team } from '../generated/entity/teams/team';
|
import { Team } from '../generated/entity/teams/team';
|
||||||
import { EntityReference, User } from '../generated/entity/teams/user';
|
import { EntityReference, User } from '../generated/entity/teams/user';
|
||||||
import { Paging } from '../generated/type/paging';
|
import { Paging } from '../generated/type/paging';
|
||||||
|
import { TagLabel } from '../generated/type/tagLabel';
|
||||||
import { ServicesType } from '../interface/service.interface';
|
import { ServicesType } from '../interface/service.interface';
|
||||||
import jsonData from '../jsons/en';
|
import jsonData from '../jsons/en';
|
||||||
import { getEntityFeedLink, getTitleCase } from './EntityUtils';
|
import { getEntityFeedLink, getTitleCase } from './EntityUtils';
|
||||||
@ -1010,3 +1012,18 @@ export const getTierFromEntityInfo = (entity: FormattedTableData) => {
|
|||||||
getTierFromSearchTableTags((entity.tags || []).map((tag) => tag.tagFQN))
|
getTierFromSearchTableTags((entity.tags || []).map((tag) => tag.tagFQN))
|
||||||
)?.split(FQN_SEPARATOR_CHAR)[1];
|
)?.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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user