mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-26 17:37:33 +00:00
feat(graphql): Adding resolvers for adding multiple tags, terms, and owners (#4917)
This commit is contained in:
parent
3c32ac7916
commit
259b3453a0
@ -104,8 +104,11 @@ import com.linkedin.datahub.graphql.resolvers.load.TimeSeriesAspectResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.load.UsageTypeResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddLinkResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddOwnerResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddOwnersResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTagResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTagsResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTermResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTermsResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.MutableTypeResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.RemoveLinkResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.RemoveOwnerResolver;
|
||||
@ -584,14 +587,17 @@ public class GmsGraphQLEngine {
|
||||
.dataFetcher("updateCorpUserProperties", new MutableTypeResolver<>(corpUserType))
|
||||
.dataFetcher("updateCorpGroupProperties", new MutableTypeResolver<>(corpGroupType))
|
||||
.dataFetcher("addTag", new AddTagResolver(entityService))
|
||||
.dataFetcher("addTags", new AddTagsResolver(entityService))
|
||||
.dataFetcher("removeTag", new RemoveTagResolver(entityService))
|
||||
.dataFetcher("addTerm", new AddTermResolver(entityService))
|
||||
.dataFetcher("addTerms", new AddTermsResolver(entityService))
|
||||
.dataFetcher("removeTerm", new RemoveTermResolver(entityService))
|
||||
.dataFetcher("createPolicy", new UpsertPolicyResolver(this.entityClient))
|
||||
.dataFetcher("updatePolicy", new UpsertPolicyResolver(this.entityClient))
|
||||
.dataFetcher("deletePolicy", new DeletePolicyResolver(this.entityClient))
|
||||
.dataFetcher("updateDescription", new UpdateDescriptionResolver(entityService))
|
||||
.dataFetcher("addOwner", new AddOwnerResolver(entityService))
|
||||
.dataFetcher("addOwners", new AddOwnersResolver(entityService))
|
||||
.dataFetcher("removeOwner", new RemoveOwnerResolver(entityService))
|
||||
.dataFetcher("addLink", new AddLinkResolver(entityService))
|
||||
.dataFetcher("removeLink", new RemoveLinkResolver(entityService))
|
||||
|
||||
@ -47,7 +47,7 @@ public class AddOwnerResolver implements DataFetcher<CompletableFuture<Boolean>>
|
||||
);
|
||||
try {
|
||||
|
||||
log.debug("Adding Link. input: {}", input.toString());
|
||||
log.debug("Adding Owner. input: {}", input.toString());
|
||||
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
OwnerUtils.addOwner(
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.mutate;
|
||||
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.AddOwnersInput;
|
||||
import com.linkedin.datahub.graphql.generated.OwnerInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AddOwnersResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
|
||||
private final EntityService _entityService;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
|
||||
final AddOwnersInput input = bindArgument(environment.getArgument("input"), AddOwnersInput.class);
|
||||
List<OwnerInput> owners = input.getOwners();
|
||||
Urn targetUrn = Urn.createFromString(input.getResourceUrn());
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
|
||||
if (!OwnerUtils.isAuthorizedToUpdateOwners(environment.getContext(), targetUrn)) {
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
OwnerUtils.validateAddInput(
|
||||
owners,
|
||||
targetUrn,
|
||||
_entityService
|
||||
);
|
||||
try {
|
||||
|
||||
log.debug("Adding Owners. input: {}", input.toString());
|
||||
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
OwnerUtils.addOwners(
|
||||
owners,
|
||||
targetUrn,
|
||||
actor,
|
||||
_entityService
|
||||
);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add owners to resource with input {}, {}", input.toString(), e.getMessage());
|
||||
throw new RuntimeException(String.format("Failed to add owners to resource with input %s", input.toString()), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.mutate;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
@ -51,8 +52,8 @@ public class AddTagResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
|
||||
log.info("Adding Tag. input: {}", input.toString());
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
LabelUtils.addTagToTarget(
|
||||
tagUrn,
|
||||
LabelUtils.addTagsToTarget(
|
||||
ImmutableList.of(tagUrn),
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
actor,
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.mutate;
|
||||
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.AddTagsInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AddTagsResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
|
||||
private final EntityService _entityService;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
|
||||
final AddTagsInput input = bindArgument(environment.getArgument("input"), AddTagsInput.class);
|
||||
List<Urn> tagUrns = input.getTagUrns().stream()
|
||||
.map(UrnUtils::getUrn)
|
||||
.collect(Collectors.toList());
|
||||
Urn targetUrn = Urn.createFromString(input.getResourceUrn());
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
|
||||
if (!LabelUtils.isAuthorizedToUpdateTags(environment.getContext(), targetUrn, input.getSubResource())) {
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
LabelUtils.validateInput(
|
||||
tagUrns,
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
input.getSubResourceType(),
|
||||
"tag",
|
||||
_entityService,
|
||||
false
|
||||
);
|
||||
try {
|
||||
log.info("Adding Tags. input: {}", input.toString());
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
LabelUtils.addTagsToTarget(
|
||||
tagUrns,
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
actor,
|
||||
_entityService
|
||||
);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to perform update against input {}, {}", input.toString(), e.getMessage());
|
||||
throw new RuntimeException(String.format("Failed to perform update against input %s", input.toString()), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.mutate;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
@ -44,8 +45,8 @@ public class AddTermResolver implements DataFetcher<CompletableFuture<Boolean>>
|
||||
try {
|
||||
log.info("Adding Term. input: {}", input);
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
LabelUtils.addTermToTarget(
|
||||
termUrn,
|
||||
LabelUtils.addTermsToTarget(
|
||||
ImmutableList.of(termUrn),
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
actor,
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.mutate;
|
||||
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.AddTermsInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AddTermsResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
private final EntityService _entityService;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
|
||||
final AddTermsInput input = bindArgument(environment.getArgument("input"), AddTermsInput.class);
|
||||
List<Urn> termUrns = input.getTermUrns().stream()
|
||||
.map(UrnUtils::getUrn)
|
||||
.collect(Collectors.toList());
|
||||
Urn targetUrn = Urn.createFromString(input.getResourceUrn());
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
|
||||
if (!LabelUtils.isAuthorizedToUpdateTerms(environment.getContext(), targetUrn, input.getSubResource())) {
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
LabelUtils.validateInput(
|
||||
termUrns,
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
input.getSubResourceType(),
|
||||
"glossaryTerm",
|
||||
_entityService,
|
||||
false
|
||||
);
|
||||
|
||||
try {
|
||||
log.info("Adding Term. input: {}", input);
|
||||
Urn actor = CorpuserUrn.createFromString(((QueryContext) environment.getContext()).getActorUrn());
|
||||
LabelUtils.addTermsToTarget(
|
||||
termUrns,
|
||||
targetUrn,
|
||||
input.getSubResource(),
|
||||
actor,
|
||||
_entityService
|
||||
);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to perform update against input {}, {}", input.toString(), e.getMessage());
|
||||
throw new RuntimeException(String.format("Failed to perform update against input %s", input.toString()), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,8 @@ import com.linkedin.metadata.entity.EntityService;
|
||||
import com.linkedin.schema.EditableSchemaFieldInfo;
|
||||
import com.linkedin.schema.EditableSchemaMetadata;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -100,8 +102,8 @@ public class LabelUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTagToTarget(
|
||||
Urn labelUrn,
|
||||
public static void addTagsToTarget(
|
||||
List<Urn> labelUrns,
|
||||
Urn targetUrn,
|
||||
String subResource,
|
||||
Urn actor,
|
||||
@ -114,7 +116,7 @@ public class LabelUtils {
|
||||
if (!tags.hasTags()) {
|
||||
tags.setTags(new TagAssociationArray());
|
||||
}
|
||||
addTagIfNotExists(tags, labelUrn);
|
||||
addTagsIfNotExists(tags, labelUrns);
|
||||
persistAspect(targetUrn, TAGS_ASPECT_NAME, tags, actor, entityService);
|
||||
} else {
|
||||
com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata =
|
||||
@ -126,13 +128,13 @@ public class LabelUtils {
|
||||
editableFieldInfo.setGlobalTags(new GlobalTags());
|
||||
}
|
||||
|
||||
addTagIfNotExists(editableFieldInfo.getGlobalTags(), labelUrn);
|
||||
addTagsIfNotExists(editableFieldInfo.getGlobalTags(), labelUrns);
|
||||
persistAspect(targetUrn, EDITABLE_SCHEMA_METADATA, editableSchemaMetadata, actor, entityService);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTermToTarget(
|
||||
Urn labelUrn,
|
||||
public static void addTermsToTarget(
|
||||
List<Urn> labelUrns,
|
||||
Urn targetUrn,
|
||||
String subResource,
|
||||
Urn actor,
|
||||
@ -147,7 +149,8 @@ public class LabelUtils {
|
||||
terms.setTerms(new GlossaryTermAssociationArray());
|
||||
}
|
||||
|
||||
addTermIfNotExistsToEntity(terms, labelUrn);
|
||||
addTermsIfNotExistsToEntity(terms, labelUrns);
|
||||
System.out.println(String.format("Persisting terms! %s", terms.toString()));
|
||||
persistAspect(targetUrn, GLOSSARY_TERM_ASPECT_NAME, terms, actor, entityService);
|
||||
} else {
|
||||
com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata =
|
||||
@ -161,12 +164,12 @@ public class LabelUtils {
|
||||
|
||||
editableFieldInfo.getGlossaryTerms().setAuditStamp(getAuditStamp(actor));
|
||||
|
||||
addTermIfNotExistsToEntity(editableFieldInfo.getGlossaryTerms(), labelUrn);
|
||||
addTermsIfNotExistsToEntity(editableFieldInfo.getGlossaryTerms(), labelUrns);
|
||||
persistAspect(targetUrn, EDITABLE_SCHEMA_METADATA, editableSchemaMetadata, actor, entityService);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addTermIfNotExistsToEntity(GlossaryTerms terms, Urn termUrn)
|
||||
private static void addTermsIfNotExistsToEntity(GlossaryTerms terms, List<Urn> termUrns)
|
||||
throws URISyntaxException {
|
||||
if (!terms.hasTerms()) {
|
||||
terms.setTerms(new GlossaryTermAssociationArray());
|
||||
@ -174,14 +177,24 @@ public class LabelUtils {
|
||||
|
||||
GlossaryTermAssociationArray termArray = terms.getTerms();
|
||||
|
||||
// if term exists, do not add it again
|
||||
if (termArray.stream().anyMatch(association -> association.getUrn().equals(termUrn))) {
|
||||
List<Urn> termsToAdd = new ArrayList<>();
|
||||
for (Urn termUrn : termUrns) {
|
||||
if (termArray.stream().anyMatch(association -> association.getUrn().equals(termUrn))) {
|
||||
continue;
|
||||
}
|
||||
termsToAdd.add(termUrn);
|
||||
}
|
||||
|
||||
// Check for no terms to add
|
||||
if (termsToAdd.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GlossaryTermAssociation newAssociation = new GlossaryTermAssociation();
|
||||
newAssociation.setUrn(GlossaryTermUrn.createFromUrn(termUrn));
|
||||
termArray.add(newAssociation);
|
||||
for (Urn termUrn : termsToAdd) {
|
||||
GlossaryTermAssociation newAssociation = new GlossaryTermAssociation();
|
||||
newAssociation.setUrn(GlossaryTermUrn.createFromUrn(termUrn));
|
||||
termArray.add(newAssociation);
|
||||
}
|
||||
}
|
||||
|
||||
private static TagAssociationArray removeTagIfExists(GlobalTags tags, Urn tagUrn) {
|
||||
@ -206,21 +219,31 @@ public class LabelUtils {
|
||||
return termArray;
|
||||
}
|
||||
|
||||
private static void addTagIfNotExists(GlobalTags tags, Urn tagUrn) throws URISyntaxException {
|
||||
private static void addTagsIfNotExists(GlobalTags tags, List<Urn> tagUrns) throws URISyntaxException {
|
||||
if (!tags.hasTags()) {
|
||||
tags.setTags(new TagAssociationArray());
|
||||
}
|
||||
|
||||
TagAssociationArray tagAssociationArray = tags.getTags();
|
||||
|
||||
// if tag exists, do not add it again
|
||||
if (tagAssociationArray.stream().anyMatch(association -> association.getTag().equals(tagUrn))) {
|
||||
List<Urn> tagsToAdd = new ArrayList<>();
|
||||
for (Urn tagUrn : tagUrns) {
|
||||
if (tagAssociationArray.stream().anyMatch(association -> association.getTag().equals(tagUrn))) {
|
||||
continue;
|
||||
}
|
||||
tagsToAdd.add(tagUrn);
|
||||
}
|
||||
|
||||
// Check for no tags to add
|
||||
if (tagsToAdd.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
TagAssociation newAssociation = new TagAssociation();
|
||||
newAssociation.setTag(TagUrn.createFromUrn(tagUrn));
|
||||
tagAssociationArray.add(newAssociation);
|
||||
for (Urn tagUrn : tagsToAdd) {
|
||||
TagAssociation newAssociation = new TagAssociation();
|
||||
newAssociation.setTag(TagUrn.createFromUrn(tagUrn));
|
||||
tagAssociationArray.add(newAssociation);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAuthorizedToUpdateTags(@Nonnull QueryContext context, Urn targetUrn, String subResource) {
|
||||
@ -265,6 +288,24 @@ public class LabelUtils {
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
|
||||
public static Boolean validateInput(
|
||||
List<Urn> labelUrns,
|
||||
Urn targetUrn,
|
||||
String subResource,
|
||||
SubResourceType subResourceType,
|
||||
String labelEntityType,
|
||||
EntityService entityService,
|
||||
Boolean isRemoving
|
||||
) {
|
||||
for (Urn urn : labelUrns) {
|
||||
boolean labelResult = validateInput(urn, targetUrn, subResource, subResourceType, labelEntityType, entityService, isRemoving);
|
||||
if (!labelResult) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Boolean validateInput(
|
||||
Urn labelUrn,
|
||||
Urn targetUrn,
|
||||
|
||||
@ -9,15 +9,18 @@ import com.linkedin.common.OwnershipSource;
|
||||
import com.linkedin.common.OwnershipSourceType;
|
||||
import com.linkedin.common.OwnershipType;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
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.generated.OwnerEntityType;
|
||||
import com.linkedin.datahub.graphql.generated.OwnerInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -49,6 +52,23 @@ public class OwnerUtils {
|
||||
persistAspect(resourceUrn, Constants.OWNERSHIP_ASPECT_NAME, ownershipAspect, actor, entityService);
|
||||
}
|
||||
|
||||
public static void addOwners(
|
||||
List<OwnerInput> owners,
|
||||
Urn resourceUrn,
|
||||
Urn actor,
|
||||
EntityService entityService
|
||||
) {
|
||||
Ownership ownershipAspect = (Ownership) getAspectFromEntity(
|
||||
resourceUrn.toString(),
|
||||
Constants.OWNERSHIP_ASPECT_NAME,
|
||||
entityService,
|
||||
new Ownership());
|
||||
for (OwnerInput input : owners) {
|
||||
addOwner(ownershipAspect, UrnUtils.getUrn(input.getOwnerUrn()), OwnershipType.valueOf(input.getType().toString()));
|
||||
}
|
||||
persistAspect(resourceUrn, Constants.OWNERSHIP_ASPECT_NAME, ownershipAspect, actor, entityService);
|
||||
}
|
||||
|
||||
public static void removeOwner(
|
||||
Urn ownerUrn,
|
||||
Urn resourceUrn,
|
||||
@ -106,6 +126,24 @@ public class OwnerUtils {
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
|
||||
public static Boolean validateAddInput(
|
||||
List<OwnerInput> owners,
|
||||
Urn resourceUrn,
|
||||
EntityService entityService
|
||||
) {
|
||||
for (OwnerInput owner : owners) {
|
||||
boolean result = validateAddInput(
|
||||
UrnUtils.getUrn(owner.getOwnerUrn()),
|
||||
owner.getOwnerEntityType(),
|
||||
resourceUrn,
|
||||
entityService);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Boolean validateAddInput(
|
||||
Urn ownerUrn,
|
||||
OwnerEntityType ownerEntityType,
|
||||
@ -125,6 +163,10 @@ public class OwnerUtils {
|
||||
throw new IllegalArgumentException(String.format("Failed to change ownership for resource %s. Resource does not exist.", resourceUrn));
|
||||
}
|
||||
|
||||
if (!entityService.exists(ownerUrn)) {
|
||||
throw new IllegalArgumentException(String.format("Failed to change ownership for resource %s. Owner does not exist.", resourceUrn));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +210,11 @@ type Mutation {
|
||||
"""
|
||||
addTag(input: TagAssociationInput!): Boolean
|
||||
|
||||
"""
|
||||
Add multiple tags to a particular Entity or subresource
|
||||
"""
|
||||
addTags(input: AddTagsInput!): Boolean
|
||||
|
||||
"""
|
||||
Remove a tag from a particular Entity or subresource
|
||||
"""
|
||||
@ -220,6 +225,11 @@ type Mutation {
|
||||
"""
|
||||
addTerm(input: TermAssociationInput!): Boolean
|
||||
|
||||
"""
|
||||
Add multiple glossary terms to a particular Entity or subresource
|
||||
"""
|
||||
addTerms(input: AddTermsInput!): Boolean
|
||||
|
||||
"""
|
||||
Remove a glossary term from a particular Entity or subresource
|
||||
"""
|
||||
@ -230,6 +240,11 @@ type Mutation {
|
||||
"""
|
||||
addOwner(input: AddOwnerInput!): Boolean
|
||||
|
||||
"""
|
||||
Add multiple owners to a particular Entity
|
||||
"""
|
||||
addOwners(input: AddOwnersInput!): Boolean
|
||||
|
||||
"""
|
||||
Remove an owner from a particular Entity
|
||||
"""
|
||||
@ -5782,6 +5797,31 @@ input TermAssociationInput {
|
||||
subResource: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input provided when adding Terms to an asset
|
||||
"""
|
||||
input AddTermsInput {
|
||||
"""
|
||||
The primary key of the Glossary Term to add or remove
|
||||
"""
|
||||
termUrns: [String!]!
|
||||
|
||||
"""
|
||||
The target Metadata Entity to add or remove the Glossary Term from
|
||||
"""
|
||||
resourceUrn: String!
|
||||
|
||||
"""
|
||||
An optional type of a sub resource to attach the Glossary Term to
|
||||
"""
|
||||
subResourceType: SubResourceType
|
||||
|
||||
"""
|
||||
An optional sub resource identifier to attach the Glossary Term to
|
||||
"""
|
||||
subResource: String
|
||||
}
|
||||
|
||||
"""
|
||||
A type of Metadata Entity sub resource
|
||||
"""
|
||||
@ -5817,6 +5857,31 @@ input TagAssociationInput {
|
||||
subResource: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input provided when adding tags to an asset
|
||||
"""
|
||||
input AddTagsInput {
|
||||
"""
|
||||
The primary key of the Tags
|
||||
"""
|
||||
tagUrns: [String!]!
|
||||
|
||||
"""
|
||||
The target Metadata Entity to add or remove the Tag to
|
||||
"""
|
||||
resourceUrn: String!
|
||||
|
||||
"""
|
||||
An optional type of a sub resource to attach the Tag to
|
||||
"""
|
||||
subResourceType: SubResourceType
|
||||
|
||||
"""
|
||||
An optional sub resource identifier to attach the Tag to
|
||||
"""
|
||||
subResource: String
|
||||
}
|
||||
|
||||
"""
|
||||
Entities that are able to own other entities
|
||||
"""
|
||||
@ -5857,6 +5922,41 @@ input AddOwnerInput {
|
||||
resourceUrn: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input provided when adding an owner to an asset
|
||||
"""
|
||||
input OwnerInput {
|
||||
"""
|
||||
The primary key of the Owner to add or remove
|
||||
"""
|
||||
ownerUrn: String!
|
||||
|
||||
"""
|
||||
The owner type, either a user or group
|
||||
"""
|
||||
ownerEntityType: OwnerEntityType!
|
||||
|
||||
"""
|
||||
The ownership type for the new owner. If none is provided, then a new NONE will be added.
|
||||
"""
|
||||
type: OwnershipType
|
||||
}
|
||||
|
||||
"""
|
||||
Input provided when adding multiple associations between a Metadata Entity and an user or group owner
|
||||
"""
|
||||
input AddOwnersInput {
|
||||
"""
|
||||
The primary key of the Owner to add or remove
|
||||
"""
|
||||
owners: [OwnerInput!]!
|
||||
|
||||
"""
|
||||
The urn of the resource or entity to attach or remove the owner from, for example a dataset urn
|
||||
"""
|
||||
resourceUrn: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input provided when removing the association between a Metadata Entity and an user or group owner
|
||||
"""
|
||||
|
||||
@ -0,0 +1,213 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.owner;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.generated.AddOwnersInput;
|
||||
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
|
||||
import com.linkedin.datahub.graphql.generated.OwnerInput;
|
||||
import com.linkedin.datahub.graphql.generated.OwnershipType;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddOwnersResolver;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import org.mockito.Mockito;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static com.linkedin.datahub.graphql.TestUtils.*;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
|
||||
public class AddOwnersResolverTest {
|
||||
|
||||
private static final String TEST_ENTITY_URN = "urn:li:dataset:(urn:li:dataPlatform:mysql,my-test,PROD)";
|
||||
private static final String TEST_OWNER_1_URN = "urn:li:corpuser:test-id-1";
|
||||
private static final String TEST_OWNER_2_URN = "urn:li:corpuser:test-id-2";
|
||||
|
||||
@Test
|
||||
public void testGetSuccessNoExistingOwners() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.OWNERSHIP_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_2_URN))).thenReturn(true);
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER),
|
||||
new OwnerInput(TEST_OWNER_2_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)
|
||||
), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
// Unable to easily validate exact payload due to the injected timestamp
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.any(MetadataChangeProposal.class),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_OWNER_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_OWNER_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSuccessExistingOwners() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.OWNERSHIP_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_2_URN))).thenReturn(true);
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER),
|
||||
new OwnerInput(TEST_OWNER_2_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)
|
||||
), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
// Unable to easily validate exact payload due to the injected timestamp
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.any(MetadataChangeProposal.class),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_OWNER_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_OWNER_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureOwnerDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.OWNERSHIP_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_1_URN))).thenReturn(false);
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureResourceDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.OWNERSHIP_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(false);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_OWNER_1_URN))).thenReturn(true);
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnauthorized() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
QueryContext mockContext = getMockDenyContext();
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEntityClientException() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.doThrow(RuntimeException.class).when(mockService).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
|
||||
AddOwnersResolver resolver = new AddOwnersResolver(Mockito.mock(EntityService.class));
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
AddOwnersInput input = new AddOwnersInput(ImmutableList.of(
|
||||
new OwnerInput(TEST_OWNER_1_URN, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)), TEST_ENTITY_URN);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,246 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.tag;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.GlobalTags;
|
||||
import com.linkedin.common.TagAssociation;
|
||||
import com.linkedin.common.TagAssociationArray;
|
||||
import com.linkedin.common.urn.TagUrn;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.generated.AddTagsInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTagsResolver;
|
||||
import com.linkedin.events.metadata.ChangeType;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import com.linkedin.metadata.utils.GenericRecordUtils;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import org.mockito.Mockito;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static com.linkedin.datahub.graphql.TestUtils.*;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
|
||||
public class AddTagsResolverTest {
|
||||
|
||||
private static final String TEST_ENTITY_URN = "urn:li:dataset:(urn:li:dataPlatform:mysql,my-test,PROD)";
|
||||
private static final String TEST_TAG_1_URN = "urn:li:tag:test-id-1";
|
||||
private static final String TEST_TAG_2_URN = "urn:li:tag:test-id-2";
|
||||
|
||||
@Test
|
||||
public void testGetSuccessNoExistingTags() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOBAL_TAGS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_2_URN))).thenReturn(true);
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN,
|
||||
TEST_TAG_2_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
final GlobalTags newTags = new GlobalTags().setTags(new TagAssociationArray(ImmutableList.of(
|
||||
new TagAssociation().setTag(TagUrn.createFromString(TEST_TAG_1_URN)),
|
||||
new TagAssociation().setTag(TagUrn.createFromString(TEST_TAG_2_URN))
|
||||
)));
|
||||
|
||||
final MetadataChangeProposal proposal = new MetadataChangeProposal();
|
||||
proposal.setEntityUrn(Urn.createFromString(TEST_ENTITY_URN));
|
||||
proposal.setEntityType(Constants.DATASET_ENTITY_NAME);
|
||||
proposal.setAspectName(Constants.GLOBAL_TAGS_ASPECT_NAME);
|
||||
proposal.setAspect(GenericRecordUtils.serializeAspect(newTags));
|
||||
proposal.setChangeType(ChangeType.UPSERT);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.eq(proposal),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TAG_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TAG_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSuccessExistingTags() throws Exception {
|
||||
GlobalTags originalTags = new GlobalTags().setTags(new TagAssociationArray(ImmutableList.of(
|
||||
new TagAssociation().setTag(TagUrn.createFromString(TEST_TAG_1_URN))))
|
||||
);
|
||||
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOBAL_TAGS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(originalTags);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_2_URN))).thenReturn(true);
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN,
|
||||
TEST_TAG_2_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
final GlobalTags newTags = new GlobalTags().setTags(new TagAssociationArray(ImmutableList.of(
|
||||
new TagAssociation().setTag(TagUrn.createFromString(TEST_TAG_1_URN)),
|
||||
new TagAssociation().setTag(TagUrn.createFromString(TEST_TAG_2_URN))
|
||||
)));
|
||||
|
||||
final MetadataChangeProposal proposal = new MetadataChangeProposal();
|
||||
proposal.setEntityUrn(Urn.createFromString(TEST_ENTITY_URN));
|
||||
proposal.setEntityType(Constants.DATASET_ENTITY_NAME);
|
||||
proposal.setAspectName(Constants.GLOBAL_TAGS_ASPECT_NAME);
|
||||
proposal.setAspect(GenericRecordUtils.serializeAspect(newTags));
|
||||
proposal.setChangeType(ChangeType.UPSERT);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.eq(proposal),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TAG_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TAG_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureTagDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOBAL_TAGS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_1_URN))).thenReturn(false);
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureResourceDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOBAL_TAGS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(false);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TAG_1_URN))).thenReturn(true);
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnauthorized() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
QueryContext mockContext = getMockDenyContext();
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEntityClientException() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.doThrow(RuntimeException.class).when(mockService).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
|
||||
AddTagsResolver resolver = new AddTagsResolver(Mockito.mock(EntityService.class));
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
AddTagsInput input = new AddTagsInput(ImmutableList.of(
|
||||
TEST_TAG_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.term;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.GlossaryTermAssociation;
|
||||
import com.linkedin.common.GlossaryTermAssociationArray;
|
||||
import com.linkedin.common.GlossaryTerms;
|
||||
import com.linkedin.common.urn.GlossaryTermUrn;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.generated.AddTermsInput;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddTermsResolver;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import org.mockito.Mockito;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static com.linkedin.datahub.graphql.TestUtils.*;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
|
||||
public class AddTermsResolverTest {
|
||||
|
||||
private static final String TEST_ENTITY_URN = "urn:li:dataset:(urn:li:dataPlatform:mysql,my-test,PROD)";
|
||||
private static final String TEST_TERM_1_URN = "urn:li:glossaryTerm:test-id-1";
|
||||
private static final String TEST_TERM_2_URN = "urn:li:glossaryTerm:test-id-2";
|
||||
|
||||
@Test
|
||||
public void testGetSuccessNoExistingTerms() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOSSARY_TERMS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_2_URN))).thenReturn(true);
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN,
|
||||
TEST_TERM_2_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
// Unable to easily validate exact payload due to the injected timestamp
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.any(MetadataChangeProposal.class),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TERM_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TERM_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSuccessExistingTerms() throws Exception {
|
||||
GlossaryTerms originalTerms = new GlossaryTerms().setTerms(new GlossaryTermAssociationArray(ImmutableList.of(
|
||||
new GlossaryTermAssociation().setUrn(GlossaryTermUrn.createFromString(TEST_TERM_1_URN))))
|
||||
);
|
||||
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOSSARY_TERMS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(originalTerms);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_1_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_2_URN))).thenReturn(true);
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN,
|
||||
TEST_TERM_2_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
assertTrue(resolver.get(mockEnv).get());
|
||||
|
||||
// Unable to easily validate exact payload due to the injected timestamp
|
||||
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
|
||||
Mockito.any(MetadataChangeProposal.class),
|
||||
Mockito.any(AuditStamp.class)
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TERM_1_URN))
|
||||
);
|
||||
|
||||
Mockito.verify(mockService, Mockito.times(1)).exists(
|
||||
Mockito.eq(Urn.createFromString(TEST_TERM_2_URN))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureTermDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOSSARY_TERMS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(true);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_1_URN))).thenReturn(false);
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFailureResourceDoesNotExist() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.when(mockService.getAspect(
|
||||
Mockito.eq(UrnUtils.getUrn(TEST_ENTITY_URN)),
|
||||
Mockito.eq(Constants.GLOSSARY_TERMS_ASPECT_NAME),
|
||||
Mockito.eq(0L)))
|
||||
.thenReturn(null);
|
||||
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_ENTITY_URN))).thenReturn(false);
|
||||
Mockito.when(mockService.exists(Urn.createFromString(TEST_TERM_1_URN))).thenReturn(true);
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnauthorized() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(mockService);
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
QueryContext mockContext = getMockDenyContext();
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
Mockito.verify(mockService, Mockito.times(0)).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEntityClientException() throws Exception {
|
||||
EntityService mockService = Mockito.mock(EntityService.class);
|
||||
|
||||
Mockito.doThrow(RuntimeException.class).when(mockService).ingestProposal(
|
||||
Mockito.any(),
|
||||
Mockito.any(AuditStamp.class));
|
||||
|
||||
AddTermsResolver resolver = new AddTermsResolver(Mockito.mock(EntityService.class));
|
||||
|
||||
// Execute resolver
|
||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||
QueryContext mockContext = getMockAllowContext();
|
||||
AddTermsInput input = new AddTermsInput(ImmutableList.of(
|
||||
TEST_TERM_1_URN
|
||||
), TEST_ENTITY_URN, null, null);
|
||||
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
|
||||
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
|
||||
|
||||
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
|
||||
}
|
||||
}
|
||||
@ -130,5 +130,37 @@
|
||||
"contentType":"application/json"
|
||||
},
|
||||
"systemMetadata":null
|
||||
},
|
||||
{
|
||||
"auditHeader": null,
|
||||
"proposedSnapshot": {
|
||||
"com.linkedin.pegasus2avro.metadata.snapshot.CorpUserSnapshot": {
|
||||
"urn": "urn:li:corpuser:jdoe",
|
||||
"aspects": [
|
||||
{
|
||||
"com.linkedin.pegasus2avro.identity.CorpUserInfo": {
|
||||
"active": true,
|
||||
"displayName": {
|
||||
"string": "John Doe"
|
||||
},
|
||||
"email": "jdoe@linkedin.com",
|
||||
"title": {
|
||||
"string": "Software Engineer"
|
||||
},
|
||||
"managerUrn": null,
|
||||
"departmentId": null,
|
||||
"departmentName": null,
|
||||
"firstName": null,
|
||||
"lastName": null,
|
||||
"fullName": {
|
||||
"string": "John Doe"
|
||||
},
|
||||
"countryCode": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"proposedDelta": null
|
||||
}
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user