mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 02:56:10 +00:00
improvements: added unit tests for version pages (#12381)
* added unit tests for version page components for Dashboard, Pipelines, Tables and Topics * fixed entity version page test mock * added and improved unit tests for version page related components * fixed failing unit tests * moved all mock files in mock folder * Fixed test titles Worked on comments
This commit is contained in:
parent
8bac9b6ba8
commit
43c1f1e3a2
@ -18,8 +18,11 @@ import { CustomPropertyProps } from 'components/common/CustomPropertyTable/Custo
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
|
||||
import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import VersionTable from 'components/VersionTable/VersionTable.component';
|
||||
import { getVersionPathWithTab } from 'constants/constants';
|
||||
import { EntityField } from 'constants/Feeds.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
@ -42,9 +45,6 @@ import {
|
||||
getEntityVersionByField,
|
||||
getEntityVersionTags,
|
||||
} from '../../utils/EntityVersionUtils';
|
||||
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from '../Loader/Loader';
|
||||
import VersionTable from '../VersionTable/VersionTable.component';
|
||||
import { ContainerVersionProp } from './ContainerVersion.interface';
|
||||
|
||||
const ContainerVersion: React.FC<ContainerVersionProp> = ({
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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 { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import { containerVersionMockProps } from '../../mocks/ContainerVersion.mock';
|
||||
import ContainerVersion from './ContainerVersion.component';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/description/DescriptionV1', () =>
|
||||
jest.fn().mockImplementation(() => <div>DescriptionV1</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () =>
|
||||
jest.fn().mockImplementation(() => <div>EntityVersionTimeLine</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/VersionTable/VersionTable.component', () =>
|
||||
jest.fn().mockImplementation(() => <div>VersionTable</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: 'container',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ContainerVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<ContainerVersion {...containerVersionMockProps} />);
|
||||
});
|
||||
|
||||
const dataAssetsVersionHeader = screen.getByText('DataAssetsVersionHeader');
|
||||
const description = screen.getByText('DescriptionV1');
|
||||
const schemaTabLabel = screen.getByText('label.schema');
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
|
||||
expect(dataAssetsVersionHeader).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(schemaTabLabel).toBeInTheDocument();
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ContainerVersion {...containerVersionMockProps} isVersionLoading />
|
||||
);
|
||||
});
|
||||
|
||||
const loader = screen.getByText('Loader');
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.queryByText('VersionTable');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(versionTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ContainerVersion
|
||||
{...containerVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
const loader = screen.queryByText('Loader');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
|
||||
const versionTable = screen.queryByText('VersionTable');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
expect(loader).toBeNull();
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(versionTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(<ContainerVersion {...containerVersionMockProps} />);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/container/sample_data.ecommerce_db.shopify.raw_product_catalog/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ContainerVersion
|
||||
{...containerVersionMockProps}
|
||||
entityPermissions={{ ...DEFAULT_ENTITY_PERMISSION, ViewBasic: true }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
let errorPlaceHolder = screen.queryByText('ErrorPlaceHolder');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -19,7 +19,10 @@ import { CustomPropertyTable } from 'components/common/CustomPropertyTable/Custo
|
||||
import { CustomPropertyProps } from 'components/common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
|
||||
import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import { getVersionPathWithTab } from 'constants/constants';
|
||||
@ -41,9 +44,6 @@ import {
|
||||
getEntityVersionByField,
|
||||
getEntityVersionTags,
|
||||
} from '../../utils/EntityVersionUtils';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from '../Loader/Loader';
|
||||
import { DashboardVersionProp } from './DashboardVersion.interface';
|
||||
|
||||
const DashboardVersion: FC<DashboardVersionProp> = ({
|
||||
@ -240,47 +240,43 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="dashboard-version-container">
|
||||
{isVersionLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div
|
||||
className={classNames('version-data')}
|
||||
data-testid="version-data">
|
||||
<Row gutter={[0, 12]}>
|
||||
<Col span={24}>
|
||||
<DataAssetsVersionHeader
|
||||
breadcrumbLinks={slashedDashboardName}
|
||||
currentVersionData={currentVersionData}
|
||||
deleted={deleted}
|
||||
displayName={displayName}
|
||||
ownerDisplayName={ownerDisplayName}
|
||||
ownerRef={ownerRef}
|
||||
tierDisplayName={tierDisplayName}
|
||||
version={version}
|
||||
onVersionClick={backHandler}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Tabs
|
||||
data-testid="tabs"
|
||||
defaultActiveKey={tab ?? EntityTabs.DETAILS}
|
||||
items={tabItems}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)}
|
||||
{isVersionLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className={classNames('version-data')} data-testid="version-data">
|
||||
<Row gutter={[0, 12]}>
|
||||
<Col span={24}>
|
||||
<DataAssetsVersionHeader
|
||||
breadcrumbLinks={slashedDashboardName}
|
||||
currentVersionData={currentVersionData}
|
||||
deleted={deleted}
|
||||
displayName={displayName}
|
||||
ownerDisplayName={ownerDisplayName}
|
||||
ownerRef={ownerRef}
|
||||
tierDisplayName={tierDisplayName}
|
||||
version={version}
|
||||
onVersionClick={backHandler}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Tabs
|
||||
data-testid="tabs"
|
||||
defaultActiveKey={tab ?? EntityTabs.DETAILS}
|
||||
items={tabItems}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EntityVersionTimeLine
|
||||
show
|
||||
currentVersion={version}
|
||||
versionHandler={versionHandler}
|
||||
versionList={versionList}
|
||||
onBack={backHandler}
|
||||
/>
|
||||
</div>
|
||||
<EntityVersionTimeLine
|
||||
show
|
||||
currentVersion={version}
|
||||
versionHandler={versionHandler}
|
||||
versionList={versionList}
|
||||
onBack={backHandler}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,18 +11,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findByTestId, findByText, render } from '@testing-library/react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import DashboardVersion from './DashboardVersion.component';
|
||||
import { DashboardVersionProp } from './DashboardVersion.interface';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import {
|
||||
dashboardVersionProps,
|
||||
mockNoChartData,
|
||||
mockTagChangeVersion,
|
||||
} from './dashboardVersion.mock';
|
||||
} from '../../mocks/dashboardVersion.mock';
|
||||
import DashboardVersion from './DashboardVersion.component';
|
||||
import { DashboardVersionProp } from './DashboardVersion.interface';
|
||||
|
||||
jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>RichTextEditorPreviewer.component</div>);
|
||||
@ -32,54 +37,75 @@ jest.mock('components/common/description/DescriptionV1', () => {
|
||||
return jest.fn().mockImplementation(() => <div>Description.component</div>);
|
||||
});
|
||||
|
||||
jest.mock('../EntityVersionTimeLine/EntityVersionTimeLine', () => {
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>EntityVersionTimeLine.component</div>);
|
||||
});
|
||||
|
||||
jest.mock('../Loader/Loader', () => {
|
||||
jest.mock('components/Loader/Loader', () => {
|
||||
return jest.fn().mockImplementation(() => <div>Loader.component</div>);
|
||||
});
|
||||
|
||||
jest.mock('components/containers/PageLayoutV1', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }: { children: ReactNode }) => (
|
||||
<div data-testid="PageLayoutV1">{children}</div>
|
||||
));
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () => {
|
||||
return jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>);
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsViewer/tags-viewer', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsViewer</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: 'dashboard',
|
||||
}),
|
||||
Link: jest.fn().mockImplementation(() => <div>Link</div>),
|
||||
}));
|
||||
|
||||
JSON.parse = jest.fn().mockReturnValue([]);
|
||||
|
||||
describe('Test DashboardVersion page', () => {
|
||||
it('Checks if the page has all the proper components rendered', async () => {
|
||||
const { container } = render(
|
||||
<DashboardVersion {...dashboardVersionProps} />,
|
||||
{
|
||||
describe('DashboardVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<DashboardVersion {...dashboardVersionProps} />, {
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardVersionContainer = await findByTestId(
|
||||
container,
|
||||
'dashboard-version-container'
|
||||
);
|
||||
const versionData = await findByTestId(container, 'version-data');
|
||||
const schemaTable = await findByTestId(container, 'schema-table');
|
||||
const versionData = await screen.findByTestId('version-data');
|
||||
const schemaTable = await screen.findByTestId('schema-table');
|
||||
|
||||
const entityVersionTimeLine = await findByText(
|
||||
container,
|
||||
const entityVersionTimeLine = await screen.findByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const tabs = await findByTestId(container, 'tabs');
|
||||
const description = await findByText(container, 'Description.component');
|
||||
const richTextEditorPreviewer = await findByText(
|
||||
container,
|
||||
const tabs = await screen.findByTestId('tabs');
|
||||
const description = await screen.findByText('Description.component');
|
||||
const richTextEditorPreviewer = await screen.findByText(
|
||||
'RichTextEditorPreviewer.component'
|
||||
);
|
||||
|
||||
expect(dashboardVersionContainer).toBeInTheDocument();
|
||||
expect(versionData).toBeInTheDocument();
|
||||
expect(schemaTable).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
@ -88,38 +114,33 @@ describe('Test DashboardVersion page', () => {
|
||||
expect(richTextEditorPreviewer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Checks if the page has all the proper components rendered, if change version is related to tags', async () => {
|
||||
const { container } = render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
currentVersionData={
|
||||
mockTagChangeVersion as DashboardVersionProp['currentVersionData']
|
||||
it('Should render components properly if version changes are related to tags', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
currentVersionData={
|
||||
mockTagChangeVersion as DashboardVersionProp['currentVersionData']
|
||||
}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
const dashboardVersionContainer = await findByTestId(
|
||||
container,
|
||||
'dashboard-version-container'
|
||||
);
|
||||
const versionData = await findByTestId(container, 'version-data');
|
||||
const schemaTable = await findByTestId(container, 'schema-table');
|
||||
const versionData = await screen.findByTestId('version-data');
|
||||
const schemaTable = await screen.findByTestId('schema-table');
|
||||
|
||||
const entityVersionTimeLine = await findByText(
|
||||
container,
|
||||
const entityVersionTimeLine = await screen.findByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const tabs = await findByTestId(container, 'tabs');
|
||||
const description = await findByText(container, 'Description.component');
|
||||
const richTextEditorPreviewer = await findByText(
|
||||
container,
|
||||
const tabs = await screen.findByTestId('tabs');
|
||||
const description = await screen.findByText('Description.component');
|
||||
const richTextEditorPreviewer = await screen.findByText(
|
||||
'RichTextEditorPreviewer.component'
|
||||
);
|
||||
|
||||
expect(dashboardVersionContainer).toBeInTheDocument();
|
||||
expect(versionData).toBeInTheDocument();
|
||||
expect(schemaTable).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
@ -128,52 +149,109 @@ describe('Test DashboardVersion page', () => {
|
||||
expect(richTextEditorPreviewer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Checks if the page has all the proper components rendered, if the dashboard deleted is undefined', async () => {
|
||||
const { container } = render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
currentVersionData={mockNoChartData}
|
||||
deleted={undefined}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
it('Should render component properly if "deleted" field for dashboard is undefined', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
currentVersionData={mockNoChartData}
|
||||
deleted={undefined}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const dashboardVersionContainer = await findByTestId(
|
||||
container,
|
||||
'dashboard-version-container'
|
||||
);
|
||||
const versionData = await findByTestId(container, 'version-data');
|
||||
const entityVersionTimeLine = await findByText(
|
||||
container,
|
||||
const versionData = await screen.findByTestId('version-data');
|
||||
const entityVersionTimeLine = await screen.findByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const tabs = await findByTestId(container, 'tabs');
|
||||
const description = await findByText(container, 'Description.component');
|
||||
const tabs = await screen.findByTestId('tabs');
|
||||
const description = await screen.findByText('Description.component');
|
||||
|
||||
expect(dashboardVersionContainer).toBeInTheDocument();
|
||||
expect(versionData).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(tabs).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('If version is loading it should show loading component', async () => {
|
||||
const { container } = render(
|
||||
<DashboardVersion {...dashboardVersionProps} isVersionLoading />,
|
||||
{
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(<DashboardVersion {...dashboardVersionProps} isVersionLoading />, {
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardVersionContainer = await findByTestId(
|
||||
container,
|
||||
'dashboard-version-container'
|
||||
const entityVersionTimeLine = await screen.findByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const loader = await findByText(container, 'Loader.component');
|
||||
const loader = await screen.findByText('Loader.component');
|
||||
|
||||
expect(dashboardVersionContainer).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(loader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
entityPermissions={ENTITY_PERMISSIONS}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/dashboard/sample_superset.eta_predictions_performance/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<DashboardVersion
|
||||
{...dashboardVersionProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const versionData = screen.queryByTestId('version-data');
|
||||
const schemaTable = screen.queryByTestId('schema-table');
|
||||
|
||||
const tabs = screen.queryByTestId('tabs');
|
||||
const description = screen.queryByText('Description.component');
|
||||
const richTextEditorPreviewer = screen.queryByText(
|
||||
'RichTextEditorPreviewer.component'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(versionData).toBeNull();
|
||||
expect(schemaTable).toBeNull();
|
||||
expect(tabs).toBeNull();
|
||||
expect(description).toBeNull();
|
||||
expect(richTextEditorPreviewer).toBeNull();
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -361,6 +361,7 @@ export const DataAssetsHeader = ({
|
||||
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
data-testid="version-button"
|
||||
icon={<Icon component={VersionIcon} />}
|
||||
onClick={onVersionClick}>
|
||||
<Typography.Text>{version}</Typography.Text>
|
||||
|
||||
@ -86,6 +86,7 @@ function DataAssetsVersionHeader({
|
||||
<Col>
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
data-testid="version-button"
|
||||
icon={<Icon component={VersionIcon} />}
|
||||
onClick={onVersionClick}>
|
||||
<Typography.Text>{version}</Typography.Text>
|
||||
|
||||
@ -38,7 +38,7 @@ import { EntityTabs, EntityType } from 'enums/entity.enum';
|
||||
import { MlFeature, Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { TagSource } from 'generated/type/tagLabel';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import React, { FC, Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
|
||||
@ -222,130 +222,128 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
|
||||
<Col span={24}>
|
||||
{(currentVersionData as Mlmodel).mlFeatures &&
|
||||
(currentVersionData as Mlmodel).mlFeatures?.length ? (
|
||||
<Fragment>
|
||||
<Row data-testid="feature-list">
|
||||
<Col span={24}>
|
||||
<Divider className="m-y-md" />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Title level={5}>
|
||||
{t('label.feature-plural-used')}
|
||||
</Typography.Title>
|
||||
</Col>
|
||||
<Row data-testid="feature-list">
|
||||
<Col span={24}>
|
||||
<Divider className="m-y-md" />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Title level={5}>
|
||||
{t('label.feature-plural-used')}
|
||||
</Typography.Title>
|
||||
</Col>
|
||||
|
||||
{mlFeaturesData?.map((feature: MlFeature) => (
|
||||
<Col key={feature.fullyQualifiedName} span={24}>
|
||||
<Card
|
||||
bordered
|
||||
className="m-b-xlg"
|
||||
data-testid="feature-card"
|
||||
key={feature.fullyQualifiedName}>
|
||||
<Row>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Typography.Text className="font-semibold">
|
||||
{feature.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Space align="start">
|
||||
<Space>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.type')}:`}
|
||||
</Typography.Text>{' '}
|
||||
<Typography.Text>
|
||||
{feature.dataType || '--'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Divider
|
||||
className="border-gray"
|
||||
type="vertical"
|
||||
/>
|
||||
<Space>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.algorithm')}:`}
|
||||
</Typography.Text>{' '}
|
||||
<Typography.Text>
|
||||
{feature.featureAlgorithm || '--'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
{mlFeaturesData?.map((feature: MlFeature) => (
|
||||
<Col key={feature.fullyQualifiedName} span={24}>
|
||||
<Card
|
||||
bordered
|
||||
className="m-b-xlg"
|
||||
data-testid="feature-card"
|
||||
key={feature.fullyQualifiedName}>
|
||||
<Row>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Typography.Text className="font-semibold">
|
||||
{feature.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Space align="start">
|
||||
<Space>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.type')}:`}
|
||||
</Typography.Text>{' '}
|
||||
<Typography.Text>
|
||||
{feature.dataType || '--'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="130px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.glossary-term-plural')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Divider
|
||||
className="border-gray"
|
||||
type="vertical"
|
||||
/>
|
||||
<Space>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.algorithm')}:`}
|
||||
</Typography.Text>{' '}
|
||||
<Typography.Text>
|
||||
{feature.featureAlgorithm || '--'}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="130px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.glossary-term-plural')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
|
||||
<Col flex="auto">
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={
|
||||
getFilterTags(feature.tags ?? [])
|
||||
.Glossary
|
||||
}
|
||||
type="border"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={
|
||||
getFilterTags(feature.tags ?? [])
|
||||
.Glossary
|
||||
}
|
||||
type="border"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="130px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.tag-plural')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={
|
||||
getFilterTags(feature.tags ?? [])
|
||||
.Classification
|
||||
}
|
||||
type="border"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="130px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.tag-plural')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={
|
||||
getFilterTags(feature.tags ?? [])
|
||||
.Classification
|
||||
}
|
||||
type="border"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="120px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.description')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Space align="start">
|
||||
{feature.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={feature.description}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="m-b-xs" span={24}>
|
||||
<Row gutter={8} wrap={false}>
|
||||
<Col flex="120px">
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{`${t('label.description')} :`}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Space align="start">
|
||||
{feature.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={feature.description}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<SourceList feature={feature} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Fragment>
|
||||
<Col span={24}>
|
||||
<SourceList feature={feature} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
) : (
|
||||
<ErrorPlaceHolder />
|
||||
)}
|
||||
|
||||
@ -25,7 +25,6 @@ export interface MlModelVersionProp {
|
||||
owner: Mlmodel['owner'];
|
||||
tier: TagLabel;
|
||||
slashedMlModelName: TitleBreadcrumbProps['titleLinks'];
|
||||
topicFQN: string;
|
||||
versionList: EntityHistory;
|
||||
deleted?: boolean;
|
||||
backHandler: () => void;
|
||||
|
||||
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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 { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import {
|
||||
mlModelVersionMockProps,
|
||||
mockMlModelDetails,
|
||||
} from '../../mocks/MlModelVersion.mock';
|
||||
import MlModelVersion from './MlModelVersion.component';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/description/DescriptionV1', () =>
|
||||
jest.fn().mockImplementation(() => <div>DescriptionV1</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () =>
|
||||
jest.fn().mockImplementation(() => <div>EntityVersionTimeLine</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/VersionTable/VersionTable.component', () =>
|
||||
jest.fn().mockImplementation(() => <div>VersionTable</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: 'container',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('MlModelVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<MlModelVersion {...mlModelVersionMockProps} />);
|
||||
});
|
||||
|
||||
const dataAssetsVersionHeader = screen.getByText('DataAssetsVersionHeader');
|
||||
const description = screen.getByText('DescriptionV1');
|
||||
const featureTabLabel = screen.getByText('label.feature-plural');
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
|
||||
expect(dataAssetsVersionHeader).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(featureTabLabel).toBeInTheDocument();
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(<MlModelVersion {...mlModelVersionMockProps} isVersionLoading />);
|
||||
});
|
||||
|
||||
const loader = screen.getByText('Loader');
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const featureTabLabel = screen.queryByText('label.feature-plural');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(featureTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MlModelVersion
|
||||
{...mlModelVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
const loader = screen.queryByText('Loader');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const featureTabLabel = screen.queryByText('label.feature-plural');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
expect(loader).toBeNull();
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(featureTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
});
|
||||
|
||||
it('No data placeholder should be displayed if no mlFeatures are present in the mlModel data', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MlModelVersion
|
||||
{...mlModelVersionMockProps}
|
||||
currentVersionData={{ ...mockMlModelDetails, mlFeatures: undefined }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const featureTabLabel = screen.getByText('label.feature-plural');
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(featureTabLabel).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(<MlModelVersion {...mlModelVersionMockProps} />);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/mlmodel/mlflow_svc.eta_predictions/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MlModelVersion
|
||||
{...mlModelVersionMockProps}
|
||||
entityPermissions={{ ...DEFAULT_ENTITY_PERMISSION, ViewBasic: true }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
let errorPlaceHolder = screen.queryByText('ErrorPlaceHolder');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -19,7 +19,10 @@ import { CustomPropertyTable } from 'components/common/CustomPropertyTable/Custo
|
||||
import { CustomPropertyProps } from 'components/common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
|
||||
import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
|
||||
@ -55,9 +58,6 @@ import {
|
||||
getTextDiff,
|
||||
} from '../../utils/EntityVersionUtils';
|
||||
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from '../Loader/Loader';
|
||||
import { PipelineVersionProp } from './PipelineVersion.interface';
|
||||
|
||||
const PipelineVersion: FC<PipelineVersionProp> = ({
|
||||
|
||||
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 { EntityTabs } from 'enums/entity.enum';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import {
|
||||
mockColumnDiffPipelineVersionMockProps,
|
||||
pipelineVersionMockProps,
|
||||
} from '../../mocks/PipelineVersion.mock';
|
||||
import PipelineVersion from './PipelineVersion.component';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () =>
|
||||
jest.fn().mockImplementation(() => <div>RichTextEditorPreviewer</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsViewer/tags-viewer', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsViewer</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/description/DescriptionV1', () =>
|
||||
jest.fn().mockImplementation(() => <div>DescriptionV1</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () =>
|
||||
jest.fn().mockImplementation(() => <div>EntityVersionTimeLine</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: EntityTabs.PIPELINE,
|
||||
}),
|
||||
Link: jest.fn().mockImplementation(() => <div>Link</div>),
|
||||
}));
|
||||
|
||||
JSON.parse = jest.fn().mockReturnValue([]);
|
||||
|
||||
describe('PipelineVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<PipelineVersion {...mockColumnDiffPipelineVersionMockProps} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
});
|
||||
|
||||
const dataAssetsVersionHeader = screen.getByText('DataAssetsVersionHeader');
|
||||
const description = screen.getByText('DescriptionV1');
|
||||
const schemaTabLabel = screen.getByText('label.task-plural');
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const schemaTable = screen.getByTestId('schema-table');
|
||||
|
||||
expect(dataAssetsVersionHeader).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(schemaTabLabel).toBeInTheDocument();
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(schemaTable).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PipelineVersion {...pipelineVersionMockProps} isVersionLoading />
|
||||
);
|
||||
});
|
||||
|
||||
const loader = screen.getByText('Loader');
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const schemaTable = screen.queryByTestId('schema-table');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(schemaTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PipelineVersion
|
||||
{...pipelineVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
const loader = screen.queryByText('Loader');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
|
||||
const schemaTable = screen.queryByTestId('schema-table');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
expect(loader).toBeNull();
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(schemaTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(<PipelineVersion {...pipelineVersionMockProps} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/pipeline/sample_airflow.snowflake_etl/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PipelineVersion
|
||||
{...pipelineVersionMockProps}
|
||||
entityPermissions={{ ...DEFAULT_ENTITY_PERMISSION, ViewBasic: true }}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const schemaTable = screen.getByTestId('schema-table');
|
||||
let errorPlaceHolder = screen.queryByText('ErrorPlaceHolder');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(schemaTable).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -18,8 +18,11 @@ import { CustomPropertyProps } from 'components/common/CustomPropertyTable/Custo
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
|
||||
import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import VersionTable from 'components/VersionTable/VersionTable.component';
|
||||
import { getVersionPathWithTab } from 'constants/constants';
|
||||
import { EntityField } from 'constants/Feeds.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
@ -44,12 +47,9 @@ import {
|
||||
getEntityVersionByField,
|
||||
getEntityVersionTags,
|
||||
} from '../../utils/EntityVersionUtils';
|
||||
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from '../Loader/Loader';
|
||||
import VersionTable from '../VersionTable/VersionTable.component';
|
||||
import { DatasetVersionProp } from './DatasetVersion.interface';
|
||||
import { TableVersionProp } from './TableVersion.interface';
|
||||
|
||||
const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
||||
const TableVersion: React.FC<TableVersionProp> = ({
|
||||
version,
|
||||
currentVersionData,
|
||||
isVersionLoading,
|
||||
@ -62,7 +62,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
||||
backHandler,
|
||||
versionHandler,
|
||||
entityPermissions,
|
||||
}: DatasetVersionProp) => {
|
||||
}: TableVersionProp) => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const { tab } = useParams<{ tab: EntityTabs }>();
|
||||
@ -265,4 +265,4 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default DatasetVersion;
|
||||
export default TableVersion;
|
||||
@ -18,7 +18,7 @@ import { EntityHistory } from '../../generated/type/entityHistory';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
|
||||
|
||||
export interface DatasetVersionProp {
|
||||
export interface TableVersionProp {
|
||||
version: string;
|
||||
currentVersionData: VersionData;
|
||||
isVersionLoading: boolean;
|
||||
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import { tableVersionMockProps } from '../../mocks/TableVersion.mock';
|
||||
import TableVersion from './TableVersion.component';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/description/DescriptionV1', () =>
|
||||
jest.fn().mockImplementation(() => <div>DescriptionV1</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () =>
|
||||
jest.fn().mockImplementation(() => <div>EntityVersionTimeLine</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/VersionTable/VersionTable.component', () =>
|
||||
jest.fn().mockImplementation(() => <div>VersionTable</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: 'tables',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TableVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<TableVersion {...tableVersionMockProps} />);
|
||||
});
|
||||
|
||||
const dataAssetsVersionHeader = screen.getByText('DataAssetsVersionHeader');
|
||||
const description = screen.getByText('DescriptionV1');
|
||||
const schemaTabLabel = screen.getByText('label.schema');
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
|
||||
expect(dataAssetsVersionHeader).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(schemaTabLabel).toBeInTheDocument();
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(<TableVersion {...tableVersionMockProps} isVersionLoading />);
|
||||
});
|
||||
|
||||
const loader = screen.getByText('Loader');
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.queryByText('VersionTable');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(versionTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TableVersion
|
||||
{...tableVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
const loader = screen.queryByText('Loader');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
|
||||
const versionTable = screen.queryByText('VersionTable');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
expect(loader).toBeNull();
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(versionTable).toBeNull();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(<TableVersion {...tableVersionMockProps} />);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/table/sample_data.ecommerce_db.shopify.raw_product_catalog/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TableVersion
|
||||
{...tableVersionMockProps}
|
||||
entityPermissions={{ ...DEFAULT_ENTITY_PERMISSION, ViewBasic: true }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const versionTable = screen.getByText('VersionTable');
|
||||
let errorPlaceHolder = screen.queryByText('ErrorPlaceHolder');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(versionTable).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -18,6 +18,8 @@ import { CustomPropertyProps } from 'components/common/CustomPropertyTable/Custo
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
|
||||
import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import TopicSchemaFields from 'components/TopicDetails/TopicSchema/TopicSchema';
|
||||
@ -37,8 +39,6 @@ import {
|
||||
getEntityVersionTags,
|
||||
getUpdatedMessageSchema,
|
||||
} from '../../utils/EntityVersionUtils';
|
||||
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
|
||||
import Loader from '../Loader/Loader';
|
||||
import { TopicVersionProp } from './TopicVersion.interface';
|
||||
|
||||
const TopicVersion: FC<TopicVersionProp> = ({
|
||||
|
||||
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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 { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import React from 'react';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import { topicVersionMockProps } from '../../mocks/TopicVersion.mock';
|
||||
import TopicVersion from './TopicVersion.component';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader',
|
||||
() => jest.fn().mockImplementation(() => <div>DataAssetsVersionHeader</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TabsLabel/TabsLabel.component', () =>
|
||||
jest.fn().mockImplementation(({ name }) => <div>{name}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
|
||||
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
CustomPropertyTable: jest
|
||||
.fn()
|
||||
.mockImplementation(() => <div>CustomPropertyTable</div>),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/description/DescriptionV1', () =>
|
||||
jest.fn().mockImplementation(() => <div>DescriptionV1</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/EntityVersionTimeLine/EntityVersionTimeLine', () =>
|
||||
jest.fn().mockImplementation(() => <div>EntityVersionTimeLine</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/TopicDetails/TopicSchema/TopicSchema', () =>
|
||||
jest.fn().mockImplementation(() => <div>TopicSchema</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockReturnValue({
|
||||
tab: 'topics',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TopicVersion tests', () => {
|
||||
it('Should render component properly if not loading', async () => {
|
||||
await act(async () => {
|
||||
render(<TopicVersion {...topicVersionMockProps} />);
|
||||
});
|
||||
|
||||
const dataAssetsVersionHeader = screen.getByText('DataAssetsVersionHeader');
|
||||
const description = screen.getByText('DescriptionV1');
|
||||
const schemaTabLabel = screen.getByText('label.schema');
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const topicSchema = screen.getByText('TopicSchema');
|
||||
|
||||
expect(dataAssetsVersionHeader).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(schemaTabLabel).toBeInTheDocument();
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(topicSchema).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display Loader if isVersionLoading is true', async () => {
|
||||
await act(async () => {
|
||||
render(<TopicVersion {...topicVersionMockProps} isVersionLoading />);
|
||||
});
|
||||
|
||||
const loader = screen.getByText('Loader');
|
||||
const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const topicSchema = screen.queryByText('TopicSchema');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
expect(entityVersionTimeLine).toBeInTheDocument();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(topicSchema).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder if no viewing permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TopicVersion
|
||||
{...topicVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
const loader = screen.queryByText('Loader');
|
||||
const dataAssetsVersionHeader = screen.queryByText(
|
||||
'DataAssetsVersionHeader'
|
||||
);
|
||||
const schemaTabLabel = screen.queryByText('label.schema');
|
||||
const customPropertyTabLabel = screen.queryByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
|
||||
const topicSchema = screen.queryByText('TopicSchema');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
expect(loader).toBeNull();
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(dataAssetsVersionHeader).toBeNull();
|
||||
expect(schemaTabLabel).toBeNull();
|
||||
expect(customPropertyTabLabel).toBeNull();
|
||||
expect(topicSchema).toBeNull();
|
||||
});
|
||||
|
||||
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TopicVersion
|
||||
{...topicVersionMockProps}
|
||||
entityPermissions={{ ...DEFAULT_ENTITY_PERMISSION, ViewBasic: true }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
const topicSchema = screen.getByText('TopicSchema');
|
||||
let errorPlaceHolder = screen.queryByText('ErrorPlaceHolder');
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
expect(topicSchema).toBeInTheDocument();
|
||||
expect(errorPlaceHolder).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should update url on click of tab', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TopicVersion
|
||||
{...topicVersionMockProps}
|
||||
entityPermissions={ENTITY_PERMISSIONS}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const customPropertyTabLabel = screen.getByText(
|
||||
'label.custom-property-plural'
|
||||
);
|
||||
|
||||
expect(customPropertyTabLabel).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(customPropertyTabLabel);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/topic/sample_kafka.sales/versions/0.3/custom_properties'
|
||||
);
|
||||
});
|
||||
|
||||
it('ErrorPlaceholder should be displayed in case of no view permissions', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TopicVersion
|
||||
{...topicVersionMockProps}
|
||||
entityPermissions={DEFAULT_ENTITY_PERMISSION}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const topicSchema = screen.queryByText('TopicSchema');
|
||||
|
||||
const description = screen.queryByText('Description.component');
|
||||
|
||||
const entityVersionTimeLine = screen.queryByText(
|
||||
'EntityVersionTimeLine.component'
|
||||
);
|
||||
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
|
||||
|
||||
expect(entityVersionTimeLine).toBeNull();
|
||||
expect(topicSchema).toBeNull();
|
||||
expect(description).toBeNull();
|
||||
expect(errorPlaceHolder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 {
|
||||
Constraint,
|
||||
DataType,
|
||||
FileFormat,
|
||||
StorageServiceType,
|
||||
} from 'generated/entity/data/container';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import {
|
||||
mockBackHandler,
|
||||
mockOwner,
|
||||
mockTier,
|
||||
mockVersionHandler,
|
||||
mockVersionList,
|
||||
} from 'mocks/VersionCommon.mock';
|
||||
import { ContainerVersionProp } from '../components/ContainerVersion/ContainerVersion.interface';
|
||||
|
||||
export const mockContainerData = {
|
||||
id: 'fa390cbe-63a3-4ebb-976b-4cc9cbe5a234',
|
||||
name: 'expenditures',
|
||||
fullyQualifiedName: 's3_storage_sample.departments.finance.expenditures',
|
||||
displayName: 'Expenditures for the current year',
|
||||
description: 'Bucket containing finance expenditures information updated',
|
||||
version: 0.2,
|
||||
updatedAt: 1689130888441,
|
||||
updatedBy: 'admin',
|
||||
service: {
|
||||
id: '5bd7b845-adcd-4eb0-964f-c1c99c77bbb6',
|
||||
type: 'storageService',
|
||||
name: 's3_storage_sample',
|
||||
fullyQualifiedName: 's3_storage_sample',
|
||||
deleted: false,
|
||||
},
|
||||
dataModel: {
|
||||
isPartitioned: false,
|
||||
columns: [
|
||||
{
|
||||
name: 'department_id',
|
||||
dataType: DataType.Numeric,
|
||||
dataTypeDisplay: 'numeric',
|
||||
description:
|
||||
'The ID of the department. This column is the primary key for this table.',
|
||||
fullyQualifiedName: 's3_storage_sample.expenditures.department_id',
|
||||
tags: [],
|
||||
constraint: Constraint.PrimaryKey,
|
||||
ordinalPosition: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
prefix: '/departments/finance/expenditures-2023',
|
||||
numberOfObjects: 10,
|
||||
size: 65536,
|
||||
fileFormats: [FileFormat.Zstd, FileFormat.Tsv],
|
||||
serviceType: StorageServiceType.S3,
|
||||
tags: [],
|
||||
changeDescription: {
|
||||
fieldsAdded: [],
|
||||
fieldsUpdated: [
|
||||
{
|
||||
name: 'description',
|
||||
oldValue: 'Bucket containing finance expenditures information',
|
||||
newValue: 'Bucket containing finance expenditures information updated',
|
||||
},
|
||||
],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.1,
|
||||
},
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const containerVersionMockProps: ContainerVersionProp = {
|
||||
version: '0.3',
|
||||
currentVersionData: mockContainerData,
|
||||
isVersionLoading: false,
|
||||
owner: mockOwner,
|
||||
tier: mockTier,
|
||||
breadCrumbList: [],
|
||||
containerFQN: 'sample_data.ecommerce_db.shopify.raw_product_catalog',
|
||||
versionList: mockVersionList,
|
||||
deleted: false,
|
||||
backHandler: mockBackHandler,
|
||||
versionHandler: mockVersionHandler,
|
||||
entityPermissions: ENTITY_PERMISSIONS,
|
||||
};
|
||||
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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,
|
||||
MlModelServiceType,
|
||||
} from 'generated/entity/data/mlmodel';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import {
|
||||
mockBackHandler,
|
||||
mockOwner,
|
||||
mockTier,
|
||||
mockVersionHandler,
|
||||
mockVersionList,
|
||||
} from 'mocks/VersionCommon.mock';
|
||||
import { MlModelVersionProp } from '../components/MlModelVersion/MlModelVersion.interface';
|
||||
|
||||
export const mockMlModelDetails = {
|
||||
id: '68b51299-dfd1-48ce-a00e-9611da93e7a3',
|
||||
name: 'eta_predictions',
|
||||
fullyQualifiedName: 'mlflow_svc.eta_predictions',
|
||||
displayName: 'ETA Predictions',
|
||||
description: 'ETA Predictions Model updated',
|
||||
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: '655888c1-6a55-4730-a0a2-f1aa287301e2',
|
||||
type: 'table',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_sale',
|
||||
description: '',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/655888c1-6a55-4730-a0a2-f1aa287301e2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'product',
|
||||
dataType: FeatureType.Numerical,
|
||||
fullyQualifiedName: 'mlflow_svc.eta_predictions.product',
|
||||
featureSources: [
|
||||
{
|
||||
name: 'gross_sales',
|
||||
dataType: FeatureSourceDataType.Integer,
|
||||
fullyQualifiedName:
|
||||
'sample_data.ecommerce_db.shopify.fact_sale.gross_sales',
|
||||
dataSource: {
|
||||
id: '655888c1-6a55-4730-a0a2-f1aa287301e2',
|
||||
type: 'table',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_sale',
|
||||
description: '',
|
||||
href: 'http://openmetadata-server:8585/api/v1/tables/655888c1-6a55-4730-a0a2-f1aa287301e2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
mlHyperParameters: [
|
||||
{
|
||||
name: 'regularisation',
|
||||
value: '0.5',
|
||||
},
|
||||
{
|
||||
name: 'random',
|
||||
value: 'hello',
|
||||
},
|
||||
],
|
||||
target: 'ETA_time',
|
||||
dashboard: {
|
||||
id: 'c995db51-0143-4561-9ea5-8cfabf401c48',
|
||||
type: 'dashboard',
|
||||
name: 'eta_predictions_performance',
|
||||
fullyQualifiedName: 'sample_superset.eta_predictions_performance',
|
||||
description: '',
|
||||
displayName: 'ETA Predictions Performance',
|
||||
deleted: false,
|
||||
},
|
||||
mlStore: {
|
||||
storage: 's3://path-to-pickle',
|
||||
imageRepository: 'https://docker.hub.com/image',
|
||||
},
|
||||
server: 'http://my-server.ai',
|
||||
followers: [],
|
||||
tags: [],
|
||||
version: 0.2,
|
||||
updatedAt: 1689140045807,
|
||||
updatedBy: 'admin',
|
||||
service: {
|
||||
id: '0d546f38-8198-4444-9e84-9f203d598fe5',
|
||||
type: 'mlmodelService',
|
||||
name: 'mlflow_svc',
|
||||
fullyQualifiedName: 'mlflow_svc',
|
||||
deleted: false,
|
||||
},
|
||||
serviceType: MlModelServiceType.Mlflow,
|
||||
changeDescription: {
|
||||
fieldsAdded: [
|
||||
{
|
||||
name: 'mlFeatures',
|
||||
newValue: `[{"name":"sales","dataType":"numerical","description":"Sales amount update","fullyQualifiedName":"mlflow_svc.eta_predictions.sales",
|
||||
"featureSources":[{"name":"gross_sales","dataType":"integer","fullyQualifiedName":"sample_data.ecommerce_db.shopify.fact_sale.gross_sales",
|
||||
"dataSource":{"id":"655888c1-6a55-4730-a0a2-f1aa287301e2","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.fact_sale",
|
||||
"description":"The.","href":"http://openmetadata-server:8585/api/v1/tables/655888c1-6a55-4730-a0a2-f1aa287301e2"}}]}]`,
|
||||
},
|
||||
{
|
||||
name: 'mlFeatures',
|
||||
newValue: `[{"name":"persona","dataType":"categorical","description":"type of buyer","fullyQualifiedName":"mlflow_svc.eta_predictions.persona",
|
||||
"featureSources":[{"name":"membership","dataType":"string","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer.membership",
|
||||
"dataSource":{"id":"c37dae98-987b-4e09-b1d7-79155c83f0e4","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer",
|
||||
"description":"This","href":"http://openmetadata-server:8585/api/v1/tables/c37dae98-987b-4e09-b1d7-79155c83f0e4"}},
|
||||
{"name":"platform","dataType":"string","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer.platform",
|
||||
"dataSource":{"id":"c37dae98-987b-4e09-b1d7-79155c83f0e4","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer",
|
||||
"description":"This","href":"http://openmetadata-server:8585/api/v1/tables/c37dae98-987b-4e09-b1d7-79155c83f0e4"}}],
|
||||
"featureAlgorithm":"PCA","tags":[{"tagFQN":"PII.Sensitive","source":"Classification","labelType":"Manual","state":"Confirmed"}]}]`,
|
||||
},
|
||||
],
|
||||
fieldsUpdated: [
|
||||
{
|
||||
name: 'description',
|
||||
oldValue: 'ETA Predictions Model',
|
||||
newValue: 'ETA Predictions Model updated',
|
||||
},
|
||||
],
|
||||
fieldsDeleted: [
|
||||
{
|
||||
name: 'mlFeatures',
|
||||
oldValue: `[{"name":"sales","dataType":"numerical","description":"Sales amount","fullyQualifiedName":"mlflow_svc.eta_predictions.sales",
|
||||
"featureSources":[{"name":"gross_sales","dataType":"integer","fullyQualifiedName":"sample_data.ecommerce_db.shopify.fact_sale.gross_sales",
|
||||
"dataSource":{"id":"655888c1-6a55-4730-a0a2-f1aa287301e2","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.fact_sale",
|
||||
"description":"The","href":"http://openmetadata-server:8585/api/v1/tables/655888c1-6a55-4730-a0a2-f1aa287301e2"}}]}]`,
|
||||
},
|
||||
{
|
||||
name: 'mlFeatures',
|
||||
oldValue: `[{"name":"persona","dataType":"categorical","description":"type of buyer","fullyQualifiedName":"mlflow_svc.eta_predictions.persona",
|
||||
"featureSources":[{"name":"membership","dataType":"string","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer.membership",
|
||||
"dataSource":{"id":"c37dae98-987b-4e09-b1d7-79155c83f0e4","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer",
|
||||
"description":"The","href":"http://openmetadata-server:8585/api/v1/tables/c37dae98-987b-4e09-b1d7-79155c83f0e4"}},
|
||||
{"name":"platform","dataType":"string","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer.platform",
|
||||
"dataSource":{"id":"c37dae98-987b-4e09-b1d7-79155c83f0e4","type":"table","fullyQualifiedName":"sample_data.ecommerce_db.shopify.raw_customer",
|
||||
"description":"The","href":"http://openmetadata-server:8585/api/v1/tables/c37dae98-987b-4e09-b1d7-79155c83f0e4"}}],"featureAlgorithm":"PCA"}]`,
|
||||
},
|
||||
],
|
||||
previousVersion: 0.1,
|
||||
},
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const mlModelVersionMockProps: MlModelVersionProp = {
|
||||
version: '0.3',
|
||||
currentVersionData: mockMlModelDetails,
|
||||
isVersionLoading: false,
|
||||
owner: mockOwner,
|
||||
tier: mockTier,
|
||||
slashedMlModelName: [],
|
||||
versionList: mockVersionList,
|
||||
deleted: false,
|
||||
backHandler: mockBackHandler,
|
||||
versionHandler: mockVersionHandler,
|
||||
entityPermissions: ENTITY_PERMISSIONS,
|
||||
};
|
||||
@ -10,6 +10,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
|
||||
export const PERMISSIONS = {
|
||||
alert: {
|
||||
Create: true,
|
||||
@ -389,3 +391,27 @@ export const PERMISSIONS = {
|
||||
EditCustomFields: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const ENTITY_PERMISSIONS = {
|
||||
Create: true,
|
||||
Delete: true,
|
||||
EditAll: true,
|
||||
EditCustomFields: true,
|
||||
EditDataProfile: true,
|
||||
EditDescription: true,
|
||||
EditDisplayName: true,
|
||||
EditLineage: true,
|
||||
EditOwner: true,
|
||||
EditQueries: true,
|
||||
EditSampleData: true,
|
||||
EditTags: true,
|
||||
EditTests: true,
|
||||
EditTier: true,
|
||||
ViewAll: true,
|
||||
ViewBasic: true,
|
||||
ViewDataProfile: true,
|
||||
ViewQueries: true,
|
||||
ViewSampleData: true,
|
||||
ViewTests: true,
|
||||
ViewUsage: true,
|
||||
} as OperationPermission;
|
||||
|
||||
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 { PipelineServiceType } from 'generated/entity/data/pipeline';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import {
|
||||
mockBackHandler,
|
||||
mockOwner,
|
||||
mockTier,
|
||||
mockVersionHandler,
|
||||
mockVersionList,
|
||||
} from 'mocks/VersionCommon.mock';
|
||||
import { PipelineVersionProp } from '../components/PipelineVersion/PipelineVersion.interface';
|
||||
|
||||
const mockDescriptionChangeDiff = {
|
||||
fieldsAdded: [],
|
||||
fieldsUpdated: [
|
||||
{
|
||||
name: 'tasks.snowflake_task.description',
|
||||
oldValue: 'Airflow operator to perform ETL on snowflake tables',
|
||||
newValue: 'Airflow operator to perform ETL on snowflake tables open',
|
||||
},
|
||||
],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.1,
|
||||
};
|
||||
|
||||
const mockTagChangeDiff = {
|
||||
fieldsAdded: [
|
||||
{
|
||||
name: 'tags',
|
||||
newValue:
|
||||
'[{"tagFQN":"PersonalData.Personal","source":"Classification","labelType":"Manual","state":"Confirmed"}]',
|
||||
},
|
||||
],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.2,
|
||||
};
|
||||
|
||||
const mockColumnChangeDiff = {
|
||||
fieldsAdded: [
|
||||
{
|
||||
name: 'tasks.snowflake_task.tags',
|
||||
newValue:
|
||||
'[{"tagFQN":"PII.Sensitive","source":"Classification","labelType":"Manual","state":"Confirmed"}]',
|
||||
},
|
||||
],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.3,
|
||||
};
|
||||
|
||||
export const mockPipelineData = {
|
||||
id: 'ea7d9b49-ef81-44a5-ac0d-cbe3d66e0a52',
|
||||
name: 'snowflake_etl',
|
||||
displayName: 'Snowflake ETL',
|
||||
fullyQualifiedName: 'sample_airflow.snowflake_etl',
|
||||
description: 'Snowflake ETL pipeline',
|
||||
version: 0.2,
|
||||
updatedAt: 1688625607758,
|
||||
updatedBy: 'admin',
|
||||
sourceUrl: 'http://localhost:8080/tree?dag_id=snowflake_etl',
|
||||
tasks: [
|
||||
{
|
||||
name: 'snowflake_task',
|
||||
displayName: 'Snowflake Task',
|
||||
fullyQualifiedName: 'sample_airflow.snowflake_etl.snowflake_task',
|
||||
sourceUrl:
|
||||
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
|
||||
downstreamTasks: ['assert_table_exists'],
|
||||
taskType: 'SnowflakeOperator',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
name: 'assert_table_exists',
|
||||
displayName: 'Assert Table Exists',
|
||||
fullyQualifiedName: 'sample_airflow.snowflake_etl.assert_table_exists',
|
||||
description: 'Assert if a table exists',
|
||||
sourceUrl:
|
||||
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
|
||||
downstreamTasks: [],
|
||||
taskType: 'HiveOperator',
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
followers: [],
|
||||
tags: [],
|
||||
service: {
|
||||
id: '53675b50-8121-4358-b590-8540ef70f2dd',
|
||||
type: 'pipelineService',
|
||||
name: 'sample_airflow',
|
||||
fullyQualifiedName: 'sample_airflow',
|
||||
deleted: false,
|
||||
},
|
||||
serviceType: PipelineServiceType.Airflow,
|
||||
changeDescription: mockDescriptionChangeDiff,
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const pipelineVersionMockProps: PipelineVersionProp = {
|
||||
version: '0.3',
|
||||
currentVersionData: mockPipelineData,
|
||||
isVersionLoading: false,
|
||||
owner: mockOwner,
|
||||
tier: mockTier,
|
||||
slashedPipelineName: [],
|
||||
topicFQN: 'sample_data.ecommerce_db.shopify.raw_product_catalog',
|
||||
versionList: mockVersionList,
|
||||
deleted: false,
|
||||
backHandler: mockBackHandler,
|
||||
versionHandler: mockVersionHandler,
|
||||
entityPermissions: ENTITY_PERMISSIONS,
|
||||
};
|
||||
|
||||
const mockTagDiffPipelineData = {
|
||||
...mockPipelineData,
|
||||
changeDescription: mockTagChangeDiff,
|
||||
};
|
||||
|
||||
const mockColumnDiffPipelineData = {
|
||||
...mockPipelineData,
|
||||
changeDescription: mockColumnChangeDiff,
|
||||
};
|
||||
|
||||
export const mockTagDiffPipelineVersionMockProps = {
|
||||
...pipelineVersionMockProps,
|
||||
currentVersionData: mockTagDiffPipelineData,
|
||||
};
|
||||
|
||||
export const mockColumnDiffPipelineVersionMockProps = {
|
||||
...pipelineVersionMockProps,
|
||||
currentVersionData: mockColumnDiffPipelineData,
|
||||
};
|
||||
@ -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 { DatabaseServiceType, TableType } from 'generated/entity/data/table';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import {
|
||||
mockBackHandler,
|
||||
mockOwner,
|
||||
mockTier,
|
||||
mockVersionHandler,
|
||||
mockVersionList,
|
||||
} from 'mocks/VersionCommon.mock';
|
||||
import { TableVersionProp } from '../components/TableVersion/TableVersion.interface';
|
||||
|
||||
export const mockTableData = {
|
||||
id: 'ab4f893b-c303-43d9-9375-3e620a670b02',
|
||||
name: 'raw_product_catalog',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_product_catalog',
|
||||
description:
|
||||
'This is a raw product catalog table contains the product listing, price, seller etc.. represented in our online DB. ',
|
||||
version: 0.2,
|
||||
updatedAt: 1688442727895,
|
||||
updatedBy: 'admin',
|
||||
tableType: TableType.Regular,
|
||||
columns: [],
|
||||
owner: {
|
||||
id: '38be030f-f817-4712-bc3b-ff7b9b9b805e',
|
||||
type: 'user',
|
||||
name: 'aaron_johnson0',
|
||||
fullyQualifiedName: 'aaron_johnson0',
|
||||
displayName: 'Aaron Johnson',
|
||||
deleted: false,
|
||||
},
|
||||
databaseSchema: {
|
||||
id: '3f0d9c39-0926-4028-8070-65b0c03556cb',
|
||||
type: 'databaseSchema',
|
||||
name: 'shopify',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify',
|
||||
description:
|
||||
'This **mock** database contains schema related to shopify sales and orders with related dimension tables.',
|
||||
deleted: false,
|
||||
},
|
||||
database: {
|
||||
id: 'f085e133-e184-47c8-ada5-d7e005d3153b',
|
||||
type: 'database',
|
||||
name: 'ecommerce_db',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db',
|
||||
description:
|
||||
'This **mock** database contains schemas related to shopify sales and orders with related dimension tables.',
|
||||
deleted: false,
|
||||
},
|
||||
service: {
|
||||
id: 'e61069a9-29e3-49fa-a7f4-f5227ae50b72',
|
||||
type: 'databaseService',
|
||||
name: 'sample_data',
|
||||
fullyQualifiedName: 'sample_data',
|
||||
deleted: false,
|
||||
},
|
||||
serviceType: DatabaseServiceType.BigQuery,
|
||||
tags: [],
|
||||
followers: [],
|
||||
changeDescription: {
|
||||
fieldsAdded: [
|
||||
{
|
||||
name: 'owner',
|
||||
newValue:
|
||||
'{"id":"38be030f-f817-4712-bc3b-ff7b9b9b805e","type":"user","name":"aaron_johnson0","fullyQualifiedName":"aaron_johnson0","displayName":"Aaron Johnson","deleted":false}',
|
||||
},
|
||||
],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.1,
|
||||
},
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const tableVersionMockProps: TableVersionProp = {
|
||||
version: '0.3',
|
||||
currentVersionData: mockTableData,
|
||||
isVersionLoading: false,
|
||||
owner: mockOwner,
|
||||
tier: mockTier,
|
||||
slashedTableName: [],
|
||||
datasetFQN: 'sample_data.ecommerce_db.shopify.raw_product_catalog',
|
||||
versionList: mockVersionList,
|
||||
deleted: false,
|
||||
backHandler: mockBackHandler,
|
||||
versionHandler: mockVersionHandler,
|
||||
entityPermissions: ENTITY_PERMISSIONS,
|
||||
};
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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,
|
||||
MessagingServiceType,
|
||||
} from 'generated/entity/data/topic';
|
||||
import { DataTypeTopic, SchemaType } from 'generated/type/schema';
|
||||
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
|
||||
import {
|
||||
mockBackHandler,
|
||||
mockOwner,
|
||||
mockTier,
|
||||
mockVersionHandler,
|
||||
mockVersionList,
|
||||
} from 'mocks/VersionCommon.mock';
|
||||
import { TopicVersionProp } from '../components/TopicVersion/TopicVersion.interface';
|
||||
|
||||
export const mockTopicData = {
|
||||
id: 'eccc8f43-abd8-494b-9173-6b18644d5336',
|
||||
name: 'sales',
|
||||
fullyQualifiedName: 'sample_kafka.sales',
|
||||
description: 'All sales related events gets captured in this topic',
|
||||
version: 0.1,
|
||||
updatedAt: 1688382358829,
|
||||
updatedBy: 'admin',
|
||||
service: {
|
||||
id: 'c43ab59d-d96d-40af-9a63-6afcfa23baff',
|
||||
type: 'messagingService',
|
||||
name: 'sample_kafka',
|
||||
fullyQualifiedName: 'sample_kafka',
|
||||
deleted: false,
|
||||
},
|
||||
serviceType: MessagingServiceType.Kafka,
|
||||
messageSchema: {
|
||||
schemaText:
|
||||
'{namespace: "openmetadata.kafka",type: "record",name: "Order",fields: [ { name: "sale_id", type: "int" },],}',
|
||||
schemaType: SchemaType.Avro,
|
||||
schemaFields: [
|
||||
{
|
||||
name: 'Order',
|
||||
dataType: DataTypeTopic.Record,
|
||||
fullyQualifiedName: 'sample_kafka.sales.Order',
|
||||
tags: [],
|
||||
children: [
|
||||
{
|
||||
name: 'sale_id',
|
||||
dataType: DataTypeTopic.Int,
|
||||
dataTypeDisplay: 'int',
|
||||
fullyQualifiedName: 'sample_kafka.sales.Order.sale_id',
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
partitions: 128,
|
||||
cleanupPolicies: [CleanupPolicy.Compact, CleanupPolicy.Delete],
|
||||
replicationFactor: 4,
|
||||
maximumMessageSize: 249,
|
||||
retentionSize: 1931232624,
|
||||
followers: [],
|
||||
tags: [],
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const topicVersionMockProps: TopicVersionProp = {
|
||||
version: '0.3',
|
||||
currentVersionData: mockTopicData,
|
||||
isVersionLoading: false,
|
||||
owner: mockOwner,
|
||||
tier: mockTier,
|
||||
slashedTopicName: [],
|
||||
topicFQN: 'sample_data.ecommerce_db.shopify.raw_product_catalog',
|
||||
versionList: mockVersionList,
|
||||
deleted: false,
|
||||
backHandler: mockBackHandler,
|
||||
versionHandler: mockVersionHandler,
|
||||
entityPermissions: ENTITY_PERMISSIONS,
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { LabelType, State, TagSource } from 'generated/type/tagLabel';
|
||||
|
||||
export const mockOwner = {
|
||||
id: '38be030f-f817-4712-bc3b-ff7b9b9b805e',
|
||||
type: 'user',
|
||||
name: 'aaron_johnson0',
|
||||
fullyQualifiedName: 'aaron_johnson0',
|
||||
displayName: 'Aaron Johnson',
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
export const mockTier = {
|
||||
tagFQN: 'Tier.Tier3',
|
||||
description: 'description',
|
||||
source: TagSource.Classification,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
};
|
||||
|
||||
export const mockVersionList = {
|
||||
entityType: 'table',
|
||||
versions: [''],
|
||||
};
|
||||
|
||||
export const mockBackHandler = jest.fn();
|
||||
export const mockVersionHandler = jest.fn();
|
||||
@ -13,7 +13,7 @@
|
||||
/* eslint-disable max-len */
|
||||
|
||||
import { VersionData } from 'pages/EntityVersionPage/EntityVersionPage.component';
|
||||
import { DashboardVersionProp } from './DashboardVersion.interface';
|
||||
import { DashboardVersionProp } from '../components/DashboardVersion/DashboardVersion.interface';
|
||||
|
||||
export const dashboardVersionProps = {
|
||||
version: '0.3',
|
||||
@ -15,10 +15,10 @@ import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-b
|
||||
import ContainerVersion from 'components/ContainerVersion/ContainerVersion.component';
|
||||
import DashboardVersion from 'components/DashboardVersion/DashboardVersion.component';
|
||||
import DataModelVersion from 'components/DataModelVersion/DataModelVersion.component';
|
||||
import DatasetVersion from 'components/DatasetVersion/DatasetVersion.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import MlModelVersion from 'components/MlModelVersion/MlModelVersion.component';
|
||||
import PipelineVersion from 'components/PipelineVersion/PipelineVersion.component';
|
||||
import TableVersion from 'components/TableVersion/TableVersion.component';
|
||||
import TopicVersion from 'components/TopicVersion/TopicVersion.component';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
@ -639,7 +639,7 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
switch (entityType) {
|
||||
case EntityType.TABLE: {
|
||||
return (
|
||||
<DatasetVersion
|
||||
<TableVersion
|
||||
backHandler={backHandler}
|
||||
currentVersionData={currentVersionData}
|
||||
datasetFQN={entityFQN}
|
||||
@ -723,7 +723,6 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
owner={owner}
|
||||
slashedMlModelName={slashedEntityName}
|
||||
tier={tier as TagLabel}
|
||||
topicFQN={entityFQN}
|
||||
version={version}
|
||||
versionHandler={versionHandler}
|
||||
versionList={versionList}
|
||||
|
||||
@ -27,8 +27,8 @@ jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => mockParams),
|
||||
}));
|
||||
|
||||
jest.mock('components/DatasetVersion/DatasetVersion.component', () => {
|
||||
return jest.fn().mockReturnValue(<div>DatasetVersion component</div>);
|
||||
jest.mock('components/TableVersion/TableVersion.component', () => {
|
||||
return jest.fn().mockReturnValue(<div>TableVersion component</div>);
|
||||
});
|
||||
jest.mock('components/DashboardVersion/DashboardVersion.component', () => {
|
||||
return jest.fn().mockReturnValue(<div>DashboardVersion component</div>);
|
||||
@ -98,16 +98,14 @@ jest.mock('rest/dataModelsAPI', () => ({
|
||||
}));
|
||||
|
||||
describe('Test EntityVersionPage component', () => {
|
||||
it('Checks if the DatasetVersion component renderst if respective data pass', async () => {
|
||||
it('Checks if the TableVersion component renderst if respective data pass', async () => {
|
||||
await act(async () => {
|
||||
render(<EntityVersionPage />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
const datasetVersion = await screen.findByText(
|
||||
/DatasetVersion component/i
|
||||
);
|
||||
const tableVersion = await screen.findByText(/TableVersion component/i);
|
||||
|
||||
expect(datasetVersion).toBeInTheDocument();
|
||||
expect(tableVersion).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user