Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(UI): Refactor Dataset Health Status #5222

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.linkedin.datahub.graphql.generated.Dataset;
import com.linkedin.datahub.graphql.generated.Health;
import com.linkedin.datahub.graphql.generated.HealthStatus;
import com.linkedin.datahub.graphql.generated.HealthStatusType;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.graph.GraphClient;
import com.linkedin.metadata.query.filter.Condition;
Expand All @@ -35,6 +36,8 @@
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;


/**
Expand All @@ -44,34 +47,45 @@
* health status will be undefined for the Dataset.
*
*/
public class DatasetHealthResolver implements DataFetcher<CompletableFuture<Health>> {
@Slf4j
public class DatasetHealthResolver implements DataFetcher<CompletableFuture<List<Health>>> {

private static final String ASSERTS_RELATIONSHIP_NAME = "Asserts";
private static final String ASSERTION_RUN_EVENT_SUCCESS_TYPE = "SUCCESS";
private static final CachedHealth NO_HEALTH = new CachedHealth(false, null);

private final GraphClient _graphClient;
private final TimeseriesAspectService _timeseriesAspectService;
private final Config _config;

private final Cache<String, CachedHealth> _statusCache;

public DatasetHealthResolver(final GraphClient graphClient, final TimeseriesAspectService timeseriesAspectService) {
public DatasetHealthResolver(
final GraphClient graphClient,
final TimeseriesAspectService timeseriesAspectService) {
this(graphClient, timeseriesAspectService, new Config(true));

}
public DatasetHealthResolver(
final GraphClient graphClient,
final TimeseriesAspectService timeseriesAspectService,
final Config config) {
_graphClient = graphClient;
_timeseriesAspectService = timeseriesAspectService;
_statusCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
_config = config;
}

@Override
public CompletableFuture<Health> get(final DataFetchingEnvironment environment) throws Exception {
public CompletableFuture<List<Health>> get(final DataFetchingEnvironment environment) throws Exception {
final Dataset parent = environment.getSource();
return CompletableFuture.supplyAsync(() -> {
try {
final CachedHealth cachedStatus = _statusCache.get(parent.getUrn(), () -> (
computeHealthStatusForDataset(parent.getUrn(), environment.getContext())));
return cachedStatus.hasStatus ? cachedStatus.health : null;
return cachedStatus.healths;
} catch (Exception e) {
throw new RuntimeException("Failed to resolve dataset's health status.", e);
}
Expand All @@ -87,11 +101,15 @@ public CompletableFuture<Health> get(final DataFetchingEnvironment environment)
*
*/
private CachedHealth computeHealthStatusForDataset(final String datasetUrn, final QueryContext context) {
final Health result = computeAssertionHealthForDataset(datasetUrn, context);
if (result == null) {
return NO_HEALTH;
final List<Health> healthStatuses = new ArrayList<>();

if (_config.getAssertionsEnabled()) {
final Health assertionsHealth = computeAssertionHealthForDataset(datasetUrn, context);
if (assertionsHealth != null) {
healthStatuses.add(assertionsHealth);
}
}
return new CachedHealth(true, result);
return new CachedHealth(healthStatuses);
}

/**
Expand Down Expand Up @@ -132,6 +150,7 @@ private Health computeAssertionHealthForDataset(final String datasetUrn, final Q

// Finally compute & return the health.
final Health health = new Health();
health.setType(HealthStatusType.ASSERTIONS);
if (failingAssertionUrns.size() > 0) {
health.setStatus(HealthStatus.FAIL);
health.setMessage(String.format("Dataset is failing %s/%s assertions.", failingAssertionUrns.size(),
Expand Down Expand Up @@ -217,9 +236,14 @@ private List<String> resultToFailedAssertionUrns(final StringArrayArray rows, fi
return failedAssertionUrns;
}

@AllArgsConstructor
@Data
@AllArgsConstructor
public static class Config {
private Boolean assertionsEnabled;
}

@AllArgsConstructor
private static class CachedHealth {
private final boolean hasStatus;
private final Health health;
private final List<Health> healths;
}
}
21 changes: 18 additions & 3 deletions datahub-graphql-core/src/main/resources/entity.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -928,9 +928,9 @@ type Dataset implements EntityWithRelationships & Entity {
lineage(input: LineageInput!): EntityLineageResult

"""
Experimental! The resolved health status of the Dataset
Experimental! The resolved health statuses of the Dataset
"""
health: Health
health: [Health!]

"""
Schema metadata of the dataset
Expand Down Expand Up @@ -1119,7 +1119,7 @@ type VersionedDataset implements Entity {
"""
Experimental! The resolved health status of the Dataset
"""
health: Health
health: [Health!]

"""
Schema metadata of the dataset
Expand Down Expand Up @@ -8202,10 +8202,25 @@ enum HealthStatus {
FAIL
}

"""
The type of the health status
"""
enum HealthStatusType {
"""
Assertions status
"""
ASSERTIONS
}

"""
The resolved Health of an Asset
"""
type Health {
"""
An enum representing the type of health indicator
"""
type: HealthStatusType!

"""
An enum representing the resolved Health status of an Asset
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.linkedin.timeseries.GenericTable;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collections;
import java.util.List;
import org.mockito.Mockito;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -90,9 +91,10 @@ public void testGetSuccessHealthy() throws Exception {
parentDataset.setUrn(TEST_DATASET_URN);
Mockito.when(mockEnv.getSource()).thenReturn(parentDataset);

Health result = resolver.get(mockEnv).get();
List<Health> result = resolver.get(mockEnv).get();
assertNotNull(result);
assertEquals(result.getStatus(), HealthStatus.PASS);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getStatus(), HealthStatus.PASS);
}

@Test
Expand Down Expand Up @@ -129,8 +131,8 @@ public void testGetSuccessNullHealth() throws Exception {
parentDataset.setUrn(TEST_DATASET_URN);
Mockito.when(mockEnv.getSource()).thenReturn(parentDataset);

Health result = resolver.get(mockEnv).get();
assertNull(result);
List<Health> result = resolver.get(mockEnv).get();
assertEquals(result.size(), 0);

Mockito.verify(mockAspectService, Mockito.times(0)).getAggregatedStats(
Mockito.any(),
Expand Down Expand Up @@ -206,8 +208,8 @@ public void testGetSuccessUnhealthy() throws Exception {
parentDataset.setUrn(TEST_DATASET_URN);
Mockito.when(mockEnv.getSource()).thenReturn(parentDataset);

Health result = resolver.get(mockEnv).get();
assertNotNull(result);
assertEquals(result.getStatus(), HealthStatus.FAIL);
List<Health> result = resolver.get(mockEnv).get();
assertEquals(result.size(), 1);
assertEquals(result.get(0).getStatus(), HealthStatus.FAIL);
}
}
6 changes: 3 additions & 3 deletions datahub-web-react/src/Mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const dataset1 = {
container: null,
upstream: null,
downstream: null,
health: null,
health: [],
assertions: null,
deprecation: null,
testResults: null,
Expand Down Expand Up @@ -288,7 +288,7 @@ export const dataset2 = {
container: null,
upstream: null,
downstream: null,
health: null,
health: [],
assertions: null,
status: null,
deprecation: null,
Expand Down Expand Up @@ -498,7 +498,7 @@ export const dataset3 = {
container: null,
lineage: null,
relationships: null,
health: null,
health: [],
assertions: null,
status: null,
readRuns: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,13 @@ export const EntityHeader = ({ refreshBrowser, headerDropdownItems, isNameEditab
</DeprecatedContainer>
</Popover>
)}
{entityData?.health && (
{entityData?.health?.map((health) => (
<EntityHealthStatus
status={entityData?.health.status}
message={entityData?.health?.message || undefined}
type={health.type}
status={health.status}
message={health.message || undefined}
/>
)}
))}
</TitleWrapper>
<EntityCount entityCount={entityCount} />
</MainHeaderContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Tooltip } from 'antd';
import styled from 'styled-components';
import { getHealthIcon } from '../../../../../shared/health/healthUtils';
import { HealthStatus } from '../../../../../../types.generated';
import { HealthStatus, HealthStatusType } from '../../../../../../types.generated';

const StatusContainer = styled.div`
display: flex;
Expand All @@ -12,12 +12,13 @@ const StatusContainer = styled.div`
`;

type Props = {
type: HealthStatusType;
status: HealthStatus;
message?: string | undefined;
};

export const EntityHealthStatus = ({ status, message }: Props) => {
const icon = getHealthIcon(status, 18);
export const EntityHealthStatus = ({ type, status, message }: Props) => {
const icon = getHealthIcon(type, status, 18);
return (
<StatusContainer>
<Tooltip title={message}>{icon}</Tooltip>
Expand Down
2 changes: 1 addition & 1 deletion datahub-web-react/src/app/entity/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export type GenericEntityProperties = {
subTypes?: Maybe<SubTypes>;
entityCount?: number;
container?: Maybe<Container>;
health?: Maybe<Health>;
health?: Maybe<Array<Health>>;
status?: Maybe<Status>;
deprecation?: Maybe<Deprecation>;
parentContainers?: Maybe<ParentContainersResult>;
Expand Down
14 changes: 12 additions & 2 deletions datahub-web-react/src/app/shared/health/healthUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CheckOutlined, CloseOutlined, WarningOutlined } from '@ant-design/icons';
import React from 'react';
import { HealthStatus } from '../../../types.generated';
import { HealthStatus, HealthStatusType } from '../../../types.generated';

export const getHealthColor = (status: HealthStatus) => {
switch (status) {
Expand All @@ -18,7 +18,7 @@ export const getHealthColor = (status: HealthStatus) => {
}
};

export const getHealthIcon = (status: HealthStatus, fontSize: number) => {
export const getAssertionsHealthIcon = (status: HealthStatus, fontSize: number) => {
switch (status) {
case HealthStatus.Pass: {
return <CheckOutlined style={{ color: getHealthColor(status), fontSize }} />;
Expand All @@ -33,3 +33,13 @@ export const getHealthIcon = (status: HealthStatus, fontSize: number) => {
throw new Error(`Unrecognized Health Status ${status} provided`);
}
};

export const getHealthIcon = (type: HealthStatusType, status: HealthStatus, fontSize: number) => {
switch (type) {
case HealthStatusType.Assertions: {
return getAssertionsHealthIcon(status, fontSize);
}
default:
throw new Error(`Unrecognized Health Status Type ${type} provided`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ export const datasetEntity = ({
previousSchemaMetadata: null,
__typename: 'Dataset',
subTypes: null,
health: [],
};
};
1 change: 1 addition & 0 deletions datahub-web-react/src/graphql/dataset.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ fragment nonSiblingDatasetFields on Dataset {
}
}
health {
type
status
message
causes
Expand Down
2 changes: 2 additions & 0 deletions docs/how/updating-datahub.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This file documents any backwards-incompatible changes in DataHub and assists pe

### Breaking Changes

- Refactored the `health` field of the `Dataset` GraphQL Type to be of type **list of HealthStatus** (was type **HealthStatus**). See [this PR](https://github.com/datahub-project/datahub/pull/5222/files) for more details.

### Potential Downtime

### Deprecations
Expand Down