Skip to content

Commit

Permalink
feat(domain) Add ability to edit a Domain name from the UI (datahub-p…
Browse files Browse the repository at this point in the history
  • Loading branch information
chriscollins3456 authored and alexey-kravtsov committed Jul 8, 2022
1 parent 70310eb commit d11fd91
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.UpdateNameInput;
import com.linkedin.domain.DomainProperties;
import com.linkedin.glossary.GlossaryTermInfo;
import com.linkedin.glossary.GlossaryNodeInfo;
import com.linkedin.metadata.Constants;
Expand Down Expand Up @@ -44,6 +45,8 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
return updateGlossaryTermName(targetUrn, input, environment.getContext());
case Constants.GLOSSARY_NODE_ENTITY_NAME:
return updateGlossaryNodeName(targetUrn, input, environment.getContext());
case Constants.DOMAIN_ENTITY_NAME:
return updateDomainName(targetUrn, input, environment.getContext());
default:
throw new RuntimeException(
String.format("Failed to update name. Unsupported resource type %s provided.", targetUrn));
Expand Down Expand Up @@ -98,4 +101,28 @@ private Boolean updateGlossaryNodeName(
}
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
}

private Boolean updateDomainName(
Urn targetUrn,
UpdateNameInput input,
QueryContext context
) {
if (AuthorizationUtils.canManageDomains(context)) {
try {
DomainProperties domainProperties = (DomainProperties) getAspectFromEntity(
targetUrn.toString(), Constants.DOMAIN_PROPERTIES_ASPECT_NAME, _entityService, null);
if (domainProperties == null) {
throw new IllegalArgumentException("Domain does not exist");
}
domainProperties.setName(input.getName());
Urn actor = CorpuserUrn.createFromString(context.getActorUrn());
persistAspect(targetUrn, Constants.DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties, 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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.UpdateNameInput;
import com.linkedin.datahub.graphql.resolvers.mutate.UpdateNameResolver;
import com.linkedin.domain.DomainProperties;
import com.linkedin.events.metadata.ChangeType;
import com.linkedin.glossary.GlossaryNodeInfo;
import com.linkedin.glossary.GlossaryTermInfo;
Expand All @@ -29,8 +30,10 @@ public class UpdateNameResolverTest {
private static final String NEW_NAME = "New Name";
private static final String TERM_URN = "urn:li:glossaryTerm:11115397daf94708a8822b8106cfd451";
private static final String NODE_URN = "urn:li:glossaryNode:22225397daf94708a8822b8106cfd451";
private static final String DOMAIN_URN = "urn:li:domain:22225397daf94708a8822b8106cfd451";
private static final UpdateNameInput INPUT = new UpdateNameInput(NEW_NAME, TERM_URN);
private static final UpdateNameInput INPUT_FOR_NODE = new UpdateNameInput(NEW_NAME, NODE_URN);
private static final UpdateNameInput INPUT_FOR_DOMAIN = new UpdateNameInput(NEW_NAME, DOMAIN_URN);
private static final CorpuserUrn TEST_ACTOR_URN = new CorpuserUrn("test");

private MetadataChangeProposal setupTests(DataFetchingEnvironment mockEnv, EntityService mockService) throws Exception {
Expand Down Expand Up @@ -111,6 +114,43 @@ public void testGetSuccessForNode() throws Exception {
);
}

@Test
public void testGetSuccessForDomain() throws Exception {
EntityService mockService = Mockito.mock(EntityService.class);
Mockito.when(mockService.exists(Urn.createFromString(DOMAIN_URN))).thenReturn(true);
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
Mockito.when(mockEnv.getArgument("input")).thenReturn(INPUT_FOR_DOMAIN);

QueryContext mockContext = getMockAllowContext();
Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class));
Mockito.when(mockContext.getActorUrn()).thenReturn(TEST_ACTOR_URN.toString());
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);

final String name = "test name";
Mockito.when(mockService.getAspect(
Urn.createFromString(DOMAIN_URN),
Constants.DOMAIN_PROPERTIES_ASPECT_NAME,
0))
.thenReturn(new DomainProperties().setName(name));

final MetadataChangeProposal proposal = new MetadataChangeProposal();
proposal.setEntityUrn(Urn.createFromString(DOMAIN_URN));
proposal.setEntityType(Constants.DOMAIN_ENTITY_NAME);
DomainProperties properties = new DomainProperties();
properties.setName(NEW_NAME);
proposal.setAspectName(Constants.DOMAIN_PROPERTIES_ASPECT_NAME);
proposal.setAspect(GenericRecordUtils.serializeAspect(properties));
proposal.setChangeType(ChangeType.UPSERT);

UpdateNameResolver resolver = new UpdateNameResolver(mockService);

assertTrue(resolver.get(mockEnv).get());
Mockito.verify(mockService, Mockito.times(1)).ingestProposal(
Mockito.eq(proposal),
Mockito.any()
);
}

@Test
public void testGetFailureEntityDoesNotExist() throws Exception {
EntityService mockService = Mockito.mock(EntityService.class);
Expand Down
15 changes: 15 additions & 0 deletions datahub-web-react/src/Mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
RecommendationRenderType,
RelationshipDirection,
Container,
PlatformPrivileges,
} from './types.generated';
import { GetTagDocument } from './graphql/tag.generated';
import { GetMlModelDocument } from './graphql/mlModel.generated';
Expand Down Expand Up @@ -3289,3 +3290,17 @@ export const mocks = [
},
},
];

export const platformPrivileges: PlatformPrivileges = {
viewAnalytics: true,
managePolicies: true,
manageIdentities: true,
generatePersonalAccessTokens: true,
manageDomains: true,
manageIngestion: true,
manageSecrets: true,
manageTokens: true,
manageTests: true,
manageGlossaries: true,
manageUserCredentials: true,
};
1 change: 1 addition & 0 deletions datahub-web-react/src/app/entity/domain/DomainEntity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class DomainEntity implements Entity<Domain> {
useUpdateQuery={undefined}
getOverrideProperties={this.getOverridePropertiesFromEntity}
headerDropdownItems={new Set([EntityMenuItems.COPY_URL])}
isNameEditable
tabs={[
{
name: 'Entities',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { platformPrivileges } from '../../../../../../Mocks';
import { EntityType } from '../../../../../../types.generated';
import { getCanEditName } from '../header/EntityHeader';

describe('getCanEditName', () => {
it('should return true for Terms if manageGlossaries privilege is true', () => {
const canEditName = getCanEditName(EntityType.GlossaryTerm, platformPrivileges);

expect(canEditName).toBe(true);
});

it('should return false for Terms if manageGlossaries privilege is false', () => {
const privilegesWithoutGlossaries = { ...platformPrivileges, manageGlossaries: false };
const canEditName = getCanEditName(EntityType.GlossaryTerm, privilegesWithoutGlossaries);

expect(canEditName).toBe(false);
});

it('should return true for Nodes if manageGlossaries privilege is true', () => {
const canEditName = getCanEditName(EntityType.GlossaryNode, platformPrivileges);

expect(canEditName).toBe(true);
});

it('should return false for Nodes if manageGlossaries privilege is false', () => {
const privilegesWithoutGlossaries = { ...platformPrivileges, manageGlossaries: false };
const canEditName = getCanEditName(EntityType.GlossaryNode, privilegesWithoutGlossaries);

expect(canEditName).toBe(false);
});

it('should return true for Domains if manageDomains privilege is true', () => {
const canEditName = getCanEditName(EntityType.Domain, platformPrivileges);

expect(canEditName).toBe(true);
});

it('should return false for Domains if manageDomains privilege is false', () => {
const privilegesWithoutDomains = { ...platformPrivileges, manageDomains: false };
const canEditName = getCanEditName(EntityType.Domain, privilegesWithoutDomains);

expect(canEditName).toBe(false);
});

it('should return false for an unsupported entity', () => {
const canEditName = getCanEditName(EntityType.Chart, platformPrivileges);

expect(canEditName).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@ const TopButtonsWrapper = styled.div`
margin-bottom: 8px;
`;

function getCanEditName(entityType: EntityType, privileges?: PlatformPrivileges) {
export function getCanEditName(entityType: EntityType, privileges?: PlatformPrivileges) {
switch (entityType) {
case EntityType.GlossaryTerm:
case EntityType.GlossaryNode:
return privileges?.manageGlossaries;
case EntityType.Domain:
return privileges?.manageDomains;
default:
return false;
}
Expand Down

0 comments on commit d11fd91

Please sign in to comment.