From 7c4643728017236ca566f132a8a2bb8fba67368c Mon Sep 17 00:00:00 2001 From: John Joyce Date: Tue, 14 Jun 2022 16:02:15 -0400 Subject: [PATCH] refactor(ui): Misc improvements to Dataset Assertions UI (#5155) --- .../dataset/DatasetHealthResolver.java | 24 ++++++++------ .../profile/header/EntityHealthStatus.tsx | 12 +++++-- .../DatasetAssertionDescription.tsx | 25 +++++++++++---- .../DatasetAssertionLogicModal.tsx | 24 ++++++++++++++ .../Validations/DatasetAssertionsSummary.tsx | 4 +-- .../Dataset/Validations/ValidationsTab.tsx | 32 +++++++++++++++---- 6 files changed, 94 insertions(+), 27 deletions(-) create mode 100644 datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionLogicModal.tsx diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetHealthResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetHealthResolver.java index 4d12bda180..9a267feb27 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetHealthResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetHealthResolver.java @@ -28,7 +28,6 @@ import com.linkedin.timeseries.GroupingBucketType; 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; @@ -122,7 +121,14 @@ public class DatasetHealthResolver implements DataFetcher relationship.getEntity().toString()).collect(Collectors.toSet()); - final List 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 failingAssertionUrns = getFailingAssertionUrns(assertionRunResults, activeAssertionUrns); // Finally compute & return the health. final Health health = new Health(); @@ -141,20 +147,18 @@ public class DatasetHealthResolver implements DataFetcher getFailingAssertionUrns(final String asserteeUrn, final Set 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 getFailingAssertionUrns(final GenericTable assertionRunsResult, final Set candidateAssertionUrns) { // Create the buckets based on the result - return resultToFailedAssertionUrns(result.getRows(), candidateAssertionUrns); + return resultToFailedAssertionUrns(assertionRunsResult.getRows(), candidateAssertionUrns); } private Filter createAssertionsFilter(final String datasetUrn) { diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/header/EntityHealthStatus.tsx b/datahub-web-react/src/app/entity/shared/containers/profile/header/EntityHealthStatus.tsx index 09801f92aa..eef4d6c522 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/header/EntityHealthStatus.tsx +++ b/datahub-web-react/src/app/entity/shared/containers/profile/header/EntityHealthStatus.tsx @@ -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; @@ -11,8 +19,8 @@ type Props = { export const EntityHealthStatus = ({ status, message }: Props) => { const icon = getHealthIcon(status, 18); return ( -
+ {icon} -
+ ); }; diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionDescription.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionDescription.tsx index c69fa8b6ed..a734e897ef 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionDescription.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionDescription.tsx @@ -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, @@ -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 = { @@ -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 */ @@ -342,7 +343,19 @@ export const DatasetAssertionDescription = ({ assertionInfo }: Props) => { } > -
{(logic && {logic}) || description}
+
{description}
+ {logic && ( +
+ setIsLogicVisible(true)} type="link"> + View Logic + +
+ )} + setIsLogicVisible(false)} + /> ); }; diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionLogicModal.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionLogicModal.tsx new file mode 100644 index 0000000000..549a10ecd2 --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionLogicModal.tsx @@ -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 ( + Close}> + + + ); +}; diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionsSummary.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionsSummary.tsx index 6a890ff9d7..e61358f3f1 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionsSummary.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/DatasetAssertionsSummary.tsx @@ -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]}; diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/ValidationsTab.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/ValidationsTab.tsx index 46ab1e9d14..34bae76dd8 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/ValidationsTab.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Validations/ValidationsTab.tsx @@ -47,23 +47,28 @@ enum ViewType { export const ValidationsTab = () => { const { urn, entityData } = useEntityData(); const { data, refetch } = useGetDatasetAssertionsQuery({ variables: { urn } }); + const [removedUrns, setRemovedUrns] = useState([]); /** * 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); @@ -72,9 +77,13 @@ export const ValidationsTab = () => { <>
-