forked from datahub-project/datahub
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(assertions): Adding Assertions Entity & Great Expectations BETA (d…
- Loading branch information
1 parent
9a9a5c3
commit 9f1c5a8
Showing
75 changed files
with
6,243 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
...main/java/com/linkedin/datahub/graphql/resolvers/assertion/AssertionRunEventResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package com.linkedin.datahub.graphql.resolvers.assertion; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.linkedin.datahub.graphql.QueryContext; | ||
import com.linkedin.datahub.graphql.generated.Assertion; | ||
import com.linkedin.datahub.graphql.generated.AssertionResultType; | ||
import com.linkedin.datahub.graphql.generated.AssertionRunEvent; | ||
import com.linkedin.datahub.graphql.generated.AssertionRunEventsResult; | ||
import com.linkedin.datahub.graphql.generated.AssertionRunStatus; | ||
import com.linkedin.datahub.graphql.types.dataset.mappers.AssertionRunEventMapper; | ||
import com.linkedin.entity.client.EntityClient; | ||
import com.linkedin.metadata.Constants; | ||
import com.linkedin.metadata.aspect.EnvelopedAspect; | ||
import com.linkedin.metadata.query.filter.ConjunctiveCriterion; | ||
import com.linkedin.metadata.query.filter.ConjunctiveCriterionArray; | ||
import com.linkedin.metadata.query.filter.Criterion; | ||
import com.linkedin.metadata.query.filter.CriterionArray; | ||
import com.linkedin.metadata.query.filter.Filter; | ||
import com.linkedin.r2.RemoteInvocationException; | ||
import graphql.schema.DataFetcher; | ||
import graphql.schema.DataFetchingEnvironment; | ||
import java.util.List; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.stream.Collectors; | ||
import javax.annotation.Nullable; | ||
|
||
|
||
/** | ||
* GraphQL Resolver used for fetching AssertionRunEvents. | ||
*/ | ||
public class AssertionRunEventResolver implements DataFetcher<CompletableFuture<AssertionRunEventsResult>> { | ||
|
||
private final EntityClient _client; | ||
|
||
public AssertionRunEventResolver(final EntityClient client) { | ||
_client = client; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<AssertionRunEventsResult> get(DataFetchingEnvironment environment) { | ||
return CompletableFuture.supplyAsync(() -> { | ||
|
||
final QueryContext context = environment.getContext(); | ||
|
||
final String urn = ((Assertion) environment.getSource()).getUrn(); | ||
final String maybeStatus = environment.getArgumentOrDefault("status", null); | ||
final Long maybeStartTimeMillis = environment.getArgumentOrDefault("startTimeMillis", null); | ||
final Long maybeEndTimeMillis = environment.getArgumentOrDefault("endTimeMillis", null); | ||
final Integer maybeLimit = environment.getArgumentOrDefault("limit", null); | ||
|
||
try { | ||
// Step 1: Fetch aspects from GMS | ||
List<EnvelopedAspect> aspects = _client.getTimeseriesAspectValues( | ||
urn, | ||
Constants.ASSERTION_ENTITY_NAME, | ||
Constants.ASSERTION_RUN_EVENT_ASPECT_NAME, | ||
maybeStartTimeMillis, | ||
maybeEndTimeMillis, | ||
maybeLimit, | ||
false, | ||
buildStatusFilter(maybeStatus), | ||
context.getAuthentication()); | ||
|
||
// Step 2: Bind profiles into GraphQL strong types. | ||
List<AssertionRunEvent> runEvents = aspects.stream().map(AssertionRunEventMapper::map).collect(Collectors.toList()); | ||
|
||
// Step 3: Package and return response. | ||
final AssertionRunEventsResult result = new AssertionRunEventsResult(); | ||
result.setTotal(runEvents.size()); | ||
result.setFailed(Math.toIntExact(runEvents.stream().filter(runEvent -> | ||
AssertionRunStatus.COMPLETE.equals(runEvent.getStatus()) | ||
&& runEvent.getResult() != null | ||
&& AssertionResultType.FAILURE.equals( | ||
runEvent.getResult().getType() | ||
)).count())); | ||
result.setSucceeded(Math.toIntExact(runEvents.stream().filter(runEvent -> | ||
AssertionRunStatus.COMPLETE.equals(runEvent.getStatus()) | ||
&& runEvent.getResult() != null | ||
&& AssertionResultType.SUCCESS.equals(runEvent.getResult().getType() | ||
)).count())); | ||
result.setRunEvents(runEvents); | ||
return result; | ||
} catch (RemoteInvocationException e) { | ||
throw new RuntimeException("Failed to retrieve Assertion Run Events from GMS", e); | ||
} | ||
}); | ||
} | ||
|
||
@Nullable | ||
private Filter buildStatusFilter(@Nullable final String status) { | ||
if (status == null) { | ||
return null; | ||
} | ||
return new Filter().setOr(new ConjunctiveCriterionArray(ImmutableList.of( | ||
new ConjunctiveCriterion().setAnd(new CriterionArray(ImmutableList.of( | ||
new Criterion() | ||
.setField("status") | ||
.setValue(status) | ||
))) | ||
))); | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
...c/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.linkedin.datahub.graphql.resolvers.assertion; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.linkedin.assertion.AssertionInfo; | ||
import com.linkedin.common.urn.Urn; | ||
import com.linkedin.datahub.graphql.QueryContext; | ||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils; | ||
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup; | ||
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup; | ||
import com.linkedin.datahub.graphql.exception.AuthorizationException; | ||
import com.linkedin.datahub.graphql.resolvers.AuthUtils; | ||
import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; | ||
import com.linkedin.entity.client.EntityClient; | ||
import com.linkedin.metadata.Constants; | ||
import com.linkedin.metadata.authorization.PoliciesConfig; | ||
import com.linkedin.metadata.entity.EntityService; | ||
import graphql.schema.DataFetcher; | ||
import graphql.schema.DataFetchingEnvironment; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
|
||
/** | ||
* GraphQL Resolver that deletes an Assertion. | ||
*/ | ||
public class DeleteAssertionResolver implements DataFetcher<CompletableFuture<Boolean>> { | ||
|
||
private final EntityClient _entityClient; | ||
private final EntityService _entityService; | ||
|
||
public DeleteAssertionResolver(final EntityClient entityClient, final EntityService entityService) { | ||
_entityClient = entityClient; | ||
_entityService = entityService; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Boolean> get(final DataFetchingEnvironment environment) throws Exception { | ||
final QueryContext context = environment.getContext(); | ||
final Urn assertionUrn = Urn.createFromString(environment.getArgument("urn")); | ||
return CompletableFuture.supplyAsync(() -> { | ||
|
||
// 1. check the entity exists. If not, return false. | ||
if (!_entityService.exists(assertionUrn)) { | ||
return true; | ||
} | ||
|
||
if (isAuthorizedToDeleteAssertion(context, assertionUrn)) { | ||
try { | ||
_entityClient.deleteEntity(assertionUrn, context.getAuthentication()); | ||
return true; | ||
} catch (Exception e) { | ||
throw new RuntimeException(String.format("Failed to perform delete against assertion with urn %s", assertionUrn), e); | ||
} | ||
} | ||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); | ||
}); | ||
} | ||
|
||
/** | ||
* Determine whether the current user is allowed to remove an assertion. | ||
*/ | ||
private boolean isAuthorizedToDeleteAssertion(final QueryContext context, final Urn assertionUrn) { | ||
|
||
// 2. fetch the assertion info | ||
AssertionInfo info = | ||
(AssertionInfo) MutationUtils.getAspectFromEntity( | ||
assertionUrn.toString(), Constants.ASSERTION_INFO_ASPECT_NAME, _entityService, null); | ||
|
||
if (info != null) { | ||
// 3. check whether the actor has permission to edit the assertions on the assertee | ||
final Urn asserteeUrn = getAsserteeUrnFromInfo(info); | ||
return isAuthorizedToDeleteAssertionFromAssertee(context, asserteeUrn); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private boolean isAuthorizedToDeleteAssertionFromAssertee(final QueryContext context, final Urn asserteeUrn) { | ||
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of( | ||
AuthUtils.ALL_PRIVILEGES_GROUP, | ||
new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.EDIT_ENTITY_ASSERTIONS_PRIVILEGE.getType())) | ||
)); | ||
return AuthorizationUtils.isAuthorized( | ||
context.getAuthorizer(), | ||
context.getActorUrn(), | ||
asserteeUrn.getEntityType(), | ||
asserteeUrn.toString(), | ||
orPrivilegeGroups); | ||
} | ||
|
||
private Urn getAsserteeUrnFromInfo(final AssertionInfo info) { | ||
switch (info.getType()) { | ||
case DATASET: | ||
return info.getDatasetAssertion().getDataset(); | ||
default: | ||
throw new RuntimeException(String.format("Unsupported Assertion Type %s provided", info.getType())); | ||
} | ||
} | ||
} |
Oops, something went wrong.