mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-12 10:35:51 +00:00
feat(groups & owners) Add ability to edit group name + assign creator as owner of metadata (#6047)
Adds ability to edit user group names from the UI. Also sets the creator or Glossary Terms, Glossary Nodes, and Domains to be the owner of said item.
This commit is contained in:
parent
3106e42e89
commit
11092c73cf
@ -767,7 +767,7 @@ public class GmsGraphQLEngine {
|
|||||||
.dataFetcher("removeUser", new RemoveUserResolver(this.entityClient))
|
.dataFetcher("removeUser", new RemoveUserResolver(this.entityClient))
|
||||||
.dataFetcher("removeGroup", new RemoveGroupResolver(this.entityClient))
|
.dataFetcher("removeGroup", new RemoveGroupResolver(this.entityClient))
|
||||||
.dataFetcher("updateUserStatus", new UpdateUserStatusResolver(this.entityClient))
|
.dataFetcher("updateUserStatus", new UpdateUserStatusResolver(this.entityClient))
|
||||||
.dataFetcher("createDomain", new CreateDomainResolver(this.entityClient))
|
.dataFetcher("createDomain", new CreateDomainResolver(this.entityClient, this.entityService))
|
||||||
.dataFetcher("deleteDomain", new DeleteDomainResolver(entityClient))
|
.dataFetcher("deleteDomain", new DeleteDomainResolver(entityClient))
|
||||||
.dataFetcher("setDomain", new SetDomainResolver(this.entityClient, this.entityService))
|
.dataFetcher("setDomain", new SetDomainResolver(this.entityClient, this.entityService))
|
||||||
.dataFetcher("batchSetDomain", new BatchSetDomainResolver(this.entityService))
|
.dataFetcher("batchSetDomain", new BatchSetDomainResolver(this.entityService))
|
||||||
@ -789,8 +789,8 @@ public class GmsGraphQLEngine {
|
|||||||
.dataFetcher("updateTest", new UpdateTestResolver(this.entityClient))
|
.dataFetcher("updateTest", new UpdateTestResolver(this.entityClient))
|
||||||
.dataFetcher("deleteTest", new DeleteTestResolver(this.entityClient))
|
.dataFetcher("deleteTest", new DeleteTestResolver(this.entityClient))
|
||||||
.dataFetcher("reportOperation", new ReportOperationResolver(this.entityClient))
|
.dataFetcher("reportOperation", new ReportOperationResolver(this.entityClient))
|
||||||
.dataFetcher("createGlossaryTerm", new CreateGlossaryTermResolver(this.entityClient))
|
.dataFetcher("createGlossaryTerm", new CreateGlossaryTermResolver(this.entityClient, this.entityService))
|
||||||
.dataFetcher("createGlossaryNode", new CreateGlossaryNodeResolver(this.entityClient))
|
.dataFetcher("createGlossaryNode", new CreateGlossaryNodeResolver(this.entityClient, this.entityService))
|
||||||
.dataFetcher("updateParentNode", new UpdateParentNodeResolver(entityService))
|
.dataFetcher("updateParentNode", new UpdateParentNodeResolver(entityService))
|
||||||
.dataFetcher("deleteGlossaryEntity",
|
.dataFetcher("deleteGlossaryEntity",
|
||||||
new DeleteGlossaryEntityResolver(this.entityClient, this.entityService))
|
new DeleteGlossaryEntityResolver(this.entityClient, this.entityService))
|
||||||
|
|||||||
@ -5,9 +5,13 @@ import com.linkedin.datahub.graphql.QueryContext;
|
|||||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||||
import com.linkedin.datahub.graphql.generated.CreateDomainInput;
|
import com.linkedin.datahub.graphql.generated.CreateDomainInput;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnershipType;
|
||||||
|
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
||||||
import com.linkedin.domain.DomainProperties;
|
import com.linkedin.domain.DomainProperties;
|
||||||
import com.linkedin.entity.client.EntityClient;
|
import com.linkedin.entity.client.EntityClient;
|
||||||
import com.linkedin.events.metadata.ChangeType;
|
import com.linkedin.events.metadata.ChangeType;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.DomainKey;
|
import com.linkedin.metadata.key.DomainKey;
|
||||||
import com.linkedin.metadata.utils.EntityKeyUtils;
|
import com.linkedin.metadata.utils.EntityKeyUtils;
|
||||||
@ -30,6 +34,7 @@ import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
|||||||
public class CreateDomainResolver implements DataFetcher<CompletableFuture<String>> {
|
public class CreateDomainResolver implements DataFetcher<CompletableFuture<String>> {
|
||||||
|
|
||||||
private final EntityClient _entityClient;
|
private final EntityClient _entityClient;
|
||||||
|
private final EntityService _entityService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
||||||
@ -63,7 +68,9 @@ public class CreateDomainResolver implements DataFetcher<CompletableFuture<Strin
|
|||||||
proposal.setAspect(GenericRecordUtils.serializeAspect(mapDomainProperties(input)));
|
proposal.setAspect(GenericRecordUtils.serializeAspect(mapDomainProperties(input)));
|
||||||
proposal.setChangeType(ChangeType.UPSERT);
|
proposal.setChangeType(ChangeType.UPSERT);
|
||||||
|
|
||||||
return _entityClient.ingestProposal(proposal, context.getAuthentication());
|
String domainUrn = _entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||||
|
OwnerUtils.addCreatorAsOwner(context, domainUrn, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER, _entityService);
|
||||||
|
return domainUrn;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create Domain with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
log.error("Failed to create Domain with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
||||||
throw new RuntimeException(String.format("Failed to create Domain with id: %s, name: %s", input.getId(), input.getName()), e);
|
throw new RuntimeException(String.format("Failed to create Domain with id: %s, name: %s", input.getId(), input.getName()), e);
|
||||||
|
|||||||
@ -6,9 +6,13 @@ import com.linkedin.datahub.graphql.QueryContext;
|
|||||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||||
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
|
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnershipType;
|
||||||
|
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
||||||
import com.linkedin.entity.client.EntityClient;
|
import com.linkedin.entity.client.EntityClient;
|
||||||
import com.linkedin.events.metadata.ChangeType;
|
import com.linkedin.events.metadata.ChangeType;
|
||||||
import com.linkedin.glossary.GlossaryNodeInfo;
|
import com.linkedin.glossary.GlossaryNodeInfo;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.GlossaryNodeKey;
|
import com.linkedin.metadata.key.GlossaryNodeKey;
|
||||||
import com.linkedin.metadata.utils.EntityKeyUtils;
|
import com.linkedin.metadata.utils.EntityKeyUtils;
|
||||||
@ -30,6 +34,7 @@ import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
|
|||||||
public class CreateGlossaryNodeResolver implements DataFetcher<CompletableFuture<String>> {
|
public class CreateGlossaryNodeResolver implements DataFetcher<CompletableFuture<String>> {
|
||||||
|
|
||||||
private final EntityClient _entityClient;
|
private final EntityClient _entityClient;
|
||||||
|
private final EntityService _entityService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
||||||
@ -56,7 +61,9 @@ public class CreateGlossaryNodeResolver implements DataFetcher<CompletableFuture
|
|||||||
proposal.setAspect(GenericRecordUtils.serializeAspect(mapGlossaryNodeInfo(input)));
|
proposal.setAspect(GenericRecordUtils.serializeAspect(mapGlossaryNodeInfo(input)));
|
||||||
proposal.setChangeType(ChangeType.UPSERT);
|
proposal.setChangeType(ChangeType.UPSERT);
|
||||||
|
|
||||||
return _entityClient.ingestProposal(proposal, context.getAuthentication());
|
String glossaryNodeUrn = _entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||||
|
OwnerUtils.addCreatorAsOwner(context, glossaryNodeUrn, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER, _entityService);
|
||||||
|
return glossaryNodeUrn;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create GlossaryNode with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
log.error("Failed to create GlossaryNode with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
||||||
throw new RuntimeException(String.format("Failed to create GlossaryNode with id: %s, name: %s", input.getId(), input.getName()), e);
|
throw new RuntimeException(String.format("Failed to create GlossaryNode with id: %s, name: %s", input.getId(), input.getName()), e);
|
||||||
|
|||||||
@ -6,9 +6,13 @@ import com.linkedin.datahub.graphql.QueryContext;
|
|||||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||||
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
|
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
|
||||||
|
import com.linkedin.datahub.graphql.generated.OwnershipType;
|
||||||
|
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
||||||
import com.linkedin.entity.client.EntityClient;
|
import com.linkedin.entity.client.EntityClient;
|
||||||
import com.linkedin.events.metadata.ChangeType;
|
import com.linkedin.events.metadata.ChangeType;
|
||||||
import com.linkedin.glossary.GlossaryTermInfo;
|
import com.linkedin.glossary.GlossaryTermInfo;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.GlossaryTermKey;
|
import com.linkedin.metadata.key.GlossaryTermKey;
|
||||||
import com.linkedin.metadata.utils.EntityKeyUtils;
|
import com.linkedin.metadata.utils.EntityKeyUtils;
|
||||||
@ -30,6 +34,7 @@ import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
|
|||||||
public class CreateGlossaryTermResolver implements DataFetcher<CompletableFuture<String>> {
|
public class CreateGlossaryTermResolver implements DataFetcher<CompletableFuture<String>> {
|
||||||
|
|
||||||
private final EntityClient _entityClient;
|
private final EntityClient _entityClient;
|
||||||
|
private final EntityService _entityService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception {
|
||||||
@ -56,7 +61,9 @@ public class CreateGlossaryTermResolver implements DataFetcher<CompletableFuture
|
|||||||
proposal.setAspect(GenericRecordUtils.serializeAspect(mapGlossaryTermInfo(input)));
|
proposal.setAspect(GenericRecordUtils.serializeAspect(mapGlossaryTermInfo(input)));
|
||||||
proposal.setChangeType(ChangeType.UPSERT);
|
proposal.setChangeType(ChangeType.UPSERT);
|
||||||
|
|
||||||
return _entityClient.ingestProposal(proposal, context.getAuthentication());
|
String glossaryTermUrn = _entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||||
|
OwnerUtils.addCreatorAsOwner(context, glossaryTermUrn, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER, _entityService);
|
||||||
|
return glossaryTermUrn;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create GlossaryTerm with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
log.error("Failed to create GlossaryTerm with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
||||||
throw new RuntimeException(String.format("Failed to create GlossaryTerm with id: %s, name: %s", input.getId(), input.getName()), e);
|
throw new RuntimeException(String.format("Failed to create GlossaryTerm with id: %s, name: %s", input.getId(), input.getName()), e);
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import com.linkedin.datahub.graphql.generated.UpdateNameInput;
|
|||||||
import com.linkedin.domain.DomainProperties;
|
import com.linkedin.domain.DomainProperties;
|
||||||
import com.linkedin.glossary.GlossaryTermInfo;
|
import com.linkedin.glossary.GlossaryTermInfo;
|
||||||
import com.linkedin.glossary.GlossaryNodeInfo;
|
import com.linkedin.glossary.GlossaryNodeInfo;
|
||||||
|
import com.linkedin.identity.CorpGroupInfo;
|
||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.entity.EntityService;
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import graphql.schema.DataFetcher;
|
import graphql.schema.DataFetcher;
|
||||||
@ -47,6 +48,8 @@ public class UpdateNameResolver implements DataFetcher<CompletableFuture<Boolean
|
|||||||
return updateGlossaryNodeName(targetUrn, input, environment.getContext());
|
return updateGlossaryNodeName(targetUrn, input, environment.getContext());
|
||||||
case Constants.DOMAIN_ENTITY_NAME:
|
case Constants.DOMAIN_ENTITY_NAME:
|
||||||
return updateDomainName(targetUrn, input, environment.getContext());
|
return updateDomainName(targetUrn, input, environment.getContext());
|
||||||
|
case Constants.CORP_GROUP_ENTITY_NAME:
|
||||||
|
return updateGroupName(targetUrn, input, environment.getContext());
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
String.format("Failed to update name. Unsupported resource type %s provided.", targetUrn));
|
String.format("Failed to update name. Unsupported resource type %s provided.", targetUrn));
|
||||||
@ -125,4 +128,28 @@ public class UpdateNameResolver implements DataFetcher<CompletableFuture<Boolean
|
|||||||
}
|
}
|
||||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean updateGroupName(
|
||||||
|
Urn targetUrn,
|
||||||
|
UpdateNameInput input,
|
||||||
|
QueryContext context
|
||||||
|
) {
|
||||||
|
if (AuthorizationUtils.canManageUsersAndGroups(context)) {
|
||||||
|
try {
|
||||||
|
CorpGroupInfo corpGroupInfo = (CorpGroupInfo) getAspectFromEntity(
|
||||||
|
targetUrn.toString(), Constants.CORP_GROUP_INFO_ASPECT_NAME, _entityService, null);
|
||||||
|
if (corpGroupInfo == null) {
|
||||||
|
throw new IllegalArgumentException("Group does not exist");
|
||||||
|
}
|
||||||
|
corpGroupInfo.setDisplayName(input.getName());
|
||||||
|
Urn actor = CorpuserUrn.createFromString(context.getActorUrn());
|
||||||
|
persistAspect(targetUrn, Constants.CORP_GROUP_INFO_ASPECT_NAME, corpGroupInfo, actor, _entityService);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(String.format("Failed to perform update against input %s", input), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.linkedin.datahub.graphql.resolvers.mutate.util;
|
package com.linkedin.datahub.graphql.resolvers.mutate.util;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.linkedin.common.urn.CorpuserUrn;
|
||||||
|
|
||||||
import com.linkedin.common.Owner;
|
import com.linkedin.common.Owner;
|
||||||
import com.linkedin.common.OwnerArray;
|
import com.linkedin.common.OwnerArray;
|
||||||
@ -34,6 +35,7 @@ import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.*;
|
|||||||
// TODO: Move to consuming from OwnerService
|
// TODO: Move to consuming from OwnerService
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class OwnerUtils {
|
public class OwnerUtils {
|
||||||
|
|
||||||
private static final ConjunctivePrivilegeGroup ALL_PRIVILEGES_GROUP = new ConjunctivePrivilegeGroup(ImmutableList.of(
|
private static final ConjunctivePrivilegeGroup ALL_PRIVILEGES_GROUP = new ConjunctivePrivilegeGroup(ImmutableList.of(
|
||||||
PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType()
|
PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType()
|
||||||
));
|
));
|
||||||
@ -218,4 +220,23 @@ public class OwnerUtils {
|
|||||||
entityService.ingestProposal(change, getAuditStamp(actor), false);
|
entityService.ingestProposal(change, getAuditStamp(actor), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addCreatorAsOwner(
|
||||||
|
QueryContext context,
|
||||||
|
String urn,
|
||||||
|
OwnerEntityType ownerEntityType,
|
||||||
|
com.linkedin.datahub.graphql.generated.OwnershipType ownershipType,
|
||||||
|
EntityService entityService) {
|
||||||
|
try {
|
||||||
|
Urn actorUrn = CorpuserUrn.createFromString(context.getActorUrn());
|
||||||
|
addOwnersToResources(
|
||||||
|
ImmutableList.of(new OwnerInput(actorUrn.toString(), ownerEntityType, ownershipType)),
|
||||||
|
ImmutableList.of(new ResourceRefInput(urn, null, null)),
|
||||||
|
actorUrn,
|
||||||
|
entityService
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(String.format("Failed to add creator as owner of tag %s", urn), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
package com.linkedin.datahub.graphql.resolvers.tag;
|
package com.linkedin.datahub.graphql.resolvers.tag;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.linkedin.common.urn.CorpuserUrn;
|
|
||||||
import com.linkedin.common.urn.Urn;
|
|
||||||
import com.linkedin.data.template.SetMode;
|
import com.linkedin.data.template.SetMode;
|
||||||
import com.linkedin.datahub.graphql.QueryContext;
|
import com.linkedin.datahub.graphql.QueryContext;
|
||||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||||
import com.linkedin.datahub.graphql.generated.CreateTagInput;
|
import com.linkedin.datahub.graphql.generated.CreateTagInput;
|
||||||
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
|
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.generated.OwnershipType;
|
||||||
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
|
|
||||||
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
|
||||||
import com.linkedin.entity.client.EntityClient;
|
import com.linkedin.entity.client.EntityClient;
|
||||||
import com.linkedin.events.metadata.ChangeType;
|
import com.linkedin.events.metadata.ChangeType;
|
||||||
@ -74,7 +69,7 @@ public class CreateTagResolver implements DataFetcher<CompletableFuture<String>>
|
|||||||
proposal.setChangeType(ChangeType.UPSERT);
|
proposal.setChangeType(ChangeType.UPSERT);
|
||||||
|
|
||||||
String tagUrn = _entityClient.ingestProposal(proposal, context.getAuthentication());
|
String tagUrn = _entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||||
addCreatorAsOwner(context, tagUrn);
|
OwnerUtils.addCreatorAsOwner(context, tagUrn, OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER, _entityService);
|
||||||
return tagUrn;
|
return tagUrn;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create Tag with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
log.error("Failed to create Tag with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage());
|
||||||
@ -89,18 +84,4 @@ public class CreateTagResolver implements DataFetcher<CompletableFuture<String>>
|
|||||||
result.setDescription(input.getDescription(), SetMode.IGNORE_NULL);
|
result.setDescription(input.getDescription(), SetMode.IGNORE_NULL);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCreatorAsOwner(QueryContext context, String tagUrn) {
|
|
||||||
try {
|
|
||||||
Urn actorUrn = CorpuserUrn.createFromString(context.getActorUrn());
|
|
||||||
OwnerUtils.addOwnersToResources(
|
|
||||||
ImmutableList.of(new OwnerInput(actorUrn.toString(), OwnerEntityType.CORP_USER, OwnershipType.TECHNICAL_OWNER)),
|
|
||||||
ImmutableList.of(new ResourceRefInput(tagUrn, null, null)),
|
|
||||||
actorUrn,
|
|
||||||
_entityService
|
|
||||||
);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(String.format("Failed to add creator as owner of tag %s", tagUrn), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -9,6 +9,7 @@ import com.linkedin.events.metadata.ChangeType;
|
|||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.DomainKey;
|
import com.linkedin.metadata.key.DomainKey;
|
||||||
import com.linkedin.metadata.utils.GenericRecordUtils;
|
import com.linkedin.metadata.utils.GenericRecordUtils;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.mxe.MetadataChangeProposal;
|
import com.linkedin.mxe.MetadataChangeProposal;
|
||||||
import com.linkedin.r2.RemoteInvocationException;
|
import com.linkedin.r2.RemoteInvocationException;
|
||||||
import graphql.schema.DataFetchingEnvironment;
|
import graphql.schema.DataFetchingEnvironment;
|
||||||
@ -27,12 +28,16 @@ public class CreateDomainResolverTest {
|
|||||||
"test-name",
|
"test-name",
|
||||||
"test-description"
|
"test-description"
|
||||||
);
|
);
|
||||||
|
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
|
@Test
|
||||||
public void testGetSuccess() throws Exception {
|
public void testGetSuccess() throws Exception {
|
||||||
// Create resolver
|
// Create resolver
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
CreateDomainResolver resolver = new CreateDomainResolver(mockClient);
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
|
CreateDomainResolver resolver = new CreateDomainResolver(mockClient, mockService);
|
||||||
|
|
||||||
// Execute resolver
|
// Execute resolver
|
||||||
QueryContext mockContext = getMockAllowContext();
|
QueryContext mockContext = getMockAllowContext();
|
||||||
@ -65,7 +70,8 @@ public class CreateDomainResolverTest {
|
|||||||
public void testGetUnauthorized() throws Exception {
|
public void testGetUnauthorized() throws Exception {
|
||||||
// Create resolver
|
// Create resolver
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
CreateDomainResolver resolver = new CreateDomainResolver(mockClient);
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
|
CreateDomainResolver resolver = new CreateDomainResolver(mockClient, mockService);
|
||||||
|
|
||||||
// Execute resolver
|
// Execute resolver
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
@ -83,10 +89,11 @@ public class CreateDomainResolverTest {
|
|||||||
public void testGetEntityClientException() throws Exception {
|
public void testGetEntityClientException() throws Exception {
|
||||||
// Create resolver
|
// Create resolver
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
Mockito.doThrow(RemoteInvocationException.class).when(mockClient).ingestProposal(
|
Mockito.doThrow(RemoteInvocationException.class).when(mockClient).ingestProposal(
|
||||||
Mockito.any(),
|
Mockito.any(),
|
||||||
Mockito.any(Authentication.class));
|
Mockito.any(Authentication.class));
|
||||||
CreateDomainResolver resolver = new CreateDomainResolver(mockClient);
|
CreateDomainResolver resolver = new CreateDomainResolver(mockClient, mockService);
|
||||||
|
|
||||||
// Execute resolver
|
// Execute resolver
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import com.linkedin.glossary.GlossaryNodeInfo;
|
|||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.GlossaryNodeKey;
|
import com.linkedin.metadata.key.GlossaryNodeKey;
|
||||||
import com.linkedin.metadata.utils.GenericRecordUtils;
|
import com.linkedin.metadata.utils.GenericRecordUtils;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.mxe.MetadataChangeProposal;
|
import com.linkedin.mxe.MetadataChangeProposal;
|
||||||
import graphql.schema.DataFetchingEnvironment;
|
import graphql.schema.DataFetchingEnvironment;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@ -74,10 +75,11 @@ public class CreateGlossaryNodeResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccess() throws Exception {
|
public void testGetSuccess() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT, "test-description", parentNodeUrn);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT, "test-description", parentNodeUrn);
|
||||||
|
|
||||||
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient);
|
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
@ -89,10 +91,11 @@ public class CreateGlossaryNodeResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccessNoDescription() throws Exception {
|
public void testGetSuccessNoDescription() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_DESCRIPTION, "", parentNodeUrn);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_DESCRIPTION, "", parentNodeUrn);
|
||||||
|
|
||||||
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient);
|
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
@ -104,10 +107,11 @@ public class CreateGlossaryNodeResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccessNoParentNode() throws Exception {
|
public void testGetSuccessNoParentNode() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_PARENT_NODE, "test-description", null);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_PARENT_NODE, "test-description", null);
|
||||||
|
|
||||||
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient);
|
CreateGlossaryNodeResolver resolver = new CreateGlossaryNodeResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import com.linkedin.glossary.GlossaryTermInfo;
|
|||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.metadata.key.GlossaryTermKey;
|
import com.linkedin.metadata.key.GlossaryTermKey;
|
||||||
import com.linkedin.metadata.utils.GenericRecordUtils;
|
import com.linkedin.metadata.utils.GenericRecordUtils;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
import com.linkedin.mxe.MetadataChangeProposal;
|
import com.linkedin.mxe.MetadataChangeProposal;
|
||||||
import graphql.schema.DataFetchingEnvironment;
|
import graphql.schema.DataFetchingEnvironment;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@ -75,10 +76,11 @@ public class CreateGlossaryTermResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccess() throws Exception {
|
public void testGetSuccess() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT, "test-description", parentNodeUrn);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT, "test-description", parentNodeUrn);
|
||||||
|
|
||||||
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient);
|
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
@ -90,10 +92,11 @@ public class CreateGlossaryTermResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccessNoDescription() throws Exception {
|
public void testGetSuccessNoDescription() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_DESCRIPTION, "", parentNodeUrn);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_DESCRIPTION, "", parentNodeUrn);
|
||||||
|
|
||||||
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient);
|
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
@ -105,10 +108,11 @@ public class CreateGlossaryTermResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetSuccessNoParentNode() throws Exception {
|
public void testGetSuccessNoParentNode() throws Exception {
|
||||||
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
EntityClient mockClient = Mockito.mock(EntityClient.class);
|
||||||
|
EntityService mockService = Mockito.mock(EntityService.class);
|
||||||
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
|
||||||
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_PARENT_NODE, "test-description", null);
|
final MetadataChangeProposal proposal = setupTest(mockEnv, TEST_INPUT_NO_PARENT_NODE, "test-description", null);
|
||||||
|
|
||||||
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient);
|
CreateGlossaryTermResolver resolver = new CreateGlossaryTermResolver(mockClient, mockService);
|
||||||
resolver.get(mockEnv).get();
|
resolver.get(mockEnv).get();
|
||||||
|
|
||||||
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
Mockito.verify(mockClient, Mockito.times(1)).ingestProposal(
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Divider, message, Space, Button, Typography, Row, Col, Tooltip } from 'antd';
|
import { Divider, message, Space, Button, Typography, Row, Col, Tooltip } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { EditOutlined, LockOutlined, MailOutlined, SlackOutlined } from '@ant-design/icons';
|
import { EditOutlined, LockOutlined, MailOutlined, SlackOutlined } from '@ant-design/icons';
|
||||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||||
import { useUpdateCorpGroupPropertiesMutation } from '../../../graphql/group.generated';
|
import { useUpdateCorpGroupPropertiesMutation } from '../../../graphql/group.generated';
|
||||||
import { EntityRelationshipsResult, Ownership } from '../../../types.generated';
|
import { EntityRelationshipsResult, Ownership } from '../../../types.generated';
|
||||||
|
import { useUpdateNameMutation } from '../../../graphql/mutations.generated';
|
||||||
|
|
||||||
import GroupEditModal from './GroupEditModal';
|
import GroupEditModal from './GroupEditModal';
|
||||||
import CustomAvatar from '../../shared/avatar/CustomAvatar';
|
import CustomAvatar from '../../shared/avatar/CustomAvatar';
|
||||||
@ -20,6 +21,7 @@ import {
|
|||||||
GroupsSection,
|
GroupsSection,
|
||||||
} from '../shared/SidebarStyledComponents';
|
} from '../shared/SidebarStyledComponents';
|
||||||
import GroupMembersSideBarSection from './GroupMembersSideBarSection';
|
import GroupMembersSideBarSection from './GroupMembersSideBarSection';
|
||||||
|
import { useGetAuthenticatedUser } from '../../useGetAuthenticatedUser';
|
||||||
|
|
||||||
const { Paragraph } = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ type SideBarData = {
|
|||||||
groupOwnerShip: Ownership;
|
groupOwnerShip: Ownership;
|
||||||
isExternalGroup: boolean;
|
isExternalGroup: boolean;
|
||||||
externalGroupType: string | undefined;
|
externalGroupType: string | undefined;
|
||||||
urn: string | undefined;
|
urn: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -61,10 +63,21 @@ const GroupNameHeader = styled(Row)`
|
|||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const GroupName = styled.div`
|
const GroupTitle = styled(Typography.Title)`
|
||||||
max-width: 260px;
|
max-width: 260px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
width: 140px;
|
width: 140px;
|
||||||
|
|
||||||
|
&&& {
|
||||||
|
margin-bottom: 0;
|
||||||
|
word-break: break-all;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-typography-edit {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +103,31 @@ export default function GroupInfoSidebar({ sideBarData, refetch }: Props) {
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
const [editGroupModal, showEditGroupModal] = useState(false);
|
const [editGroupModal, showEditGroupModal] = useState(false);
|
||||||
const canEditGroup = true; // TODO; Replace this will fine-grained understanding of user permissions.
|
const me = useGetAuthenticatedUser();
|
||||||
|
const canEditGroup = me?.platformPrivileges.manageIdentities;
|
||||||
|
const [groupTitle, setGroupTitle] = useState(name);
|
||||||
|
const [updateName] = useUpdateNameMutation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setGroupTitle(groupTitle);
|
||||||
|
}, [groupTitle]);
|
||||||
|
|
||||||
|
// Update Group Title
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const handleTitleUpdate = async (name: string) => {
|
||||||
|
setGroupTitle(name);
|
||||||
|
await updateName({ variables: { input: { name, urn } } })
|
||||||
|
.then(() => {
|
||||||
|
message.success({ content: 'Name Updated', duration: 2 });
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch((e: unknown) => {
|
||||||
|
message.destroy();
|
||||||
|
if (e instanceof Error) {
|
||||||
|
message.error({ content: `Failed to update name: \n ${e.message || ''}`, duration: 3 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getEditModalData = {
|
const getEditModalData = {
|
||||||
urn,
|
urn,
|
||||||
@ -135,7 +172,9 @@ export default function GroupInfoSidebar({ sideBarData, refetch }: Props) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<GroupName>{name}</GroupName>
|
<GroupTitle level={3} editable={canEditGroup ? { onChange: handleTitleUpdate } : false}>
|
||||||
|
{groupTitle}
|
||||||
|
</GroupTitle>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
{isExternalGroup && (
|
{isExternalGroup && (
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
|
||||||
import { Divider, Popover, Tooltip, Typography } from 'antd';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { Divider, message, Modal, Popover, Tooltip, Typography } from 'antd';
|
||||||
|
import { blue } from '@ant-design/colors';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Deprecation } from '../../../../../types.generated';
|
import { Deprecation } from '../../../../../types.generated';
|
||||||
import { getLocaleTimezone } from '../../../../shared/time/timeUtils';
|
import { getLocaleTimezone } from '../../../../shared/time/timeUtils';
|
||||||
import { ANTD_GRAY } from '../../constants';
|
import { ANTD_GRAY } from '../../constants';
|
||||||
|
import { useBatchUpdateDeprecationMutation } from '../../../../../graphql/mutations.generated';
|
||||||
|
|
||||||
const DeprecatedContainer = styled.div`
|
const DeprecatedContainer = styled.div`
|
||||||
width: 104px;
|
width: 104px;
|
||||||
@ -55,12 +57,30 @@ const StyledInfoCircleOutlined = styled(InfoCircleOutlined)`
|
|||||||
color: #ef5b5b;
|
color: #ef5b5b;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const UndeprecatedIcon = styled(InfoCircleOutlined)`
|
||||||
|
font-size: 14px;
|
||||||
|
padding-right: 6px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconGroup = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: 'black';
|
||||||
|
&:hover {
|
||||||
|
color: ${blue[4]};
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
urn: string;
|
||||||
deprecation: Deprecation;
|
deprecation: Deprecation;
|
||||||
preview?: boolean | null;
|
preview?: boolean | null;
|
||||||
|
refetch?: () => void;
|
||||||
|
showUndeprecate: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeprecationPill = ({ deprecation, preview }: Props) => {
|
export const DeprecationPill = ({ deprecation, preview, urn, refetch, showUndeprecate }: Props) => {
|
||||||
|
const [batchUpdateDeprecationMutation] = useBatchUpdateDeprecationMutation();
|
||||||
/**
|
/**
|
||||||
* Deprecation Decommission Timestamp
|
* Deprecation Decommission Timestamp
|
||||||
*/
|
*/
|
||||||
@ -78,6 +98,30 @@ export const DeprecationPill = ({ deprecation, preview }: Props) => {
|
|||||||
const hasDetails = deprecation.note !== '' || deprecation.decommissionTime !== null;
|
const hasDetails = deprecation.note !== '' || deprecation.decommissionTime !== null;
|
||||||
const isDividerNeeded = deprecation.note !== '' && deprecation.decommissionTime !== null;
|
const isDividerNeeded = deprecation.note !== '' && deprecation.decommissionTime !== null;
|
||||||
|
|
||||||
|
const batchUndeprecate = () => {
|
||||||
|
batchUpdateDeprecationMutation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
resources: [{ resourceUrn: urn }],
|
||||||
|
deprecated: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ errors }) => {
|
||||||
|
if (!errors) {
|
||||||
|
message.success({ content: 'Marked assets as un-deprecated!', duration: 2 });
|
||||||
|
refetch?.();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
message.destroy();
|
||||||
|
message.error({
|
||||||
|
content: `Failed to mark assets as un-deprecated: \n ${e.message || ''}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
overlayStyle={{ maxWidth: 240 }}
|
overlayStyle={{ maxWidth: 240 }}
|
||||||
@ -95,6 +139,27 @@ export const DeprecationPill = ({ deprecation, preview }: Props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
|
{isDividerNeeded && <ThinDivider />}
|
||||||
|
{showUndeprecate && (
|
||||||
|
<IconGroup
|
||||||
|
onClick={() =>
|
||||||
|
Modal.confirm({
|
||||||
|
title: `Confirm Mark as un-deprecated`,
|
||||||
|
content: `Are you sure you want to mark this asset as un-deprecated?`,
|
||||||
|
onOk() {
|
||||||
|
batchUndeprecate();
|
||||||
|
},
|
||||||
|
onCancel() {},
|
||||||
|
okText: 'Yes',
|
||||||
|
maskClosable: true,
|
||||||
|
closable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<UndeprecatedIcon />
|
||||||
|
Mark as un-deprecated
|
||||||
|
</IconGroup>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'No additional details'
|
'No additional details'
|
||||||
|
|||||||
@ -26,13 +26,16 @@ export default function DeprecationDropdown({ urns, disabled = false, refetch }:
|
|||||||
})
|
})
|
||||||
.then(({ errors }) => {
|
.then(({ errors }) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
message.success({ content: 'Marked assets as undeprecated!', duration: 2 });
|
message.success({ content: 'Marked assets as un-deprecated!', duration: 2 });
|
||||||
refetch?.();
|
refetch?.();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
message.destroy();
|
message.destroy();
|
||||||
message.error({ content: `Failed to mark assets as undeprecated: \n ${e.message || ''}`, duration: 3 });
|
message.error({
|
||||||
|
content: `Failed to mark assets as un-deprecated: \n ${e.message || ''}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,11 +51,11 @@ export default function DeprecationDropdown({ urns, disabled = false, refetch }:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Mark as undeprecated',
|
title: 'Mark as un-deprecated',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: `Confirm Mark as undeprecated`,
|
title: `Confirm Mark as un-deprecated`,
|
||||||
content: `Are you sure you want to mark these assets as undeprecated?`,
|
content: `Are you sure you want to mark these assets as un-deprecated?`,
|
||||||
onOk() {
|
onOk() {
|
||||||
batchUndeprecate();
|
batchUndeprecate();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -125,7 +125,13 @@ export const EntityHeader = ({
|
|||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<EntityName isNameEditable={canEditName} />
|
<EntityName isNameEditable={canEditName} />
|
||||||
{entityData?.deprecation?.deprecated && (
|
{entityData?.deprecation?.deprecated && (
|
||||||
<DeprecationPill deprecation={entityData?.deprecation} preview={isCompact} />
|
<DeprecationPill
|
||||||
|
urn={urn}
|
||||||
|
deprecation={entityData?.deprecation}
|
||||||
|
showUndeprecate
|
||||||
|
preview={isCompact}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{entityData?.health?.map((health) => (
|
{entityData?.health?.map((health) => (
|
||||||
<EntityHealthStatus
|
<EntityHealthStatus
|
||||||
|
|||||||
@ -288,7 +288,9 @@ export default function DefaultPreviewCard({
|
|||||||
</EntityTitle>
|
</EntityTitle>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{deprecation?.deprecated && <DeprecationPill deprecation={deprecation} preview />}
|
{deprecation?.deprecated && (
|
||||||
|
<DeprecationPill deprecation={deprecation} urn="" showUndeprecate={false} preview />
|
||||||
|
)}
|
||||||
{externalUrl && (
|
{externalUrl && (
|
||||||
<ExternalUrlContainer>
|
<ExternalUrlContainer>
|
||||||
<ExternalUrlButton type="link" href={externalUrl} target="_blank">
|
<ExternalUrlButton type="link" href={externalUrl} target="_blank">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user