mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-27 00:31:42 +00:00
UI: Improved summary panel (#9470)
* Improved topic summary panel content * added functionality to show nested column and schema details inside the EntitySummaryPanel. * added unit tests for SummaryListItems component * added unit tests for summary panel components code optimizations and improvements * fixed localization in SummaryListItems component fixed unit tests * fixed codesmells * - added schema type field to show for topic summary - fixed width issue for nested field details in summary panel - added unit tests for EntitySummaryPanelUtils - moved imports on top in EntitySummaryPanel test * fixed failing unit tests * Fixed failing unit tests Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
parent
e52e4207f7
commit
91a794aaa0
@ -14,16 +14,19 @@
|
||||
import { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ChartType } from 'pages/DashboardDetailsPage/DashboardDetailsPage.component';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Dashboard } from '../../../../generated/entity/data/dashboard';
|
||||
import { fetchCharts } from '../../../../utils/DashboardDetailsUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import SVGIcons from '../../../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface DashboardSummaryProps {
|
||||
entityDetails: Dashboard;
|
||||
@ -57,6 +60,11 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
fetchChartsDetails();
|
||||
}, [entityDetails]);
|
||||
|
||||
const formattedChartsData: BasicEntityInfo[] = useMemo(
|
||||
() => getFormattedEntityData(SummaryEntityType.CHART, charts),
|
||||
[charts]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
@ -69,24 +77,33 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col className="text-gray" span={10}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid="dashboard-url-label"
|
||||
span={10}>
|
||||
{`${t('label.dashboard')} ${t('label.url-uppercase')}`}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.dashboardUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text className="link">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
<Col data-testid="dashboard-url-value" span={12}>
|
||||
{entityDetails.dashboardUrl ? (
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.dashboardUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text
|
||||
className="link"
|
||||
data-testid="dashboard-link-name">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
@ -94,12 +111,14 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="charts-header">
|
||||
{t('label.chart-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList charts={charts || []} />
|
||||
<SummaryList formattedEntityData={formattedChartsData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
mockDashboardEntityDetails,
|
||||
mockFetchChartsResponse,
|
||||
} from '../mocks/DashboardSummary.mock';
|
||||
import DashboardSummary from './DashboardSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../../../utils/DashboardDetailsUtils', () => ({
|
||||
fetchCharts: jest.fn().mockImplementation(() => mockFetchChartsResponse),
|
||||
}));
|
||||
|
||||
describe('DashboardSummary component tests', () => {
|
||||
it('Component should render properly', async () => {
|
||||
await act(async () => {
|
||||
render(<DashboardSummary entityDetails={mockDashboardEntityDetails} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const dashboardUrlLabel = screen.getByTestId('dashboard-url-label');
|
||||
const dashboardUrlValue = screen.getByTestId('dashboard-link-name');
|
||||
const chartsHeader = screen.getByTestId('charts-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(dashboardTitle).toBeInTheDocument();
|
||||
expect(dashboardUrlLabel).toBeInTheDocument();
|
||||
expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name);
|
||||
expect(chartsHeader).toBeInTheDocument();
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('If the dashboard url is not present in dashboard details, "-" should be displayed as dashboard url value', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<DashboardSummary
|
||||
entityDetails={{
|
||||
...mockDashboardEntityDetails,
|
||||
dashboardUrl: undefined,
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const dashboardUrlValue = screen.getByTestId('dashboard-url-value');
|
||||
|
||||
expect(dashboardUrlValue).toContainHTML('-');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -68,7 +68,11 @@ export default function EntitySummaryPanel({
|
||||
return (
|
||||
<div className={classNames('summary-panel-container')}>
|
||||
{summaryComponent}
|
||||
<CloseOutlined className="close-icon" onClick={handleClosePanel} />
|
||||
<CloseOutlined
|
||||
className="close-icon"
|
||||
data-testid="summary-panel-close-icon"
|
||||
onClick={handleClosePanel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { ExplorePageTabs } from '../../../enums/Explore.enum';
|
||||
import EntitySummaryPanel from './EntitySummaryPanel.component';
|
||||
import { mockDashboardEntityDetails } from './mocks/DashboardSummary.mock';
|
||||
import { mockMlModelEntityDetails } from './mocks/MlModelSummary.mock';
|
||||
import { mockPipelineEntityDetails } from './mocks/PipelineSummary.mock';
|
||||
import { mockTableEntityDetails } from './mocks/TableSummary.mock';
|
||||
import { mockTopicEntityDetails } from './mocks/TopicSummary.mock';
|
||||
|
||||
const mockHandleClosePanel = jest.fn();
|
||||
|
||||
jest.mock('./TableSummary/TableSummary.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableSummary">TableSummary</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('./TopicSummary/TopicSummary.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TopicSummary">TopicSummary</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('./DashboardSummary/DashboardSummary.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="DashboardSummary">DashboardSummary</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('./PipelineSummary/PipelineSummary.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="PipelineSummary">PipelineSummary</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('./MlModelSummary/MlModelSummary.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="MlModelSummary">MlModelSummary</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({ tab: 'table' })),
|
||||
}));
|
||||
|
||||
describe('EntitySummaryPanel component tests', () => {
|
||||
it('TableSummary should render for table data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockTableEntityDetails,
|
||||
entityType: ExplorePageTabs.TABLES,
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const tableSummary = screen.getByTestId('TableSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(tableSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(closeIcon);
|
||||
});
|
||||
|
||||
expect(mockHandleClosePanel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('TopicSummary should render for topics data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockTopicEntityDetails,
|
||||
entityType: ExplorePageTabs.TOPICS,
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const topicSummary = screen.getByTestId('TopicSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(topicSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(closeIcon);
|
||||
});
|
||||
|
||||
expect(mockHandleClosePanel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('DashboardSummary should render for dashboard data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockDashboardEntityDetails,
|
||||
entityType: ExplorePageTabs.DASHBOARDS,
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const dashboardSummary = screen.getByTestId('DashboardSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(dashboardSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(closeIcon);
|
||||
});
|
||||
|
||||
expect(mockHandleClosePanel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('PipelineSummary should render for pipeline data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockPipelineEntityDetails,
|
||||
entityType: ExplorePageTabs.PIPELINES,
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const pipelineSummary = screen.getByTestId('PipelineSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(pipelineSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(closeIcon);
|
||||
});
|
||||
|
||||
expect(mockHandleClosePanel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('MlModelSummary should render for mlModel data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockMlModelEntityDetails,
|
||||
entityType: ExplorePageTabs.MLMODELS,
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const mlModelSummary = screen.getByTestId('MlModelSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(mlModelSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(closeIcon);
|
||||
});
|
||||
|
||||
expect(mockHandleClosePanel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -12,15 +12,19 @@
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { ReactNode, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getDashboardDetailsPath } from '../../../../constants/constants';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Mlmodel } from '../../../../generated/entity/data/mlmodel';
|
||||
import { getEntityName } from '../../../../utils/CommonUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface MlModelSummaryProps {
|
||||
entityDetails: Mlmodel;
|
||||
@ -55,6 +59,15 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) {
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
const formattedFeaturesData: BasicEntityInfo[] = useMemo(
|
||||
() =>
|
||||
getFormattedEntityData(
|
||||
SummaryEntityType.MLFEATURE,
|
||||
entityDetails.mlFeatures
|
||||
),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
@ -70,22 +83,22 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) {
|
||||
{Object.keys(basicMlModelInfo).map((fieldName) => {
|
||||
const value =
|
||||
basicMlModelInfo[fieldName as keyof BasicMlModelInfo];
|
||||
if (value) {
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col className="text-gray" span={10}>
|
||||
{fieldName}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
{basicMlModelInfo[fieldName as keyof BasicMlModelInfo]}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{startCase(fieldName)}
|
||||
</Col>
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{value ? value : '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
@ -93,12 +106,14 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) {
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="features-header">
|
||||
{t('label.feature-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList mlFeatures={entityDetails.mlFeatures || []} />
|
||||
<SummaryList formattedEntityData={formattedFeaturesData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2023 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 { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
mockMlModelEntityDetails,
|
||||
mockMlModelEntityDetails1,
|
||||
} from '../mocks/MlModelSummary.mock';
|
||||
import MlModelSummary from './MlModelSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
|
||||
);
|
||||
|
||||
describe('MlModelSummary component tests', () => {
|
||||
it('Component should render properly', () => {
|
||||
render(<MlModelSummary entityDetails={mockMlModelEntityDetails} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const mlModelTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const algorithmLabel = screen.getByTestId('algorithm-label');
|
||||
const targetLabel = screen.getByTestId('target-label');
|
||||
const serverLabel = screen.getByTestId('server-label');
|
||||
const dashboardLabel = screen.getByTestId('dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('algorithm-value');
|
||||
const targetValue = screen.getByTestId('target-value');
|
||||
const serverValue = screen.getByTestId('server-value');
|
||||
const dashboardValue = screen.getByTestId('dashboard-value');
|
||||
|
||||
expect(mlModelTitle).toBeInTheDocument();
|
||||
expect(algorithmLabel).toBeInTheDocument();
|
||||
expect(targetLabel).toBeInTheDocument();
|
||||
expect(serverLabel).toBeInTheDocument();
|
||||
expect(dashboardLabel).toBeInTheDocument();
|
||||
expect(algorithmValue).toContainHTML('Neural Network');
|
||||
expect(targetValue).toContainHTML('ETA_time');
|
||||
expect(serverValue).toContainHTML('http://my-server.ai');
|
||||
expect(dashboardValue).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Fields with no data should display "-" in value', () => {
|
||||
render(<MlModelSummary entityDetails={mockMlModelEntityDetails1} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const algorithmLabel = screen.getByTestId('algorithm-label');
|
||||
const targetLabel = screen.queryByTestId('target-label');
|
||||
const serverLabel = screen.queryByTestId('server-label');
|
||||
const dashboardLabel = screen.queryByTestId('dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('algorithm-value');
|
||||
const targetValue = screen.getByTestId('target-value');
|
||||
const serverValue = screen.getByTestId('server-value');
|
||||
const dashboardValue = screen.getByTestId('dashboard-value');
|
||||
|
||||
expect(algorithmLabel).toBeInTheDocument();
|
||||
expect(targetLabel).toBeInTheDocument();
|
||||
expect(serverLabel).toBeInTheDocument();
|
||||
expect(dashboardLabel).toBeInTheDocument();
|
||||
expect(algorithmValue).toContainHTML('Time Series');
|
||||
expect(targetValue).toContainHTML('-');
|
||||
expect(serverValue).toContainHTML('-');
|
||||
expect(dashboardValue).toContainHTML('-');
|
||||
});
|
||||
});
|
||||
@ -12,16 +12,17 @@
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Pipeline, Task } from '../../../../generated/entity/data/pipeline';
|
||||
import { Pipeline } from '../../../../generated/entity/data/pipeline';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import SVGIcons from '../../../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface PipelineSummaryProps {
|
||||
entityDetails: Pipeline;
|
||||
@ -29,28 +30,11 @@ interface PipelineSummaryProps {
|
||||
|
||||
function PipelineSummary({ entityDetails }: PipelineSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
const [tasks, setTasks] = useState<Task[]>();
|
||||
|
||||
const fetchTaskDetails = async () => {
|
||||
try {
|
||||
const updatedTasks = (entityDetails.tasks || []).map((task) => ({
|
||||
...task,
|
||||
taskUrl: task.taskUrl,
|
||||
}));
|
||||
setTasks(updatedTasks);
|
||||
} catch (err) {
|
||||
showErrorToast(
|
||||
err as AxiosError,
|
||||
t('server.entity-fetch-error', {
|
||||
entity: t('label.pipeline-detail-plural-lowercase'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTaskDetails();
|
||||
}, [entityDetails]);
|
||||
const formattedTasksData: BasicEntityInfo[] = useMemo(
|
||||
() => getFormattedEntityData(SummaryEntityType.TASK, entityDetails.tasks),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -64,24 +48,33 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) {
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col className="text-gray" span={10}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid="pipeline-url-label"
|
||||
span={10}>
|
||||
{`${t('label.pipeline')} ${t('label.url-uppercase')}`}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.pipelineUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text className="link">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
<Col data-testid="pipeline-url-value" span={12}>
|
||||
{entityDetails.pipelineUrl ? (
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.pipelineUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text
|
||||
className="link"
|
||||
data-testid="pipeline-link-name">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
@ -89,12 +82,14 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) {
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="tasks-header">
|
||||
{t('label.task-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList tasks={tasks || []} />
|
||||
<SummaryList formattedEntityData={formattedTasksData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2023 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 { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { mockPipelineEntityDetails } from '../mocks/PipelineSummary.mock';
|
||||
import PipelineSummary from './PipelineSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
|
||||
);
|
||||
|
||||
describe('PipelineSummary component tests', () => {
|
||||
it('Component should render properly', () => {
|
||||
render(<PipelineSummary entityDetails={mockPipelineEntityDetails} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const pipelineTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const pipelineUrlLabel = screen.getByTestId('pipeline-url-label');
|
||||
const pipelineUrlValue = screen.getByTestId('pipeline-link-name');
|
||||
const tasksHeader = screen.getByTestId('tasks-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(pipelineTitle).toBeInTheDocument();
|
||||
expect(pipelineUrlLabel).toBeInTheDocument();
|
||||
expect(pipelineUrlValue).toContainHTML(mockPipelineEntityDetails.name);
|
||||
expect(tasksHeader).toBeInTheDocument();
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('If the pipeline url is not present in pipeline details, "-" should be displayed as pipeline url value', () => {
|
||||
render(
|
||||
<PipelineSummary
|
||||
entityDetails={{ ...mockPipelineEntityDetails, pipelineUrl: undefined }}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const pipelineUrlValue = screen.getByTestId('pipeline-url-value');
|
||||
|
||||
expect(pipelineUrlValue).toContainHTML('-');
|
||||
});
|
||||
});
|
||||
@ -11,175 +11,60 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Collapse, Row, Typography } from 'antd';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as IconTagGrey } from '../../../../assets/svg/tag-grey.svg';
|
||||
import { MAX_CHAR_LIMIT_ENTITY_SUMMARY } from '../../../../constants/constants';
|
||||
import { getEntityName, getTagValue } from '../../../../utils/CommonUtils';
|
||||
import SVGIcons from '../../../../utils/SvgUtils';
|
||||
import { prepareConstraintIcon } from '../../../../utils/TableUtils';
|
||||
import RichTextEditorPreviewer from '../../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import TagsViewer from '../../../tags-viewer/tags-viewer';
|
||||
import { BasicColumnInfo, SummaryListProps } from './SummaryList.interface';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SummaryListProps } from './SummaryList.interface';
|
||||
import './SummaryList.style.less';
|
||||
import SummaryListItems from './SummaryListItems/SummaryListItems.component';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function SummaryList({
|
||||
columns,
|
||||
charts,
|
||||
tasks,
|
||||
mlFeatures,
|
||||
formattedEntityData,
|
||||
entityType,
|
||||
}: SummaryListProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formattedColumnsData: BasicColumnInfo[] = useMemo(() => {
|
||||
if (columns) {
|
||||
return columns.map((column) => ({
|
||||
name: column.name,
|
||||
title: <Text className="entity-title">{column.name}</Text>,
|
||||
type: column.dataType,
|
||||
tags: column.tags,
|
||||
description: column.description,
|
||||
constraint: column.constraint,
|
||||
}));
|
||||
} else if (charts) {
|
||||
return charts.map((chart) => ({
|
||||
name: chart.name,
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{getEntityName(chart)}</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
type: chart.chartType,
|
||||
tags: chart.tags,
|
||||
description: chart.description,
|
||||
}));
|
||||
} else if (tasks) {
|
||||
return tasks.map((task) => ({
|
||||
name: task.name,
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: task.taskUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{task.name}</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
type: task.taskType,
|
||||
tags: task.tags,
|
||||
description: task.description,
|
||||
}));
|
||||
} else if (mlFeatures) {
|
||||
return mlFeatures.map((feature) => ({
|
||||
algorithm: feature.featureAlgorithm,
|
||||
name: feature.name || '--',
|
||||
title: <Text className="entity-title">{feature.name}</Text>,
|
||||
type: feature.dataType,
|
||||
tags: feature.tags,
|
||||
description: feature.description,
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [columns, charts, tasks, mlFeatures]);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
{isEmpty(formattedColumnsData) ? (
|
||||
{isEmpty(formattedEntityData) ? (
|
||||
<div className="m-y-md">
|
||||
<Text className="text-gray">{t('message.no-data-available')}</Text>
|
||||
</div>
|
||||
) : (
|
||||
formattedColumnsData.map((entity) => (
|
||||
<Col key={entity.name} span={24}>
|
||||
<Row gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
{columns &&
|
||||
prepareConstraintIcon(
|
||||
entity.name,
|
||||
entity.constraint,
|
||||
undefined,
|
||||
'm-r-xss',
|
||||
'14px'
|
||||
)}
|
||||
{entity.title}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row className="text-xs font-300" gutter={[4, 4]}>
|
||||
<Col>
|
||||
{entity.type && (
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t(
|
||||
'label.type'
|
||||
)}:`}</Text>
|
||||
<Text className="font-medium">{entity.type}</Text>
|
||||
</Space>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
{entity.algorithm && (
|
||||
<>
|
||||
<Col>
|
||||
<Divider type="vertical" />
|
||||
</Col>
|
||||
<Col>
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t(
|
||||
'label.algorithm'
|
||||
)}:`}</Text>
|
||||
<Text className="font-medium">
|
||||
{entity.algorithm}
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
{entity.tags && entity.tags.length !== 0 && (
|
||||
<>
|
||||
<Col>
|
||||
<Divider type="vertical" />
|
||||
</Col>
|
||||
<Col className="flex-grow">
|
||||
<Space>
|
||||
<IconTagGrey className="w-12 h-12" />
|
||||
<Row wrap>
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={(entity.tags || []).map((tag) =>
|
||||
getTagValue(tag)
|
||||
)}
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph>
|
||||
{entity.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
markdown={entity.description || ''}
|
||||
maxLength={MAX_CHAR_LIMIT_ENTITY_SUMMARY}
|
||||
/>
|
||||
) : (
|
||||
t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})
|
||||
)}
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
</Col>
|
||||
))
|
||||
formattedEntityData.map((entity) =>
|
||||
isEmpty(entity.children) || isUndefined(entity.children) ? (
|
||||
<SummaryListItems
|
||||
entityDetails={entity}
|
||||
isColumnsData={entityType === SummaryEntityType.COLUMN}
|
||||
key={`${entity.name}-summary-list-item`}
|
||||
/>
|
||||
) : (
|
||||
<Collapse
|
||||
ghost
|
||||
className="summary-list-collapse w-full"
|
||||
collapsible="icon"
|
||||
key={`${entity.name}-collapse`}>
|
||||
<Collapse.Panel
|
||||
data-testid={`${entity.name}-collapse`}
|
||||
header={
|
||||
<SummaryListItems
|
||||
entityDetails={entity}
|
||||
isColumnsData={entityType === SummaryEntityType.COLUMN}
|
||||
/>
|
||||
}
|
||||
key={`${entity.name}-collapse-panel`}>
|
||||
<SummaryList
|
||||
entityType={entityType}
|
||||
formattedEntityData={entity.children}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
|
||||
@ -12,27 +12,13 @@
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { Chart, ChartType } from '../../../../generated/entity/data/chart';
|
||||
import {
|
||||
FeatureType,
|
||||
MlFeature,
|
||||
} from '../../../../generated/entity/data/mlmodel';
|
||||
import { Task } from '../../../../generated/entity/data/pipeline';
|
||||
import {
|
||||
Column,
|
||||
Constraint,
|
||||
DataType,
|
||||
} from '../../../../generated/entity/data/table';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { ChartType } from '../../../../generated/entity/data/chart';
|
||||
import { FeatureType } from '../../../../generated/entity/data/mlmodel';
|
||||
import { Constraint, DataType } from '../../../../generated/entity/data/table';
|
||||
import { TagLabel } from '../../../../generated/type/tagLabel';
|
||||
|
||||
export interface SummaryListProps {
|
||||
columns?: Column[];
|
||||
charts?: Chart[];
|
||||
tasks?: Task[];
|
||||
mlFeatures?: MlFeature[];
|
||||
}
|
||||
|
||||
export interface BasicColumnInfo {
|
||||
export interface BasicEntityInfo {
|
||||
algorithm?: string;
|
||||
name: string;
|
||||
title: ReactNode;
|
||||
@ -40,4 +26,10 @@ export interface BasicColumnInfo {
|
||||
tags?: TagLabel[];
|
||||
description?: string;
|
||||
constraint?: Constraint;
|
||||
children?: BasicEntityInfo[];
|
||||
}
|
||||
|
||||
export interface SummaryListProps {
|
||||
formattedEntityData: BasicEntityInfo[];
|
||||
entityType?: SummaryEntityType;
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
.summary-list-collapse {
|
||||
.ant-collapse-item {
|
||||
.ant-collapse-header {
|
||||
padding: 0px;
|
||||
|
||||
.ant-collapse-arrow {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.ant-collapse-content {
|
||||
.ant-collapse-content-box {
|
||||
padding: 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2023 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import {
|
||||
mockFormattedEntityData,
|
||||
mockFormattedEntityDataWithChildren,
|
||||
} from '../mocks/SummaryList.mock';
|
||||
import SummaryList from './SummaryList.component';
|
||||
|
||||
jest.mock('./SummaryListItems/SummaryListItems.component', () =>
|
||||
jest.fn().mockImplementation(({ entityDetails, isColumnsData }) => (
|
||||
<div data-testid={`SummaryListItems-${entityDetails.name}`}>
|
||||
<div>{entityDetails.name}</div>
|
||||
<div data-testid={`isColumnsData-${entityDetails.name}`}>
|
||||
{`${isColumnsData}`}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
|
||||
describe('SummaryList component tests', () => {
|
||||
it('No data placeholder should display when an empty array is sent as a prop', () => {
|
||||
const { getByText } = render(<SummaryList formattedEntityData={[]} />);
|
||||
|
||||
const noDataPlaceholder = getByText('message.no-data-available');
|
||||
|
||||
expect(noDataPlaceholder).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Summary list items should render properly for given formatted entity data', () => {
|
||||
const { getByTestId } = render(
|
||||
<SummaryList formattedEntityData={mockFormattedEntityData} />
|
||||
);
|
||||
|
||||
const summaryListItem1 = getByTestId('SummaryListItems-name1');
|
||||
const summaryListItem2 = getByTestId('SummaryListItems-name2');
|
||||
const isColumnData1 = getByTestId('isColumnsData-name1');
|
||||
const isColumnData2 = getByTestId('isColumnsData-name2');
|
||||
|
||||
expect(summaryListItem1).toBeInTheDocument();
|
||||
expect(summaryListItem2).toBeInTheDocument();
|
||||
expect(isColumnData1).toContainHTML('false');
|
||||
expect(isColumnData2).toContainHTML('false');
|
||||
});
|
||||
|
||||
it('SummaryListItem component should receive isColumnsData prop true for entityType "column"', () => {
|
||||
const { getByTestId } = render(
|
||||
<SummaryList
|
||||
entityType={SummaryEntityType.COLUMN}
|
||||
formattedEntityData={mockFormattedEntityData}
|
||||
/>
|
||||
);
|
||||
|
||||
const summaryListItem1 = getByTestId('SummaryListItems-name1');
|
||||
const summaryListItem2 = getByTestId('SummaryListItems-name2');
|
||||
const isColumnData1 = getByTestId('isColumnsData-name1');
|
||||
const isColumnData2 = getByTestId('isColumnsData-name2');
|
||||
|
||||
expect(summaryListItem1).toBeInTheDocument();
|
||||
expect(summaryListItem2).toBeInTheDocument();
|
||||
expect(isColumnData1).toContainHTML('true');
|
||||
expect(isColumnData2).toContainHTML('true');
|
||||
});
|
||||
|
||||
it('Collapse should render for entity with children', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<SummaryList formattedEntityData={mockFormattedEntityDataWithChildren} />
|
||||
);
|
||||
|
||||
const collapse1 = getByTestId('name1-collapse');
|
||||
const collapse2 = queryByTestId('name2-collapse');
|
||||
|
||||
expect(collapse1).toBeInTheDocument();
|
||||
expect(collapse2).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as IconTagGrey } from '../../../../../assets/svg/tag-grey.svg';
|
||||
import { MAX_CHAR_LIMIT_ENTITY_SUMMARY } from '../../../../../constants/constants';
|
||||
import { getTagValue } from '../../../../../utils/CommonUtils';
|
||||
import { prepareConstraintIcon } from '../../../../../utils/TableUtils';
|
||||
import RichTextEditorPreviewer from '../../../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import TagsViewer from '../../../../tags-viewer/tags-viewer';
|
||||
import { SummaryListItemProps } from './SummaryListItems.interface';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
function SummaryListItem({
|
||||
entityDetails,
|
||||
isColumnsData,
|
||||
}: SummaryListItemProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Col key={entityDetails.name} span={24}>
|
||||
<Row gutter={[0, 4]}>
|
||||
<Col data-testid="title-container" span={24}>
|
||||
{isColumnsData &&
|
||||
prepareConstraintIcon(
|
||||
entityDetails.name,
|
||||
entityDetails.constraint,
|
||||
undefined,
|
||||
'm-r-xss',
|
||||
'14px'
|
||||
)}
|
||||
{entityDetails.title}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row className="text-xs font-300" gutter={[4, 4]}>
|
||||
<Col>
|
||||
{entityDetails.type && (
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t('label.type')}:`}</Text>
|
||||
<Text className="font-medium" data-testid="entity-type">
|
||||
{entityDetails.type}
|
||||
</Text>
|
||||
</Space>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
{entityDetails.algorithm && (
|
||||
<>
|
||||
<Col>
|
||||
<Divider type="vertical" />
|
||||
</Col>
|
||||
<Col>
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t(
|
||||
'label.algorithm'
|
||||
)}:`}</Text>
|
||||
<Text className="font-medium" data-testid="algorithm">
|
||||
{entityDetails.algorithm}
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
{entityDetails.tags && entityDetails.tags.length !== 0 && (
|
||||
<>
|
||||
<Col>
|
||||
<Divider type="vertical" />
|
||||
</Col>
|
||||
<Col className="flex-grow">
|
||||
<Space>
|
||||
<IconTagGrey
|
||||
className="w-12 h-12"
|
||||
data-testid="tag-grey-icon"
|
||||
/>
|
||||
<Row wrap>
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={(entityDetails.tags || []).map((tag) =>
|
||||
getTagValue(tag)
|
||||
)}
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph>
|
||||
{entityDetails.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
markdown={entityDetails.description || ''}
|
||||
maxLength={MAX_CHAR_LIMIT_ENTITY_SUMMARY}
|
||||
/>
|
||||
) : (
|
||||
t('label.no-entity', { entity: t('label.description') })
|
||||
)}
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default SummaryListItem;
|
||||
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 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 { BasicEntityInfo } from '../SummaryList.interface';
|
||||
|
||||
export interface SummaryListItemProps {
|
||||
entityDetails: BasicEntityInfo;
|
||||
isColumnsData?: boolean;
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2023 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
mockEntityDetails,
|
||||
mockEntityDetailsWithConstraint,
|
||||
mockEntityDetailsWithoutDescription,
|
||||
mockEntityDetailsWithTagsAndAlgorithm,
|
||||
} from '../../mocks/SummaryListItems.mock';
|
||||
import SummaryListItem from './SummaryListItems.component';
|
||||
|
||||
jest.mock('../../../../common/rich-text-editor/RichTextEditorPreviewer', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="RichTextEditorPreviewer">RichTextEditorPreviewer</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../../../../tags-viewer/tags-viewer', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="TagsViewer">TagsViewer</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../../../../utils/TableUtils', () => ({
|
||||
prepareConstraintIcon: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="constraints">Constraints</div>),
|
||||
}));
|
||||
|
||||
describe('SummaryListItems component tests', () => {
|
||||
it('Component should render properly with title, type and description of the entity', () => {
|
||||
const { getByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetails} />
|
||||
);
|
||||
|
||||
const titleContainer = getByTestId('title-container');
|
||||
const title = getByTestId('title');
|
||||
const type = getByTestId('entity-type');
|
||||
const description = getByTestId('RichTextEditorPreviewer');
|
||||
|
||||
expect(titleContainer).toBeInTheDocument();
|
||||
expect(title).toContainHTML('Title');
|
||||
expect(type).toBeInTheDocument();
|
||||
expect(type).toContainHTML(mockEntityDetails.type);
|
||||
expect(description).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('tags and algorithm should not render if entity has no tags and algorithm', () => {
|
||||
const { queryByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetails} />
|
||||
);
|
||||
|
||||
const tags = queryByTestId('TagsViewer');
|
||||
const algorithm = queryByTestId('algorithm');
|
||||
|
||||
expect(tags).toBeNull();
|
||||
expect(algorithm).toBeNull();
|
||||
});
|
||||
|
||||
it('tags and algorithm should render if entity has tags and algorithm', () => {
|
||||
const { getByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetailsWithTagsAndAlgorithm} />
|
||||
);
|
||||
|
||||
const tags = getByTestId('TagsViewer');
|
||||
const algorithm = getByTestId('algorithm');
|
||||
|
||||
expect(tags).toBeInTheDocument();
|
||||
expect(algorithm).toBeInTheDocument();
|
||||
expect(algorithm).toContainHTML(
|
||||
mockEntityDetailsWithTagsAndAlgorithm.algorithm
|
||||
);
|
||||
});
|
||||
|
||||
it('no description placeholder should be displayed for entity having no description', async () => {
|
||||
const { getByText, queryByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetailsWithoutDescription} />
|
||||
);
|
||||
|
||||
const richTextEditorPreviewer = queryByTestId('RichTextEditorPreviewer');
|
||||
const noDescription = getByText('label.no-entity');
|
||||
|
||||
expect(richTextEditorPreviewer).toBeNull();
|
||||
expect(noDescription).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('constraint icon should not be present for entity without constraint details', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetails} />
|
||||
);
|
||||
|
||||
const richTextEditorPreviewer = queryByTestId('constraints');
|
||||
|
||||
expect(richTextEditorPreviewer).toBeNull();
|
||||
});
|
||||
|
||||
it('constraint icon should not be present for entity if isColumnsData prop is not true', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<SummaryListItem entityDetails={mockEntityDetailsWithConstraint} />
|
||||
);
|
||||
|
||||
const richTextEditorPreviewer = queryByTestId('constraints');
|
||||
|
||||
expect(richTextEditorPreviewer).toBeNull();
|
||||
});
|
||||
|
||||
it('constraint icon should be displayed for entity with constraint details', async () => {
|
||||
const { getByTestId } = render(
|
||||
<SummaryListItem
|
||||
isColumnsData
|
||||
entityDetails={mockEntityDetailsWithConstraint}
|
||||
/>
|
||||
);
|
||||
|
||||
const richTextEditorPreviewer = getByTestId('constraints');
|
||||
|
||||
expect(richTextEditorPreviewer).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -24,6 +24,7 @@ import {
|
||||
import { getListTestCase } from 'rest/testAPI';
|
||||
import { API_RES_MAX_SIZE } from '../../../../constants/constants';
|
||||
import { INITIAL_TEST_RESULT_SUMMARY } from '../../../../constants/profiler.constant';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Table, TableType } from '../../../../generated/entity/data/table';
|
||||
import { Include } from '../../../../generated/type/include';
|
||||
@ -32,6 +33,7 @@ import {
|
||||
formTwoDigitNmber,
|
||||
} from '../../../../utils/CommonUtils';
|
||||
import { updateTestResults } from '../../../../utils/DataQualityAndProfilerUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import { generateEntityLink } from '../../../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
@ -40,6 +42,7 @@ import {
|
||||
TableTestsType,
|
||||
} from '../../../TableProfiler/TableProfiler.interface';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
import { BasicTableInfo, TableSummaryProps } from './TableSummary.interface';
|
||||
|
||||
function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
@ -162,6 +165,11 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
[tableType, columns, tableQueries]
|
||||
);
|
||||
|
||||
const formattedColumnsData: BasicEntityInfo[] = useMemo(
|
||||
() => getFormattedEntityData(SummaryEntityType.COLUMN, columns),
|
||||
[columns]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(entityDetails)) {
|
||||
setTableDetails(entityDetails);
|
||||
@ -185,10 +193,13 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
{Object.keys(basicTableInfo).map((fieldName) => (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col className="text-gray" span={10}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{fieldName}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{basicTableInfo[fieldName as keyof BasicTableInfo]}
|
||||
</Col>
|
||||
</Row>
|
||||
@ -201,13 +212,15 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
|
||||
<Row className={classNames('m-md')} gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="profiler-header">
|
||||
{t('label.profiler-amp-data-quality')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{isUndefined(overallSummary) ? (
|
||||
<Typography.Text>
|
||||
<Typography.Text data-testid="no-profiler-enabled-message">
|
||||
{t('message.no-profiler-enabled-summary-message')}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
@ -216,7 +229,9 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
<Col key={field.title} span={10}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="text-gray">
|
||||
<Typography.Text
|
||||
className="text-gray"
|
||||
data-testid={`${field.title}-label`}>
|
||||
{field.title}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
@ -225,7 +240,8 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
className={classNames(
|
||||
'summary-panel-statistics-count',
|
||||
field.className
|
||||
)}>
|
||||
)}
|
||||
data-testid={`${field.title}-value`}>
|
||||
{field.value}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
@ -239,12 +255,17 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
<Divider className="m-0" />
|
||||
<Row className={classNames('m-md')} gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList columns={columns} />
|
||||
<SummaryList
|
||||
entityType={SummaryEntityType.COLUMN}
|
||||
formattedEntityData={formattedColumnsData}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getLatestTableProfileByFqn } from 'rest/tableAPI';
|
||||
import { mockTableEntityDetails } from '../mocks/TableSummary.mock';
|
||||
import TableSummary from './TableSummary.component';
|
||||
|
||||
jest.mock('rest/testAPI', () => ({
|
||||
getListTestCase: jest.fn().mockReturnValue([]),
|
||||
}));
|
||||
|
||||
jest.mock('rest/tableAPI', () => ({
|
||||
getLatestTableProfileByFqn: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTableEntityDetails),
|
||||
getTableQueryByTableId: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTableEntityDetails),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
|
||||
);
|
||||
|
||||
describe('TableSummary component tests', () => {
|
||||
it('Component should render properly', async () => {
|
||||
await act(async () => {
|
||||
render(<TableSummary entityDetails={mockTableEntityDetails} />);
|
||||
});
|
||||
|
||||
const tableTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const profilerHeader = screen.getByTestId('profiler-header');
|
||||
const schemaHeader = screen.getByTestId('schema-header');
|
||||
const typeLabel = screen.getByTestId('Type-label');
|
||||
const queriesLabel = screen.getByTestId('Queries-label');
|
||||
const columnsLabel = screen.getByTestId('Columns-label');
|
||||
const typeValue = screen.getByTestId('Type-value');
|
||||
const queriesValue = screen.getByTestId('Queries-value');
|
||||
const columnsValue = screen.getByTestId('Columns-value');
|
||||
const noProfilerPlaceholder = screen.getByTestId(
|
||||
'no-profiler-enabled-message'
|
||||
);
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(tableTitle).toBeInTheDocument();
|
||||
expect(profilerHeader).toBeInTheDocument();
|
||||
expect(schemaHeader).toBeInTheDocument();
|
||||
expect(typeLabel).toBeInTheDocument();
|
||||
expect(queriesLabel).toBeInTheDocument();
|
||||
expect(columnsLabel).toBeInTheDocument();
|
||||
expect(typeValue).toContainHTML('Regular');
|
||||
expect(queriesValue).toContainHTML('2');
|
||||
expect(columnsValue).toContainHTML('2');
|
||||
expect(noProfilerPlaceholder).toContainHTML(
|
||||
'message.no-profiler-enabled-summary-message'
|
||||
);
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Profiler data should be displayed for tables with profiler data available', async () => {
|
||||
(getLatestTableProfileByFqn as jest.Mock).mockImplementationOnce(() => ({
|
||||
...mockTableEntityDetails,
|
||||
profile: { rowCount: 30, columnCount: 2, timestamp: 38478857 },
|
||||
}));
|
||||
|
||||
await act(async () => {
|
||||
render(<TableSummary entityDetails={mockTableEntityDetails} />);
|
||||
});
|
||||
|
||||
const rowCountLabel = screen.getByTestId('label.row-count-label');
|
||||
const colCountLabel = screen.getByTestId('label.column-entity-label');
|
||||
const tableSampleLabel = screen.getByTestId(
|
||||
'label.table-entity-text %-label'
|
||||
);
|
||||
const testsPassedLabel = screen.getByTestId(
|
||||
'label.test-plural label.passed-label'
|
||||
);
|
||||
const testsAbortedLabel = screen.getByTestId(
|
||||
'label.test-plural label.aborted-label'
|
||||
);
|
||||
const testsFailedLabel = screen.getByTestId(
|
||||
'label.test-plural label.failed-label'
|
||||
);
|
||||
const rowCountValue = screen.getByTestId('label.row-count-value');
|
||||
const colCountValue = screen.getByTestId('label.column-entity-value');
|
||||
const tableSampleValue = screen.getByTestId(
|
||||
'label.table-entity-text %-value'
|
||||
);
|
||||
const testsPassedValue = screen.getByTestId(
|
||||
'label.test-plural label.passed-value'
|
||||
);
|
||||
const testsAbortedValue = screen.getByTestId(
|
||||
'label.test-plural label.aborted-value'
|
||||
);
|
||||
const testsFailedValue = screen.getByTestId(
|
||||
'label.test-plural label.failed-value'
|
||||
);
|
||||
|
||||
expect(rowCountLabel).toBeInTheDocument();
|
||||
expect(colCountLabel).toBeInTheDocument();
|
||||
expect(tableSampleLabel).toBeInTheDocument();
|
||||
expect(testsPassedLabel).toBeInTheDocument();
|
||||
expect(testsAbortedLabel).toBeInTheDocument();
|
||||
expect(testsFailedLabel).toBeInTheDocument();
|
||||
expect(rowCountValue).toContainHTML('30');
|
||||
expect(colCountValue).toContainHTML('2');
|
||||
expect(tableSampleValue).toContainHTML('100%');
|
||||
expect(testsPassedValue).toContainHTML('00');
|
||||
expect(testsAbortedValue).toContainHTML('00');
|
||||
expect(testsFailedValue).toContainHTML('00');
|
||||
});
|
||||
});
|
||||
@ -13,15 +13,20 @@
|
||||
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { isArray } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Topic } from '../../../../generated/entity/data/topic';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import { bytesToSize } from '../../../../utils/StringsUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import { getConfigObject } from '../../../../utils/TopicDetailsUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SchemaEditor from '../../../schema-editor/SchemaEditor';
|
||||
import { TopicConfigObjectInterface } from '../../../TopicDetails/TopicDetails.interface';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface TopicSummaryProps {
|
||||
entityDetails: Topic;
|
||||
@ -29,15 +34,49 @@ interface TopicSummaryProps {
|
||||
|
||||
function TopicSummary({ entityDetails }: TopicSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
const [topicDetails, setTopicDetails] = useState<Topic>(entityDetails);
|
||||
|
||||
const topicConfig = useMemo(() => {
|
||||
const configs = getConfigObject(entityDetails);
|
||||
const configs = getConfigObject(topicDetails);
|
||||
|
||||
return {
|
||||
...configs,
|
||||
'Retention Size': bytesToSize(configs['Retention Size'] ?? 0),
|
||||
'Max Message Size': bytesToSize(configs['Max Message Size'] ?? 0),
|
||||
};
|
||||
}, [topicDetails]);
|
||||
|
||||
const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo(
|
||||
() =>
|
||||
getFormattedEntityData(
|
||||
SummaryEntityType.SCHEMAFIELD,
|
||||
topicDetails.messageSchema?.schemaFields
|
||||
),
|
||||
[topicDetails]
|
||||
);
|
||||
|
||||
const fetchExtraTopicInfo = async () => {
|
||||
try {
|
||||
const res = await getTopicByFqn(
|
||||
entityDetails.fullyQualifiedName ?? '',
|
||||
''
|
||||
);
|
||||
|
||||
const { partitions } = res;
|
||||
|
||||
setTopicDetails({ ...entityDetails, partitions });
|
||||
} catch {
|
||||
showErrorToast(
|
||||
t('server.entity-details-fetch-error', {
|
||||
entityType: t('label.topic-lowercase'),
|
||||
entityName: entityDetails.name,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchExtraTopicInfo();
|
||||
}, [entityDetails]);
|
||||
|
||||
return (
|
||||
@ -56,14 +95,19 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) {
|
||||
const value =
|
||||
topicConfig[fieldName as keyof TopicConfigObjectInterface];
|
||||
|
||||
const fieldValue = isArray(value) ? value.join(', ') : value;
|
||||
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col className="text-gray" span={10}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{fieldName}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
{isArray(value) ? value.join(', ') : value}
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{fieldValue ? fieldValue : '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
@ -73,21 +117,22 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) {
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md">
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="section-header">
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{entityDetails.messageSchema?.schemaText ? (
|
||||
<SchemaEditor
|
||||
editorClass="summary-schema-editor"
|
||||
value={entityDetails.messageSchema.schemaText}
|
||||
/>
|
||||
{entityDetails.messageSchema?.schemaFields ? (
|
||||
<SummaryList formattedEntityData={formattedSchemaFieldsData} />
|
||||
) : (
|
||||
<div className="m-y-md">
|
||||
<Typography.Text className="text-gray">
|
||||
<Typography.Text
|
||||
className="text-gray"
|
||||
data-testid="no-data-message">
|
||||
{t('message.no-data-available')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import { mockTopicEntityDetails } from '../mocks/TopicSummary.mock';
|
||||
import TopicSummary from './TopicSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
|
||||
);
|
||||
|
||||
jest.mock('rest/topicsAPI', () => ({
|
||||
getTopicByFqn: jest.fn().mockImplementation(() => ({ partitions: 128 })),
|
||||
}));
|
||||
|
||||
describe('TopicSummary component tests', () => {
|
||||
it('Component should render properly', async () => {
|
||||
await act(async () => {
|
||||
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
|
||||
});
|
||||
|
||||
const topicTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const partitionsLabel = screen.getByTestId('Partitions-label');
|
||||
const replicationFactorLabel = screen.getByTestId(
|
||||
'Replication Factor-label'
|
||||
);
|
||||
const retentionSizeLabel = screen.getByTestId('Retention Size-label');
|
||||
const cleanUpPoliciesLabel = screen.getByTestId('CleanUp Policies-label');
|
||||
const maxMessageSizeLabel = screen.getByTestId('Max Message Size-label');
|
||||
const partitionsValue = screen.getByTestId('Partitions-value');
|
||||
const replicationFactorValue = screen.getByTestId(
|
||||
'Replication Factor-value'
|
||||
);
|
||||
const retentionSizeValue = screen.getByTestId('Retention Size-value');
|
||||
const cleanUpPoliciesValue = screen.getByTestId('CleanUp Policies-value');
|
||||
const maxMessageSizeValue = screen.getByTestId('Max Message Size-value');
|
||||
const schemaHeader = screen.getByTestId('schema-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(topicTitle).toBeInTheDocument();
|
||||
expect(partitionsLabel).toBeInTheDocument();
|
||||
expect(replicationFactorLabel).toBeInTheDocument();
|
||||
expect(retentionSizeLabel).toBeInTheDocument();
|
||||
expect(cleanUpPoliciesLabel).toBeInTheDocument();
|
||||
expect(maxMessageSizeLabel).toBeInTheDocument();
|
||||
expect(partitionsValue).toContainHTML('128');
|
||||
expect(replicationFactorValue).toContainHTML('4');
|
||||
expect(retentionSizeValue).toContainHTML('1018.83 MB');
|
||||
expect(cleanUpPoliciesValue).toContainHTML('delete');
|
||||
expect(maxMessageSizeValue).toContainHTML('208 Bytes');
|
||||
expect(schemaHeader).toBeInTheDocument();
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('No data message should be shown in case not schemaFields are available in topic details', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TopicSummary
|
||||
entityDetails={{
|
||||
...mockTopicEntityDetails,
|
||||
messageSchema: {
|
||||
...mockTopicEntityDetails.messageSchema,
|
||||
schemaFields: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const summaryList = screen.queryByTestId('SummaryList');
|
||||
const noDataMessage = screen.queryByTestId('no-data-message');
|
||||
|
||||
expect(summaryList).toBeNull();
|
||||
expect(noDataMessage).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('In case any topic field is not present, "-" should be displayed in place of value', async () => {
|
||||
(getTopicByFqn as jest.Mock).mockImplementationOnce(() => Promise.reject());
|
||||
await act(async () => {
|
||||
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
|
||||
});
|
||||
|
||||
const partitionsValue = screen.getByTestId('Partitions-value');
|
||||
|
||||
expect(partitionsValue).toContainHTML('-');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
Dashboard,
|
||||
DashboardServiceType,
|
||||
} from '../../../../generated/entity/data/dashboard';
|
||||
|
||||
const mockDate = new Date('2023-01-03');
|
||||
|
||||
export const mockDashboardEntityDetails: Dashboard = {
|
||||
id: '2edaff89-b1d4-47b6-a081-d72f08e1def9',
|
||||
name: 'deck.gl Demo',
|
||||
displayName: 'deck.gl Demo',
|
||||
fullyQualifiedName: 'sample_superset.10',
|
||||
description: '',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627828951,
|
||||
updatedBy: 'admin',
|
||||
dashboardUrl: 'http://localhost:808/superset/dashboard/deck/',
|
||||
charts: [
|
||||
{
|
||||
id: 'eba9c260-4036-4c57-92fe-6c6e3d703bda',
|
||||
type: 'chart',
|
||||
name: '127',
|
||||
fullyQualifiedName: 'sample_superset.127',
|
||||
description: '',
|
||||
displayName: 'Are you an ethnic minority in your city?',
|
||||
deleted: false,
|
||||
href: 'http://openmetadata-server:8585/api/v1/charts/eba9c260-4036-4c57-92fe-6c6e3d703bda',
|
||||
},
|
||||
],
|
||||
href: 'http://openmetadata-server:8585/api/v1/dashboards/2edaff89-b1d4-47b6-a081-d72f08e1def9',
|
||||
followers: [],
|
||||
service: {
|
||||
id: '38ae6d66-7086-4e00-b2d6-cabd2b951993',
|
||||
type: 'dashboardService',
|
||||
name: 'sample_superset',
|
||||
fullyQualifiedName: 'sample_superset',
|
||||
deleted: false,
|
||||
href: 'http://openmetadata-server:8585/api/v1/services/dashboardServices/38ae6d66-7086-4e00-b2d6-cabd2b951993',
|
||||
},
|
||||
serviceType: DashboardServiceType.Superset,
|
||||
usageSummary: {
|
||||
dailyStats: {
|
||||
count: 0,
|
||||
percentileRank: 0,
|
||||
},
|
||||
weeklyStats: {
|
||||
count: 0,
|
||||
percentileRank: 0,
|
||||
},
|
||||
monthlyStats: {
|
||||
count: 0,
|
||||
percentileRank: 0,
|
||||
},
|
||||
date: mockDate,
|
||||
},
|
||||
deleted: false,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
export const mockFetchChartsResponse = [
|
||||
{
|
||||
id: 'eba9c260-4036-4c57-92fe-6c6e3d703bda',
|
||||
name: '127',
|
||||
displayName: 'Are you an ethnic minority in your city?',
|
||||
fullyQualifiedName: 'sample_superset.127',
|
||||
description: '',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627828742,
|
||||
updatedBy: 'admin',
|
||||
chartType: 'Other',
|
||||
chartUrl:
|
||||
'http://localhost:8088/superset/explore/?form_data=%7B%22slice_id%22%3A%20127%7D',
|
||||
href: 'http://localhost:8585/api/v1/charts/eba9c260-4036-4c57-92fe-6c6e3d703bda',
|
||||
tags: [],
|
||||
service: {
|
||||
id: '38ae6d66-7086-4e00-b2d6-cabd2b951993',
|
||||
type: 'dashboardService',
|
||||
name: 'sample_superset',
|
||||
fullyQualifiedName: 'sample_superset',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/services/dashboardServices/38ae6d66-7086-4e00-b2d6-cabd2b951993',
|
||||
},
|
||||
serviceType: 'Superset',
|
||||
deleted: false,
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
FeatureSourceDataType,
|
||||
FeatureType,
|
||||
Mlmodel,
|
||||
} from '../../../../generated/entity/data/mlmodel';
|
||||
|
||||
export const mockMlModelEntityDetails: Mlmodel = {
|
||||
id: 'e42a5d43-36fd-4636-ae89-5bcc6a61542e',
|
||||
name: 'eta_predictions',
|
||||
displayName: 'ETA Predictions',
|
||||
fullyQualifiedName: 'mlflow_svc.eta_predictions',
|
||||
description: 'ETA Predictions Model',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627829904,
|
||||
updatedBy: 'admin',
|
||||
algorithm: 'Neural Network',
|
||||
mlFeatures: [
|
||||
{
|
||||
name: 'sales',
|
||||
dataType: FeatureType.Numerical,
|
||||
description: 'Sales amount',
|
||||
fullyQualifiedName: 'mlflow_svc.eta_predictions.sales',
|
||||
featureSources: [
|
||||
{
|
||||
name: 'gross_sales',
|
||||
dataType: FeatureSourceDataType.Integer,
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify.fact_sale.gross_sales',
|
||||
dataSource: {
|
||||
id: '148e01f8-817a-4094-aa39-970746e3427e',
|
||||
type: 'table',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_sale',
|
||||
description:
|
||||
'The fact table captures the value of products sold or returned.',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/148e01f8-817a-4094-aa39-970746e3427e',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'persona',
|
||||
dataType: FeatureType.Categorical,
|
||||
description: 'type of buyer',
|
||||
fullyQualifiedName: 'mlflow_svc.eta_predictions.persona',
|
||||
featureSources: [
|
||||
{
|
||||
name: 'membership',
|
||||
dataType: FeatureSourceDataType.String,
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify.raw_customer.membership',
|
||||
dataSource: {
|
||||
id: 'e2e27b89-9eda-441f-afe4-3c9b780e9e15',
|
||||
type: 'table',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_customer',
|
||||
description:
|
||||
'This is a raw customers table as represented in our online DB. ',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/e2e27b89-9eda-441f-afe4-3c9b780e9e15',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
dataType: FeatureSourceDataType.String,
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify.raw_customer.platform',
|
||||
dataSource: {
|
||||
id: 'e2e27b89-9eda-441f-afe4-3c9b780e9e15',
|
||||
type: 'table',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_customer',
|
||||
description:
|
||||
'This is a raw customers table as represented in our online DB. This contains personal.',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/e2e27b89-9eda-441f-afe4-3c9b780e9e15',
|
||||
},
|
||||
},
|
||||
],
|
||||
featureAlgorithm: 'PCA',
|
||||
},
|
||||
],
|
||||
mlHyperParameters: [
|
||||
{
|
||||
name: 'regularisation',
|
||||
value: '0.5',
|
||||
},
|
||||
{
|
||||
name: 'random',
|
||||
value: 'hello',
|
||||
},
|
||||
],
|
||||
dashboard: {
|
||||
name: 'DashboardName',
|
||||
id: '4352345234534538992643452345',
|
||||
type: '',
|
||||
},
|
||||
target: 'ETA_time',
|
||||
mlStore: {
|
||||
storage: 's3://path-to-pickle',
|
||||
imageRepository: 'https://docker.hub.com/image',
|
||||
},
|
||||
service: {
|
||||
name: 'MLFlow',
|
||||
id: '43523452345345325423452345',
|
||||
type: '',
|
||||
},
|
||||
server: 'http://my-server.ai',
|
||||
tags: [],
|
||||
followers: [],
|
||||
href: 'http://openmetadata-server:8585/api/v1/mlmodels/e42a5d43-36fd-4636-ae89-5bcc6a61542e',
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const mockMlModelEntityDetails1: Mlmodel = {
|
||||
id: 'b849cc70-ceda-4f2a-8de2-022a5c7f78a6',
|
||||
name: 'forecast_sales',
|
||||
displayName: 'Sales Forecast Predictions',
|
||||
fullyQualifiedName: 'mlflow_svc.forecast_sales',
|
||||
description: 'Sales Forecast Predictions Model',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627829947,
|
||||
updatedBy: 'admin',
|
||||
algorithm: 'Time Series',
|
||||
mlFeatures: [],
|
||||
mlHyperParameters: [],
|
||||
target: '',
|
||||
server: '',
|
||||
service: {
|
||||
name: 'MLFlow',
|
||||
id: '43523452345345325423452345',
|
||||
type: '',
|
||||
},
|
||||
tags: [],
|
||||
followers: [],
|
||||
href: 'http://openmetadata-server:8585/api/v1/mlmodels/b849cc70-ceda-4f2a-8de2-022a5c7f78a6',
|
||||
deleted: false,
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Pipeline } from '../../../../generated/entity/data/pipeline';
|
||||
|
||||
export const mockPipelineEntityDetails: Pipeline = {
|
||||
id: 'b35f7a53-16a9-4ed1-9223-801c3d75674f',
|
||||
name: 'dim_address_etl',
|
||||
displayName: 'dim_address etl',
|
||||
fullyQualifiedName: 'sample_airflow.dim_address_etl',
|
||||
description: 'dim_address ETL pipeline',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627829327,
|
||||
updatedBy: 'admin',
|
||||
pipelineUrl: 'http://localhost:8080/tree?dag_id=dim_address_etl',
|
||||
tasks: [
|
||||
{
|
||||
name: 'dim_address_task',
|
||||
displayName: 'dim_address Task',
|
||||
description:
|
||||
'Airflow operator to perform ETL and generate dim_address table',
|
||||
taskUrl:
|
||||
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=dim_address_task',
|
||||
downstreamTasks: ['assert_table_exists'],
|
||||
taskType: 'PrestoOperator',
|
||||
},
|
||||
{
|
||||
name: 'assert_table_exists',
|
||||
displayName: 'Assert Table Exists',
|
||||
description: 'Assert if a table exists',
|
||||
taskUrl:
|
||||
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
|
||||
downstreamTasks: [],
|
||||
taskType: 'HiveOperator',
|
||||
},
|
||||
],
|
||||
deleted: false,
|
||||
href: 'http://openmetadata-server:8585/api/v1/pipelines/b35f7a53-16a9-4ed1-9223-801c3d75674f',
|
||||
followers: [],
|
||||
tags: [],
|
||||
service: {
|
||||
id: 'd1c5f7b4-dc61-4336-a4b1-a27e0b97d791',
|
||||
type: 'pipelineService',
|
||||
name: 'sample_airflow',
|
||||
fullyQualifiedName: 'sample_airflow',
|
||||
deleted: false,
|
||||
href: 'http://openmetadata-server:8585/api/v1/services/pipelineServices/d1c5f7b4-dc61-4336-a4b1-a27e0b97d791',
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
export const mockFormattedEntityData = [
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: 'Description for name1',
|
||||
name: 'name1',
|
||||
tags: [],
|
||||
title: 'Title1',
|
||||
type: 'ARRAY',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: 'Description for name2',
|
||||
name: 'name2',
|
||||
tags: [],
|
||||
title: 'Title2',
|
||||
type: 'OBJECT',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockFormattedEntityDataWithChildren = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: 'Description for child1',
|
||||
name: 'child1',
|
||||
tags: [],
|
||||
title: 'ChildTitle2',
|
||||
type: 'OBJECT',
|
||||
},
|
||||
],
|
||||
constraint: undefined,
|
||||
description: 'Description for name1',
|
||||
name: 'name1',
|
||||
tags: [],
|
||||
title: 'Title1',
|
||||
type: 'ARRAY',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: 'Description for name2',
|
||||
name: 'name2',
|
||||
tags: [],
|
||||
title: 'Title2',
|
||||
type: 'OBJECT',
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2023 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 React from 'react';
|
||||
import { Constraint } from '../../../../generated/entity/data/table';
|
||||
import {
|
||||
LabelType,
|
||||
State,
|
||||
TagSource,
|
||||
} from '../../../../generated/type/tagLabel';
|
||||
|
||||
export const mockEntityDetails = {
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: 'Description for shipping_address',
|
||||
name: 'shipping_address',
|
||||
tags: [],
|
||||
title: <div data-testid="title">Title</div>,
|
||||
type: 'ARRAY',
|
||||
};
|
||||
|
||||
export const mockEntityDetailsWithTagsAndAlgorithm = {
|
||||
children: [],
|
||||
algorithm: 'The Algo',
|
||||
constraint: undefined,
|
||||
description: undefined,
|
||||
name: 'shipping_address',
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PersonalData.SpecialCategory',
|
||||
labelType: LabelType.Manual,
|
||||
description: 'Test Description',
|
||||
source: TagSource.Tag,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
],
|
||||
title: <div data-testid="title">Title</div>,
|
||||
type: 'ARRAY',
|
||||
};
|
||||
|
||||
export const mockEntityDetailsWithoutDescription = {
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description: undefined,
|
||||
name: 'shipping_address',
|
||||
tags: [],
|
||||
title: <div data-testid="title">Title</div>,
|
||||
type: 'ARRAY',
|
||||
};
|
||||
|
||||
export const mockEntityDetailsWithConstraint = {
|
||||
children: [],
|
||||
constraint: Constraint.PrimaryKey,
|
||||
description: undefined,
|
||||
name: 'shipping_address',
|
||||
tags: [],
|
||||
title: <div data-testid="title">Title</div>,
|
||||
type: 'ARRAY',
|
||||
};
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
DatabaseServiceType,
|
||||
DataType,
|
||||
LabelType,
|
||||
State,
|
||||
Table,
|
||||
TagSource,
|
||||
} from '../../../../generated/entity/data/table';
|
||||
|
||||
const mockDate = new Date('2023-01-03');
|
||||
|
||||
export const mockTableEntityDetails: Table = {
|
||||
id: '8dd1f238-6ba0-46c6-a091-7db81f2a6bed',
|
||||
name: 'dim.api/client',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify."dim.api/client"',
|
||||
description:
|
||||
'This dimension table contains a row for each channel or app that your customers use to create orders. ',
|
||||
displayName: 'dim.api/client',
|
||||
version: 0.2,
|
||||
updatedAt: 1672668265493,
|
||||
updatedBy: 'admin',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/8dd1f238-6ba0-46c6-a091-7db81f2a6bed',
|
||||
columns: [
|
||||
{
|
||||
name: 'api_client_id',
|
||||
dataType: DataType.Numeric,
|
||||
dataTypeDisplay: 'numeric',
|
||||
description:
|
||||
'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.',
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify."dim.api/client".api_client_id',
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PersonalData.SpecialCategory',
|
||||
description:
|
||||
'GDPR special category data is personal information of data subjects that is especially sensitive.',
|
||||
source: TagSource.Tag,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
],
|
||||
ordinalPosition: 1,
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
dataType: DataType.Varchar,
|
||||
dataLength: 100,
|
||||
dataTypeDisplay: 'varchar',
|
||||
description:
|
||||
'Full name of the app or channel. For example, Point of Sale, Online Store.',
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify."dim.api/client".title',
|
||||
tags: [],
|
||||
ordinalPosition: 2,
|
||||
},
|
||||
],
|
||||
deleted: false,
|
||||
serviceType: DatabaseServiceType.BigQuery,
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PersonalData.SpecialCategory',
|
||||
description:
|
||||
'GDPR special category data is personal information of data subjects that is especially sensitive.',
|
||||
source: TagSource.Tag,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
],
|
||||
tableQueries: [
|
||||
{
|
||||
query:
|
||||
'select cust.customer_id, fact_order.order_id from dim_customer cust join fact_order on',
|
||||
users: [],
|
||||
vote: 1,
|
||||
checksum: 'ff727cf70d5a7a9810704532f3571b82',
|
||||
queryDate: mockDate,
|
||||
},
|
||||
{
|
||||
query:
|
||||
'select sale.sale_id, cust.customer_id, fact_order.order_ir from shopify.',
|
||||
users: [],
|
||||
vote: 1,
|
||||
checksum: 'e14e02c387dd8482d10c4ec7d3d4c69a',
|
||||
queryDate: mockDate,
|
||||
},
|
||||
],
|
||||
followers: [],
|
||||
};
|
||||
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
CleanupPolicy,
|
||||
DataTypeTopic,
|
||||
SchemaType,
|
||||
Topic,
|
||||
} from '../../../../generated/entity/data/topic';
|
||||
|
||||
export const mockTopicEntityDetails: Topic = {
|
||||
id: '67e1dbbb-054c-4833-ba28-f95d71f0826f',
|
||||
name: 'product_events',
|
||||
displayName: 'product_events',
|
||||
fullyQualifiedName: 'sample_kafka.product_events',
|
||||
description:
|
||||
'Kafka topic to capture the product events. This topic will get updates on products decription, price etc.',
|
||||
version: 0.1,
|
||||
updatedAt: 1672627828429,
|
||||
updatedBy: 'admin',
|
||||
href: 'http://openmetadata-server:8585/api/v1/topics/67e1dbbb-054c-4833-ba28-f95d71f0826f',
|
||||
deleted: false,
|
||||
service: {
|
||||
id: '5d6f73f0-1811-49c8-8d1d-7a478ffd8177',
|
||||
type: 'messagingService',
|
||||
name: 'sample_kafka',
|
||||
fullyQualifiedName: 'sample_kafka',
|
||||
deleted: false,
|
||||
href: 'http://openmetadata-server:8585/api/v1/services/messagingServices/5d6f73f0-1811-49c8-8d1d-7a478ffd8177',
|
||||
},
|
||||
messageSchema: {
|
||||
schemaText:
|
||||
'{"namespace":"openmetadata.kafka","type":"record","name":"Product","fields":[{"name":"product_id","type":"int"}]}',
|
||||
schemaType: SchemaType.Avro,
|
||||
schemaFields: [
|
||||
{
|
||||
name: 'Product',
|
||||
dataType: DataTypeTopic.Record,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product',
|
||||
tags: [],
|
||||
children: [
|
||||
{
|
||||
name: 'product_id',
|
||||
dataType: DataTypeTopic.Int,
|
||||
fullyQualifiedName:
|
||||
'sample_kafka.product_events.Product.product_id',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product.title',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
dataType: DataTypeTopic.Double,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product.price',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'sku',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product.sku',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'barcode',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product.barcode',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'shop_id',
|
||||
dataType: DataTypeTopic.Int,
|
||||
fullyQualifiedName: 'sample_kafka.product_events.Product.shop_id',
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
partitions: 0,
|
||||
cleanupPolicies: [CleanupPolicy.Delete],
|
||||
replicationFactor: 4,
|
||||
maximumMessageSize: 208,
|
||||
retentionSize: 1068320655,
|
||||
tags: [],
|
||||
followers: [],
|
||||
};
|
||||
@ -23,6 +23,7 @@ import { Thread, ThreadType } from '../../generated/entity/feed/thread';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import { SchemaType } from '../../generated/type/schema';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
@ -102,4 +103,5 @@ export interface TopicConfigObjectInterface {
|
||||
'Retention Size'?: number;
|
||||
'CleanUp Policies'?: CleanupPolicy[];
|
||||
'Max Message Size'?: number;
|
||||
'Schema Type'?: SchemaType;
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
export enum SummaryEntityType {
|
||||
COLUMN = 'column',
|
||||
CHART = 'chart',
|
||||
TASK = 'task',
|
||||
MLFEATURE = 'mlFeature',
|
||||
SCHEMAFIELD = 'schemaField',
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2023 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 { SummaryEntityType } from '../enums/EntitySummary.enum';
|
||||
import { Column } from '../generated/entity/data/table';
|
||||
import { getFormattedEntityData } from './EntitySummaryPanelUtils';
|
||||
import {
|
||||
mockEntityDataWithNesting,
|
||||
mockEntityDataWithNestingResponse,
|
||||
mockEntityDataWithoutNesting,
|
||||
mockEntityDataWithoutNestingResponse,
|
||||
mockInvalidDataResponse,
|
||||
} from './mocks/EntitySummaryPanelUtils.mock';
|
||||
|
||||
describe('EntitySummaryPanelUtils tests', () => {
|
||||
it('getFormattedEntityData should return formatted data properly for table columns data without nesting', () => {
|
||||
const resultFormattedData = getFormattedEntityData(
|
||||
SummaryEntityType.COLUMN,
|
||||
mockEntityDataWithoutNesting
|
||||
);
|
||||
|
||||
expect(resultFormattedData).toEqual(mockEntityDataWithoutNestingResponse);
|
||||
});
|
||||
|
||||
it('getFormattedEntityData should return formatted data properly for topic fields data with nesting', () => {
|
||||
const resultFormattedData = getFormattedEntityData(
|
||||
SummaryEntityType.COLUMN,
|
||||
mockEntityDataWithNesting
|
||||
);
|
||||
|
||||
expect(resultFormattedData).toEqual(mockEntityDataWithNestingResponse);
|
||||
});
|
||||
|
||||
it('getFormattedEntityData should return empty array in case entityType is given other than from type SummaryEntityType', () => {
|
||||
const resultFormattedData = getFormattedEntityData(
|
||||
'otherType' as SummaryEntityType,
|
||||
mockEntityDataWithNesting
|
||||
);
|
||||
|
||||
expect(resultFormattedData).toEqual([]);
|
||||
});
|
||||
|
||||
it('getFormattedEntityData should not throw error if entityDetails sent does not have fields present', () => {
|
||||
const resultFormattedData = getFormattedEntityData(
|
||||
SummaryEntityType.COLUMN,
|
||||
[{}] as Column[]
|
||||
);
|
||||
|
||||
expect(resultFormattedData).toEqual(mockInvalidDataResponse);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Space, Typography } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { BasicEntityInfo } from '../components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
|
||||
import { SummaryEntityType } from '../enums/EntitySummary.enum';
|
||||
import { Chart } from '../generated/entity/data/chart';
|
||||
import { MlFeature } from '../generated/entity/data/mlmodel';
|
||||
import { Task } from '../generated/entity/data/pipeline';
|
||||
import { Column } from '../generated/entity/data/table';
|
||||
import { Field } from '../generated/entity/data/topic';
|
||||
import { getEntityName } from './CommonUtils';
|
||||
import SVGIcons from './SvgUtils';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const getFormattedEntityData = (
|
||||
entityType: SummaryEntityType,
|
||||
entityInfo?: Column[] | Field[] | Chart[] | Task[] | MlFeature[]
|
||||
): BasicEntityInfo[] => {
|
||||
if (isEmpty(entityInfo)) {
|
||||
return [];
|
||||
}
|
||||
switch (entityType) {
|
||||
case SummaryEntityType.COLUMN: {
|
||||
return (entityInfo as Column[]).map((column) => ({
|
||||
name: column.name,
|
||||
title: <Text className="entity-title">{column.name}</Text>,
|
||||
type: column.dataType,
|
||||
tags: column.tags,
|
||||
description: column.description,
|
||||
constraint: column.constraint,
|
||||
children: getFormattedEntityData(
|
||||
SummaryEntityType.COLUMN,
|
||||
column.children
|
||||
),
|
||||
}));
|
||||
}
|
||||
case SummaryEntityType.CHART: {
|
||||
return (entityInfo as Chart[]).map((chart) => ({
|
||||
name: chart.name,
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{getEntityName(chart)}</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
type: chart.chartType,
|
||||
tags: chart.tags,
|
||||
description: chart.description,
|
||||
}));
|
||||
}
|
||||
case SummaryEntityType.TASK: {
|
||||
return (entityInfo as Task[]).map((task) => ({
|
||||
name: task.name,
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: task.taskUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{task.name}</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
type: task.taskType,
|
||||
tags: task.tags,
|
||||
description: task.description,
|
||||
}));
|
||||
}
|
||||
case SummaryEntityType.MLFEATURE: {
|
||||
return (entityInfo as MlFeature[]).map((feature) => ({
|
||||
algorithm: feature.featureAlgorithm,
|
||||
name: feature.name || '--',
|
||||
title: <Text className="entity-title">{feature.name}</Text>,
|
||||
type: feature.dataType,
|
||||
tags: feature.tags,
|
||||
description: feature.description,
|
||||
}));
|
||||
}
|
||||
case SummaryEntityType.SCHEMAFIELD: {
|
||||
return (entityInfo as Field[]).map((field) => ({
|
||||
name: field.name,
|
||||
title: <Text className="entity-title">{field.name}</Text>,
|
||||
type: field.dataType,
|
||||
description: field.description,
|
||||
tags: field.tags,
|
||||
children: getFormattedEntityData(
|
||||
SummaryEntityType.SCHEMAFIELD,
|
||||
field.children
|
||||
),
|
||||
}));
|
||||
}
|
||||
default: {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -89,5 +89,6 @@ export const getConfigObject = (
|
||||
'Retention Size': topicDetails.retentionSize,
|
||||
'CleanUp Policies': topicDetails.cleanupPolicies,
|
||||
'Max Message Size': topicDetails.maximumMessageSize,
|
||||
'Schema Type': topicDetails.messageSchema?.schemaType,
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import {
|
||||
Column,
|
||||
DataType,
|
||||
LabelType,
|
||||
State,
|
||||
TagSource,
|
||||
} from '../../generated/entity/data/table';
|
||||
import { DataTypeTopic, Field } from '../../generated/entity/data/topic';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const mockEntityDataWithoutNesting: Column[] = [
|
||||
{
|
||||
name: 'api_client_id',
|
||||
dataType: DataType.Numeric,
|
||||
dataTypeDisplay: 'numeric',
|
||||
description:
|
||||
'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.',
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify."dim.api/client".api_client_id',
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PersonalData.SpecialCategory',
|
||||
description:
|
||||
'GDPR special category data is personal information of data subjects that is especially sensitive.',
|
||||
source: TagSource.Tag,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
],
|
||||
ordinalPosition: 1,
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
dataType: DataType.Varchar,
|
||||
dataLength: 100,
|
||||
dataTypeDisplay: 'varchar',
|
||||
description:
|
||||
'Full name of the app or channel. For example, Point of Sale, Online Store.',
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify."dim.api/client".title',
|
||||
tags: [],
|
||||
ordinalPosition: 2,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockEntityDataWithoutNestingResponse = [
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description:
|
||||
'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.',
|
||||
name: 'api_client_id',
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PersonalData.SpecialCategory',
|
||||
description:
|
||||
'GDPR special category data is personal information of data subjects that is especially sensitive.',
|
||||
source: 'Tag',
|
||||
labelType: 'Manual',
|
||||
state: 'Confirmed',
|
||||
},
|
||||
],
|
||||
title: <Text className="entity-title">api_client_id</Text>,
|
||||
type: 'NUMERIC',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
constraint: undefined,
|
||||
description:
|
||||
'Full name of the app or channel. For example, Point of Sale, Online Store.',
|
||||
name: 'title',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">title</Text>,
|
||||
type: 'VARCHAR',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockEntityDataWithNesting: Field[] = [
|
||||
{
|
||||
name: 'Customer',
|
||||
dataType: DataTypeTopic.Record,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer',
|
||||
tags: [],
|
||||
children: [
|
||||
{
|
||||
name: 'id',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.id',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'first_name',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.first_name',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'last_name',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.last_name',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.email',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'address_line_1',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName:
|
||||
'sample_kafka.customer_events.Customer.address_line_1',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'address_line_2',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName:
|
||||
'sample_kafka.customer_events.Customer.address_line_2',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'post_code',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.post_code',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
dataType: DataTypeTopic.String,
|
||||
fullyQualifiedName: 'sample_kafka.customer_events.Customer.country',
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const mockEntityDataWithNestingResponse = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'id',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">id</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'first_name',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">first_name</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'last_name',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">last_name</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'email',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">email</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'address_line_1',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">address_line_1</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'address_line_2',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">address_line_2</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'post_code',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">post_code</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
description: undefined,
|
||||
name: 'country',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">country</Text>,
|
||||
type: 'STRING',
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
name: 'Customer',
|
||||
tags: [],
|
||||
title: <Text className="entity-title">Customer</Text>,
|
||||
type: 'RECORD',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockInvalidDataResponse = [
|
||||
{
|
||||
children: [],
|
||||
constraints: undefined,
|
||||
description: undefined,
|
||||
name: undefined,
|
||||
tags: undefined,
|
||||
title: <Text className="entity-title" />,
|
||||
type: undefined,
|
||||
},
|
||||
];
|
||||
Loading…
x
Reference in New Issue
Block a user