feat(assertion-v2): changed Validation tab to Quality and created new Governance tab (#10935)

This commit is contained in:
amit-apptware 2024-07-27 02:54:23 +05:30 committed by GitHub
parent fdbcb684ac
commit d85da39a86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 166 additions and 43 deletions

View File

@ -1,15 +1,26 @@
import React from 'react'; import React, { useEffect } from 'react';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route, useLocation, useHistory } from 'react-router-dom';
import { Layout } from 'antd'; import { Layout } from 'antd';
import { HomePage } from './home/HomePage'; import { HomePage } from './home/HomePage';
import { SearchRoutes } from './SearchRoutes'; import { SearchRoutes } from './SearchRoutes';
import EmbedRoutes from './EmbedRoutes'; 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. * Container for all views behind an authentication wall.
*/ */
export const ProtectedRoutes = (): JSX.Element => { 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 ( return (
<Layout> <Layout>
<Switch> <Switch>

View File

@ -36,6 +36,7 @@ import AccessManagement from '../shared/tabs/Dataset/AccessManagement/AccessMana
import { matchedFieldPathsRenderer } from '../../search/matches/matchedFieldPathsRenderer'; import { matchedFieldPathsRenderer } from '../../search/matches/matchedFieldPathsRenderer';
import { getLastUpdatedMs } from './shared/utils'; import { getLastUpdatedMs } from './shared/utils';
import { IncidentTab } from '../shared/tabs/Incident/IncidentTab'; import { IncidentTab } from '../shared/tabs/Incident/IncidentTab';
import { GovernanceTab } from '../shared/tabs/Dataset/Governance/GovernanceTab';
const SUBTYPES = { const SUBTYPES = {
VIEW: 'view', VIEW: 'view',
@ -166,14 +167,22 @@ export class DatasetEntity implements Entity<Dataset> {
}, },
}, },
{ {
name: 'Validation', name: 'Quality',
component: ValidationsTab, component: ValidationsTab,
display: { display: {
visible: (_, _1) => true, visible: (_, _1) => true,
enabled: (_, dataset: GetDatasetQuery) => { enabled: (_, dataset: GetDatasetQuery) => {
return ( return (dataset?.dataset?.assertions?.total || 0) > 0;
(dataset?.dataset?.assertions?.total || 0) > 0 || dataset?.dataset?.testResults !== null },
); },
},
{
name: 'Governance',
component: GovernanceTab,
display: {
visible: (_, _1) => true,
enabled: (_, dataset: GetDatasetQuery) => {
return dataset?.dataset?.testResults !== null;
}, },
}, },
}, },

View File

@ -24,7 +24,7 @@ export const EntityHealth = ({ health, baseUrl, fontSize, tooltipPlacement }: Pr
return ( return (
<> <>
{(unhealthy && ( {(unhealthy && (
<Link to={`${baseUrl}/Validation`}> <Link to={`${baseUrl}/Quality`}>
<Container> <Container>
<EntityHealthPopover health={health} baseUrl={baseUrl} placement={tooltipPlacement}> <EntityHealthPopover health={health} baseUrl={baseUrl} placement={tooltipPlacement}>
{icon} {icon}

View File

@ -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)}
</>
);
};

View File

@ -188,7 +188,7 @@ export const DatasetAssertionsList = ({
to={`${entityRegistry.getEntityUrl( to={`${entityRegistry.getEntityUrl(
EntityType.Dataset, EntityType.Dataset,
entityData.urn, entityData.urn,
)}/Validation/Data Contract`} )}/Quality/Data Contract`}
style={{ color: REDESIGN_COLORS.BLUE }} style={{ color: REDESIGN_COLORS.BLUE }}
> >
view view
@ -200,7 +200,7 @@ export const DatasetAssertionsList = ({
to={`${entityRegistry.getEntityUrl( to={`${entityRegistry.getEntityUrl(
EntityType.Dataset, EntityType.Dataset,
entityData.urn, entityData.urn,
)}/Validation/Data Contract`} )}/Quality/Data Contract`}
> >
<DataContractLogo /> <DataContractLogo />
</Link> </Link>

View File

@ -2,9 +2,8 @@ import React, { useEffect } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useHistory, useLocation } from 'react-router'; import { useHistory, useLocation } from 'react-router';
import styled from 'styled-components'; 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 { useEntityData } from '../../../EntityContext';
import { TestResults } from './TestResults';
import { Assertions } from './Assertions'; import { Assertions } from './Assertions';
import TabToolbar from '../../../components/styled/TabToolbar'; import TabToolbar from '../../../components/styled/TabToolbar';
import { useGetValidationsTab } from './useGetValidationsTab'; import { useGetValidationsTab } from './useGetValidationsTab';
@ -22,8 +21,7 @@ const TabButton = styled(Button)<{ selected: boolean }>`
`; `;
enum TabPaths { enum TabPaths {
ASSERTIONS = 'Assertions', ASSERTIONS = 'List',
TESTS = 'Tests',
DATA_CONTRACT = 'Data Contract', DATA_CONTRACT = 'Data Contract',
} }
@ -39,9 +37,6 @@ export const ValidationsTab = () => {
const appConfig = useAppConfig(); const appConfig = useAppConfig();
const totalAssertions = (entityData as any)?.assertions?.total; 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)); const { selectedTab, basePath } = useGetValidationsTab(pathname, Object.values(TabPaths));
@ -68,17 +63,6 @@ export const ValidationsTab = () => {
disabled: totalAssertions === 0, disabled: totalAssertions === 0,
content: <Assertions />, 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) { if (appConfig.config.featureFlags?.dataContractsEnabled) {

View File

@ -2,37 +2,37 @@ import { useGetValidationsTab } from '../useGetValidationsTab';
describe('useGetValidationsTab', () => { describe('useGetValidationsTab', () => {
it('should correctly extract valid tab', () => { it('should correctly extract valid tab', () => {
const pathname = '/dataset/urn:li:abc/Validation/Assertions'; const pathname = '/dataset/urn:li:abc/Quality/List';
const tabNames = ['Assertions']; const tabNames = ['List'];
const res = useGetValidationsTab(pathname, tabNames); const res = useGetValidationsTab(pathname, tabNames);
expect(res.selectedTab).toEqual('Assertions'); expect(res.selectedTab).toEqual('List');
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation'); expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
}); });
it('should extract undefined for invalid tab', () => { 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 tabNames = ['Tests'];
const res = useGetValidationsTab(pathname, tabNames); const res = useGetValidationsTab(pathname, tabNames);
expect(res.selectedTab).toBeUndefined(); 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', () => { 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 tabNames = ['Tests'];
const res = useGetValidationsTab(pathname, tabNames); const res = useGetValidationsTab(pathname, tabNames);
expect(res.selectedTab).toBeUndefined(); 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', () => { 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 tabNames = ['Assertions'];
let res = useGetValidationsTab(pathname, tabNames); let res = useGetValidationsTab(pathname, tabNames);
expect(res.selectedTab).toEqual('Assertions'); 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']; tabNames = ['Assertions'];
res = useGetValidationsTab(pathname, tabNames); res = useGetValidationsTab(pathname, tabNames);
expect(res.selectedTab).toBeUndefined(); expect(res.selectedTab).toBeUndefined();
expect(res.basePath).toEqual('/dataset/urn:li:abc/Validation'); expect(res.basePath).toEqual('/dataset/urn:li:abc/Quality');
}); });
}); });

View File

@ -145,7 +145,7 @@ export const getHealthIcon = (type: HealthStatusType, status: HealthStatus, font
export const getHealthRedirectPath = (type: HealthStatusType) => { export const getHealthRedirectPath = (type: HealthStatusType) => {
switch (type) { switch (type) {
case HealthStatusType.Assertions: { case HealthStatusType.Assertions: {
return 'Validation/Assertions'; return 'Quality/List';
} }
case HealthStatusType.Incidents: { case HealthStatusType.Incidents: {
return 'Incidents'; return 'Incidents';

View File

@ -41,3 +41,11 @@ export const CLIENT_AUTH_COOKIE = 'actor';
* Name of the unique browser id cookie generated on client side * Name of the unique browser id cookie generated on client side
*/ */
export const BROWSER_ID_COOKIE = 'bid'; 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',
};

View 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}`;
};

View File

@ -7,8 +7,8 @@ describe("dataset health test", () => {
cy.login(); cy.login();
cy.goToDataset(urn, datasetName); cy.goToDataset(urn, datasetName);
// Ensure that the “Health” badge is present and there is an active incident warning // 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}/Quality"]`).should("be.visible");
cy.get(`[href="/dataset/${urn}/Validation"] span`).trigger("mouseover", { cy.get(`[href="/dataset/${urn}/Quality"] span`).trigger("mouseover", {
force: true, force: true,
}); });
cy.waitTextVisible("This asset may be unhealthy"); cy.waitTextVisible("This asset may be unhealthy");