Skip to content

Commit

Permalink
refactor(ui): Misc improvements to Dataset Assertions UI (#5155)
Browse files Browse the repository at this point in the history
  • Loading branch information
jjoyce0510 authored Jun 14, 2022
1 parent 76d35dc commit 7c46437
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -122,7 +121,14 @@ private Health computeAssertionHealthForDataset(final String datasetUrn, final Q
.stream()
.map(relationship -> relationship.getEntity().toString()).collect(Collectors.toSet());

final List<String> failingAssertionUrns = getFailingAssertionUrns(datasetUrn, activeAssertionUrns);
final GenericTable assertionRunResults = getAssertionRunsTable(datasetUrn);

if (!assertionRunResults.hasRows() || assertionRunResults.getRows().size() == 0) {
// No assertion run results found. Return empty health!
return null;
}

final List<String> failingAssertionUrns = getFailingAssertionUrns(assertionRunResults, activeAssertionUrns);

// Finally compute & return the health.
final Health health = new Health();
Expand All @@ -141,20 +147,18 @@ private Health computeAssertionHealthForDataset(final String datasetUrn, final Q
return null;
}

private List<String> getFailingAssertionUrns(final String asserteeUrn, final Set<String> candidateAssertionUrns) {
// Query timeseries backend
GenericTable result = _timeseriesAspectService.getAggregatedStats(
private GenericTable getAssertionRunsTable(final String asserteeUrn) {
return _timeseriesAspectService.getAggregatedStats(
Constants.ASSERTION_ENTITY_NAME,
Constants.ASSERTION_RUN_EVENT_ASPECT_NAME,
createAssertionAggregationSpecs(),
createAssertionsFilter(asserteeUrn),
createAssertionGroupingBuckets());
if (!result.hasRows()) {
// No completed assertion runs found. Return empty list.
return Collections.emptyList();
}
}

private List<String> getFailingAssertionUrns(final GenericTable assertionRunsResult, final Set<String> candidateAssertionUrns) {
// Create the buckets based on the result
return resultToFailedAssertionUrns(result.getRows(), candidateAssertionUrns);
return resultToFailedAssertionUrns(assertionRunsResult.getRows(), candidateAssertionUrns);
}

private Filter createAssertionsFilter(final String datasetUrn) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React from 'react';
import { Tooltip } from 'antd';
import styled from 'styled-components';
import { getHealthIcon } from '../../../../../shared/health/healthUtils';
import { HealthStatus } from '../../../../../../types.generated';

const StatusContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
`;

type Props = {
status: HealthStatus;
message?: string | undefined;
Expand All @@ -11,8 +19,8 @@ type Props = {
export const EntityHealthStatus = ({ status, message }: Props) => {
const icon = getHealthIcon(status, 18);
return (
<div style={{ paddingLeft: 12, paddingRight: 12, paddingTop: 4, paddingBottom: 4 }}>
<StatusContainer>
<Tooltip title={message}>{icon}</Tooltip>
</div>
</StatusContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Popover, Typography } from 'antd';
import React from 'react';
import { Popover, Typography, Button } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import {
AssertionStdAggregation,
Expand All @@ -10,10 +10,11 @@ import {
SchemaFieldRef,
} from '../../../../../../types.generated';
import { getFormattedParameterValue } from './assertionUtils';
import { DatasetAssertionLogicModal } from './DatasetAssertionLogicModal';

const SqlText = styled.pre`
margin: 0px;
const ViewLogicButton = styled(Button)`
padding: 0px;
margin: 0px;
`;

type Props = {
Expand Down Expand Up @@ -314,7 +315,7 @@ const TOOLTIP_MAX_WIDTH = 440;
*/
export const DatasetAssertionDescription = ({ assertionInfo }: Props) => {
const { scope, aggregation, fields, operator, parameters, nativeType, nativeParameters, logic } = assertionInfo;

const [isLogicVisible, setIsLogicVisible] = useState(false);
/**
* Build a description component from a) input (aggregation, inputs) b) the operator text
*/
Expand Down Expand Up @@ -342,7 +343,19 @@ export const DatasetAssertionDescription = ({ assertionInfo }: Props) => {
</>
}
>
<div>{(logic && <SqlText>{logic}</SqlText>) || description}</div>
<div>{description}</div>
{logic && (
<div>
<ViewLogicButton onClick={() => setIsLogicVisible(true)} type="link">
View Logic
</ViewLogicButton>
</div>
)}
<DatasetAssertionLogicModal
logic={logic || 'N/A'}
visible={isLogicVisible}
onClose={() => setIsLogicVisible(false)}
/>
</Popover>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Modal, Button } from 'antd';
import React from 'react';
import Query from '../Queries/Query';

export type AssertionsSummary = {
totalAssertions: number;
totalRuns: number;
failedRuns: number;
succeededRuns: number;
};

type Props = {
logic: string;
visible: boolean;
onClose: () => void;
};

export const DatasetAssertionLogicModal = ({ logic, visible, onClose }: Props) => {
return (
<Modal visible={visible} onCancel={onClose} footer={<Button onClick={onClose}>Close</Button>}>
<Query query={logic} />
</Modal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { ANTD_GRAY } from '../../../constants';

const SummaryHeader = styled.div`
width: 100%;
height: 80px;
padding-left: 40px;
padding-top: 0px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
align-items: center;
border-bottom: 1px solid ${ANTD_GRAY[4.5]};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,28 @@ enum ViewType {
export const ValidationsTab = () => {
const { urn, entityData } = useEntityData();
const { data, refetch } = useGetDatasetAssertionsQuery({ variables: { urn } });
const [removedUrns, setRemovedUrns] = useState<string[]>([]);
/**
* Determines which view should be visible: assertions or tests.
*/
const [view, setView] = useState(ViewType.ASSERTIONS);

const assertions = (data && data.dataset?.assertions?.assertions?.map((assertion) => assertion as Assertion)) || [];
const totalAssertions = data?.dataset?.assertions?.total || 0;
const maybeTotalAssertions = data?.dataset?.assertions?.total || undefined;
const effectiveTotalAssertions = maybeTotalAssertions || 0;
const filteredAssertions = assertions.filter((assertion) => !removedUrns.includes(assertion.urn));

const passingTests = (entityData as any)?.testResults?.passing || [];
const maybeFailingTests = (entityData as any)?.testResults?.failing || [];
const totalTests = maybeFailingTests.length + passingTests.length;

useEffect(() => {
if (totalAssertions === 0) {
if (totalTests > 0 && maybeTotalAssertions === 0) {
setView(ViewType.TESTS);
} else {
setView(ViewType.ASSERTIONS);
}
}, [totalAssertions]);
}, [totalTests, maybeTotalAssertions]);

// Pre-sort the list of assertions based on which has been most recently executed.
assertions.sort(sortAssertions);
Expand All @@ -72,9 +77,13 @@ export const ValidationsTab = () => {
<>
<TabToolbar>
<div>
<Button type="text" disabled={totalAssertions === 0} onClick={() => setView(ViewType.ASSERTIONS)}>
<Button
type="text"
disabled={effectiveTotalAssertions === 0}
onClick={() => setView(ViewType.ASSERTIONS)}
>
<FileProtectOutlined />
Assertions ({totalAssertions})
Assertions ({effectiveTotalAssertions})
</Button>
<Button type="text" disabled={totalTests === 0} onClick={() => setView(ViewType.TESTS)}>
<FileDoneOutlined />
Expand All @@ -84,8 +93,17 @@ export const ValidationsTab = () => {
</TabToolbar>
{(view === ViewType.ASSERTIONS && (
<>
<DatasetAssertionsSummary summary={getAssertionsStatusSummary(assertions)} />
{entityData && <DatasetAssertionsList assertions={assertions} onDelete={() => refetch()} />}
<DatasetAssertionsSummary summary={getAssertionsStatusSummary(filteredAssertions)} />
{entityData && (
<DatasetAssertionsList
assertions={filteredAssertions}
onDelete={(assertionUrn) => {
// Hack to deal with eventual consistency.
setRemovedUrns([...removedUrns, assertionUrn]);
setTimeout(() => refetch(), 3000);
}}
/>
)}
</>
)) || <TestResults passing={passingTests} failing={maybeFailingTests} />}
</>
Expand Down

0 comments on commit 7c46437

Please sign in to comment.