mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-04 22:52:54 +00:00
feat(assertion-v2): changed Validation tab to Quality and created new Governance tab (#10935)
This commit is contained in:
parent
fdbcb684ac
commit
d85da39a86
@ -1,15 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Switch, Route, useLocation, useHistory } from 'react-router-dom';
|
||||
import { Layout } from 'antd';
|
||||
import { HomePage } from './home/HomePage';
|
||||
import { SearchRoutes } from './SearchRoutes';
|
||||
import EmbedRoutes from './EmbedRoutes';
|
||||
import { PageRoutes } from '../conf/Global';
|
||||
import { NEW_ROUTE_MAP, PageRoutes } from '../conf/Global';
|
||||
import { getRedirectUrl } from '../conf/utils';
|
||||
|
||||
/**
|
||||
* Container for all views behind an authentication wall.
|
||||
*/
|
||||
export const ProtectedRoutes = (): JSX.Element => {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname.indexOf('/Validation') !== -1) {
|
||||
history.replace(getRedirectUrl(NEW_ROUTE_MAP));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Switch>
|
||||
|
||||
@ -36,6 +36,7 @@ import AccessManagement from '../shared/tabs/Dataset/AccessManagement/AccessMana
|
||||
import { matchedFieldPathsRenderer } from '../../search/matches/matchedFieldPathsRenderer';
|
||||
import { getLastUpdatedMs } from './shared/utils';
|
||||
import { IncidentTab } from '../shared/tabs/Incident/IncidentTab';
|
||||
import { GovernanceTab } from '../shared/tabs/Dataset/Governance/GovernanceTab';
|
||||
|
||||
const SUBTYPES = {
|
||||
VIEW: 'view',
|
||||
@ -166,14 +167,22 @@ export class DatasetEntity implements Entity<Dataset> {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validation',
|
||||
name: 'Quality',
|
||||
component: ValidationsTab,
|
||||
display: {
|
||||
visible: (_, _1) => true,
|
||||
enabled: (_, dataset: GetDatasetQuery) => {
|
||||
return (
|
||||
(dataset?.dataset?.assertions?.total || 0) > 0 || dataset?.dataset?.testResults !== null
|
||||
);
|
||||
return (dataset?.dataset?.assertions?.total || 0) > 0;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Governance',
|
||||
component: GovernanceTab,
|
||||
display: {
|
||||
visible: (_, _1) => true,
|
||||
enabled: (_, dataset: GetDatasetQuery) => {
|
||||
return dataset?.dataset?.testResults !== null;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -24,7 +24,7 @@ export const EntityHealth = ({ health, baseUrl, fontSize, tooltipPlacement }: Pr
|
||||
return (
|
||||
<>
|
||||
{(unhealthy && (
|
||||
<Link to={`${baseUrl}/Validation`}>
|
||||
<Link to={`${baseUrl}/Quality`}>
|
||||
<Container>
|
||||
<EntityHealthPopover health={health} baseUrl={baseUrl} placement={tooltipPlacement}>
|
||||
{icon}
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import { useHistory, useLocation } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { FileDoneOutlined } from '@ant-design/icons';
|
||||
import { useEntityData } from '../../../EntityContext';
|
||||
import { TestResults } from './TestResults';
|
||||
import TabToolbar from '../../../components/styled/TabToolbar';
|
||||
import { ANTD_GRAY } from '../../../constants';
|
||||
import { useGetValidationsTab } from '../Validations/useGetValidationsTab';
|
||||
|
||||
const TabTitle = styled.span`
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
const TabButton = styled(Button)<{ selected: boolean }>`
|
||||
background-color: ${(props) => (props.selected && ANTD_GRAY[3]) || 'none'};
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
enum TabPaths {
|
||||
TESTS = 'Tests',
|
||||
}
|
||||
|
||||
const DEFAULT_TAB = TabPaths.TESTS;
|
||||
|
||||
/**
|
||||
* Component used for rendering the Entity Governance Tab.
|
||||
*/
|
||||
export const GovernanceTab = () => {
|
||||
const { entityData } = useEntityData();
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const passingTests = (entityData as any)?.testResults?.passing || [];
|
||||
const maybeFailingTests = (entityData as any)?.testResults?.failing || [];
|
||||
const totalTests = maybeFailingTests.length + passingTests.length;
|
||||
|
||||
const { selectedTab, basePath } = useGetValidationsTab(pathname, Object.values(TabPaths));
|
||||
|
||||
// If no tab was selected, select a default tab.
|
||||
useEffect(() => {
|
||||
if (!selectedTab) {
|
||||
// Route to the default tab.
|
||||
history.replace(`${basePath}/${DEFAULT_TAB}`);
|
||||
}
|
||||
}, [selectedTab, basePath, history]);
|
||||
|
||||
/**
|
||||
* The top-level Toolbar tabs to display.
|
||||
*/
|
||||
const tabs = [
|
||||
{
|
||||
title: (
|
||||
<>
|
||||
<FileDoneOutlined />
|
||||
<TabTitle>Tests ({totalTests})</TabTitle>
|
||||
</>
|
||||
),
|
||||
path: TabPaths.TESTS,
|
||||
disabled: totalTests === 0,
|
||||
content: <TestResults passing={passingTests} failing={maybeFailingTests} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabToolbar>
|
||||
<div>
|
||||
{tabs.map((tab) => (
|
||||
<TabButton
|
||||
type="text"
|
||||
disabled={tab.disabled}
|
||||
selected={selectedTab === tab.path}
|
||||
onClick={() => history.replace(`${basePath}/${tab.path}`)}
|
||||
>
|
||||
{tab.title}
|
||||
</TabButton>
|
||||
))}
|
||||
</div>
|
||||
</TabToolbar>
|
||||
{tabs.filter((tab) => tab.path === selectedTab).map((tab) => tab.content)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -188,7 +188,7 @@ export const DatasetAssertionsList = ({
|
||||
to={`${entityRegistry.getEntityUrl(
|
||||
EntityType.Dataset,
|
||||
entityData.urn,
|
||||
)}/Validation/Data Contract`}
|
||||
)}/Quality/Data Contract`}
|
||||
style={{ color: REDESIGN_COLORS.BLUE }}
|
||||
>
|
||||
view
|
||||
@ -200,7 +200,7 @@ export const DatasetAssertionsList = ({
|
||||
to={`${entityRegistry.getEntityUrl(
|
||||
EntityType.Dataset,
|
||||
entityData.urn,
|
||||
)}/Validation/Data Contract`}
|
||||
)}/Quality/Data Contract`}
|
||||
>
|
||||
<DataContractLogo />
|
||||
</Link>
|
||||
|
||||
@ -2,9 +2,8 @@ import React, { useEffect } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import { useHistory, useLocation } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { AuditOutlined, FileDoneOutlined, FileProtectOutlined } from '@ant-design/icons';
|
||||
import { AuditOutlined, FileProtectOutlined } from '@ant-design/icons';
|
||||
import { useEntityData } from '../../../EntityContext';
|
||||
import { TestResults } from './TestResults';
|
||||
import { Assertions } from './Assertions';
|
||||
import TabToolbar from '../../../components/styled/TabToolbar';
|
||||
import { useGetValidationsTab } from './useGetValidationsTab';
|
||||
@ -22,8 +21,7 @@ const TabButton = styled(Button)<{ selected: boolean }>`
|
||||
`;
|
||||
|
||||
enum TabPaths {
|
||||
ASSERTIONS = 'Assertions',
|
||||
TESTS = 'Tests',
|
||||
ASSERTIONS = 'List',
|
||||
DATA_CONTRACT = 'Data Contract',
|
||||
}
|
||||
|
||||
@ -39,9 +37,6 @@ export const ValidationsTab = () => {
|
||||
const appConfig = useAppConfig();
|
||||
|
||||
const totalAssertions = (entityData as any)?.assertions?.total;
|
||||
const passingTests = (entityData as any)?.testResults?.passing || [];
|
||||
const maybeFailingTests = (entityData as any)?.testResults?.failing || [];
|
||||
const totalTests = maybeFailingTests.length + passingTests.length;
|
||||
|
||||
const { selectedTab, basePath } = useGetValidationsTab(pathname, Object.values(TabPaths));
|
||||
|
||||
@ -68,17 +63,6 @@ export const ValidationsTab = () => {
|
||||
disabled: totalAssertions === 0,
|
||||
content: <Assertions />,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<>
|
||||
<FileDoneOutlined />
|
||||
<TabTitle>Tests ({totalTests})</TabTitle>
|
||||
</>
|
||||
),
|
||||
path: TabPaths.TESTS,
|
||||
disabled: totalTests === 0,
|
||||
content: <TestResults passing={passingTests} failing={maybeFailingTests} />,
|
||||
},
|
||||
];
|
||||
|
||||
if (appConfig.config.featureFlags?.dataContractsEnabled) {
|
||||
|
||||
@ -2,37 +2,37 @@ import { useGetValidationsTab } from '../useGetValidationsTab';
|
||||
|
||||
describe('useGetValidationsTab', () => {
|
||||
it('should correctly extract valid tab', () => {
|
||||
const pathname = '/dataset/urn:li:abc/Validation/Assertions';
|
||||
const tabNames = ['Assertions'];
|
||||
const pathname = '/dataset/urn:li:abc/Quality/List';
|
||||
const tabNames = ['List'];
|
||||
const res = useGetValidationsTab(pathname, tabNames);
|
||||
expect(res.selectedTab).toEqual('Assertions');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation');
|
||||
expect(res.selectedTab).toEqual('List');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
|
||||
});
|
||||
it('should extract undefined for invalid tab', () => {
|
||||
const pathname = '/dataset/urn:li:abc/Validation/Assertions';
|
||||
const pathname = '/dataset/urn:li:abc/Quality/Assertions';
|
||||
const tabNames = ['Tests'];
|
||||
const res = useGetValidationsTab(pathname, tabNames);
|
||||
expect(res.selectedTab).toBeUndefined();
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
|
||||
});
|
||||
it('should extract undefined for missing tab', () => {
|
||||
const pathname = '/dataset/urn:li:abc/Validation';
|
||||
const pathname = '/dataset/urn:li:abc/Quality';
|
||||
const tabNames = ['Tests'];
|
||||
const res = useGetValidationsTab(pathname, tabNames);
|
||||
expect(res.selectedTab).toBeUndefined();
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
|
||||
});
|
||||
it('should handle trailing slashes', () => {
|
||||
let pathname = '/dataset/urn:li:abc/Validation/Assertions/';
|
||||
let pathname = '/dataset/urn:li:abc/Quality/Assertions/';
|
||||
let tabNames = ['Assertions'];
|
||||
let res = useGetValidationsTab(pathname, tabNames);
|
||||
expect(res.selectedTab).toEqual('Assertions');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
|
||||
|
||||
pathname = '/dataset/urn:li:abc/Validation/';
|
||||
pathname = '/dataset/urn:li:abc/Quality/';
|
||||
tabNames = ['Assertions'];
|
||||
res = useGetValidationsTab(pathname, tabNames);
|
||||
expect(res.selectedTab).toBeUndefined();
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation');
|
||||
expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
|
||||
});
|
||||
});
|
||||
|
||||
@ -145,7 +145,7 @@ export const getHealthIcon = (type: HealthStatusType, status: HealthStatus, font
|
||||
export const getHealthRedirectPath = (type: HealthStatusType) => {
|
||||
switch (type) {
|
||||
case HealthStatusType.Assertions: {
|
||||
return 'Validation/Assertions';
|
||||
return 'Quality/List';
|
||||
}
|
||||
case HealthStatusType.Incidents: {
|
||||
return 'Incidents';
|
||||
|
||||
@ -41,3 +41,11 @@ export const CLIENT_AUTH_COOKIE = 'actor';
|
||||
* Name of the unique browser id cookie generated on client side
|
||||
*/
|
||||
export const BROWSER_ID_COOKIE = 'bid';
|
||||
|
||||
/** New Routes Map for redirection */
|
||||
export const NEW_ROUTE_MAP = {
|
||||
'/Validation/Assertions': '/Quality/List',
|
||||
'/Validation/Tests': '/Governance/Tests',
|
||||
'/Validation/Data%20Contract': '/Quality/Data%20Contract',
|
||||
'/Validation': '/Quality',
|
||||
};
|
||||
|
||||
26
datahub-web-react/src/conf/utils.ts
Normal file
26
datahub-web-react/src/conf/utils.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
*
|
||||
* as per the new route object
|
||||
* We are redirecting older routes to new one
|
||||
* e.g.
|
||||
* {
|
||||
'/Validation/Assertions': '/Quality/List',
|
||||
}
|
||||
* */
|
||||
|
||||
export const getRedirectUrl = (newRoutes: { [key: string]: string }) => {
|
||||
let newPathname = `${window.location.pathname}${window.location.search}`;
|
||||
if (!newRoutes) {
|
||||
return newPathname;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const path of Object.keys(newRoutes)) {
|
||||
if (newPathname.indexOf(path) !== -1) {
|
||||
newPathname = newPathname.replace(path, newRoutes[path]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return `${newPathname}${window.location.search}`;
|
||||
};
|
||||
@ -7,8 +7,8 @@ describe("dataset health test", () => {
|
||||
cy.login();
|
||||
cy.goToDataset(urn, datasetName);
|
||||
// Ensure that the “Health” badge is present and there is an active incident warning
|
||||
cy.get(`[href="/dataset/${urn}/Validation"]`).should("be.visible");
|
||||
cy.get(`[href="/dataset/${urn}/Validation"] span`).trigger("mouseover", {
|
||||
cy.get(`[href="/dataset/${urn}/Quality"]`).should("be.visible");
|
||||
cy.get(`[href="/dataset/${urn}/Quality"] span`).trigger("mouseover", {
|
||||
force: true,
|
||||
});
|
||||
cy.waitTextVisible("This asset may be unhealthy");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user