mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-02 03:39:03 +00:00
feat(graphql) Add upsertPageModule graphql endpoint for home page (#13981)
This commit is contained in:
parent
beb6233e8b
commit
0beebae081
@ -139,6 +139,7 @@ import com.linkedin.datahub.graphql.resolvers.load.LoadableTypeResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.load.OwnerTypeBatchResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.load.OwnerTypeResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.load.TimeSeriesAspectResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.module.UpsertPageModuleResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddLinkResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddOwnerResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.mutate.AddOwnersResolver;
|
||||
@ -335,6 +336,7 @@ import com.linkedin.metadata.service.ERModelRelationshipService;
|
||||
import com.linkedin.metadata.service.FormService;
|
||||
import com.linkedin.metadata.service.LineageService;
|
||||
import com.linkedin.metadata.service.OwnershipTypeService;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import com.linkedin.metadata.service.PageTemplateService;
|
||||
import com.linkedin.metadata.service.QueryService;
|
||||
import com.linkedin.metadata.service.SettingsService;
|
||||
@ -412,6 +414,7 @@ public class GmsGraphQLEngine {
|
||||
private final EntityVersioningService entityVersioningService;
|
||||
private final ApplicationService applicationService;
|
||||
private final PageTemplateService pageTemplateService;
|
||||
private final PageModuleService pageModuleService;
|
||||
|
||||
private final BusinessAttributeService businessAttributeService;
|
||||
private final FeatureFlags featureFlags;
|
||||
@ -542,6 +545,7 @@ public class GmsGraphQLEngine {
|
||||
this.dataProductService = args.dataProductService;
|
||||
this.applicationService = args.applicationService;
|
||||
this.pageTemplateService = args.pageTemplateService;
|
||||
this.pageModuleService = args.pageModuleService;
|
||||
this.formService = args.formService;
|
||||
this.restrictedService = args.restrictedService;
|
||||
this.connectionService = args.connectionService;
|
||||
@ -1375,6 +1379,7 @@ public class GmsGraphQLEngine {
|
||||
.dataFetcher("updateForm", new UpdateFormResolver(this.entityClient))
|
||||
.dataFetcher(
|
||||
"upsertPageTemplate", new UpsertPageTemplateResolver(this.pageTemplateService))
|
||||
.dataFetcher("upsertPageModule", new UpsertPageModuleResolver(this.pageModuleService))
|
||||
.dataFetcher(
|
||||
"updateDocPropagationSettings",
|
||||
new UpdateDocPropagationSettingsResolver(this.settingsService))
|
||||
|
||||
@ -38,6 +38,7 @@ import com.linkedin.metadata.service.ERModelRelationshipService;
|
||||
import com.linkedin.metadata.service.FormService;
|
||||
import com.linkedin.metadata.service.LineageService;
|
||||
import com.linkedin.metadata.service.OwnershipTypeService;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import com.linkedin.metadata.service.PageTemplateService;
|
||||
import com.linkedin.metadata.service.QueryService;
|
||||
import com.linkedin.metadata.service.SettingsService;
|
||||
@ -100,6 +101,7 @@ public class GmsGraphQLEngineArgs {
|
||||
EntityVersioningService entityVersioningService;
|
||||
ApplicationService applicationService;
|
||||
PageTemplateService pageTemplateService;
|
||||
PageModuleService pageModuleService;
|
||||
boolean systemTelemetryEnabled;
|
||||
MetricUtils metricUtils;
|
||||
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.module;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.concurrency.GraphQLConcurrencyUtils;
|
||||
import com.linkedin.datahub.graphql.generated.DataHubPageModule;
|
||||
import com.linkedin.datahub.graphql.generated.DataHubPageModuleType;
|
||||
import com.linkedin.datahub.graphql.generated.PageModuleScope;
|
||||
import com.linkedin.datahub.graphql.generated.UpsertPageModuleInput;
|
||||
import com.linkedin.datahub.graphql.types.module.PageModuleMapper;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import com.linkedin.module.DataHubPageModuleParams;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.annotation.Nonnull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class UpsertPageModuleResolver implements DataFetcher<CompletableFuture<DataHubPageModule>> {
|
||||
|
||||
private final PageModuleService _pageModuleService;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DataHubPageModule> get(DataFetchingEnvironment environment)
|
||||
throws Exception {
|
||||
final QueryContext context = environment.getContext();
|
||||
final UpsertPageModuleInput input =
|
||||
bindArgument(environment.getArgument("input"), UpsertPageModuleInput.class);
|
||||
|
||||
String urn = input.getUrn();
|
||||
String name = input.getName();
|
||||
DataHubPageModuleType type = input.getType();
|
||||
PageModuleScope scope = input.getScope();
|
||||
com.linkedin.datahub.graphql.generated.PageModuleParamsInput paramsInput = input.getParams();
|
||||
|
||||
// TODO: check permissions if the scope is GLOBAL
|
||||
|
||||
return GraphQLConcurrencyUtils.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
// Map GraphQL input to GMS types
|
||||
com.linkedin.module.DataHubPageModuleType gmsType =
|
||||
com.linkedin.module.DataHubPageModuleType.valueOf(type.toString());
|
||||
com.linkedin.module.PageModuleScope gmsScope =
|
||||
com.linkedin.module.PageModuleScope.valueOf(scope.toString());
|
||||
DataHubPageModuleParams gmsParams = mapParamsInput(paramsInput);
|
||||
|
||||
validateInput(gmsType, gmsParams);
|
||||
|
||||
final Urn moduleUrn =
|
||||
_pageModuleService.upsertPageModule(
|
||||
context.getOperationContext(), urn, name, gmsType, gmsScope, gmsParams);
|
||||
|
||||
EntityResponse response =
|
||||
_pageModuleService.getPageModuleEntityResponse(
|
||||
context.getOperationContext(), moduleUrn);
|
||||
return PageModuleMapper.map(context, response);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Failed to perform upsert page module update against input %s", input),
|
||||
e);
|
||||
}
|
||||
},
|
||||
this.getClass().getSimpleName(),
|
||||
"get");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private DataHubPageModuleParams mapParamsInput(
|
||||
com.linkedin.datahub.graphql.generated.PageModuleParamsInput paramsInput) {
|
||||
DataHubPageModuleParams gmsParams = new DataHubPageModuleParams();
|
||||
|
||||
if (paramsInput.getLinkParams() != null) {
|
||||
com.linkedin.module.LinkModuleParams linkParams = new com.linkedin.module.LinkModuleParams();
|
||||
linkParams.setLinkUrn(UrnUtils.getUrn(paramsInput.getLinkParams().getLinkUrn()));
|
||||
gmsParams.setLinkParams(linkParams);
|
||||
}
|
||||
|
||||
if (paramsInput.getRichTextParams() != null) {
|
||||
com.linkedin.module.RichTextModuleParams richTextParams =
|
||||
new com.linkedin.module.RichTextModuleParams();
|
||||
richTextParams.setContent(paramsInput.getRichTextParams().getContent());
|
||||
gmsParams.setRichTextParams(richTextParams);
|
||||
}
|
||||
|
||||
return gmsParams;
|
||||
}
|
||||
|
||||
private void validateInput(
|
||||
@Nonnull final com.linkedin.module.DataHubPageModuleType type,
|
||||
@Nonnull final DataHubPageModuleParams params) {
|
||||
// check if we provide the correct params given the type of module we're creating
|
||||
if (type.equals(com.linkedin.module.DataHubPageModuleType.RICH_TEXT)) {
|
||||
if (params.getRichTextParams() == null) {
|
||||
throw new IllegalArgumentException("Did not provide rich text params for rich text module");
|
||||
}
|
||||
} else if (type.equals(com.linkedin.module.DataHubPageModuleType.LINK)) {
|
||||
if (params.getLinkParams() == null) {
|
||||
throw new IllegalArgumentException("Did not provide link params for link module");
|
||||
}
|
||||
} else {
|
||||
// TODO: add more blocks to this check as we support creating more types of modules to this
|
||||
// resolver
|
||||
// If someone tries to create one of the default modules this error will be thrown
|
||||
throw new IllegalArgumentException("Attempted to create an unsupported module type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,78 @@ type DataHubPageModule implements Entity {
|
||||
relationships(input: RelationshipsInput!): EntityRelationshipsResult
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
"""
|
||||
Create or update a DataHub page module
|
||||
"""
|
||||
upsertPageModule(input: UpsertPageModuleInput!): DataHubPageModule!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for creating or updating a DataHub page module
|
||||
"""
|
||||
input UpsertPageModuleInput {
|
||||
"""
|
||||
The URN of the page module to update. If not provided, a new module will be created.
|
||||
"""
|
||||
urn: String
|
||||
|
||||
"""
|
||||
The display name of this module
|
||||
"""
|
||||
name: String!
|
||||
|
||||
"""
|
||||
The type of this module
|
||||
"""
|
||||
type: DataHubPageModuleType!
|
||||
|
||||
"""
|
||||
The scope of this module and who can use/see it
|
||||
"""
|
||||
scope: PageModuleScope!
|
||||
|
||||
"""
|
||||
The specific parameters stored for this module
|
||||
"""
|
||||
params: PageModuleParamsInput!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the specific parameters stored for a module
|
||||
"""
|
||||
input PageModuleParamsInput {
|
||||
"""
|
||||
The params required if the module is type LINK
|
||||
"""
|
||||
linkParams: LinkModuleParamsInput
|
||||
|
||||
"""
|
||||
The params required if the module is type RICH_TEXT
|
||||
"""
|
||||
richTextParams: RichTextModuleParamsInput
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the params required if the module is type LINK
|
||||
"""
|
||||
input LinkModuleParamsInput {
|
||||
"""
|
||||
The URN of the Post entity containing the link
|
||||
"""
|
||||
linkUrn: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the params required if the module is type RICH_TEXT
|
||||
"""
|
||||
input RichTextModuleParamsInput {
|
||||
"""
|
||||
The content of the rich text module
|
||||
"""
|
||||
content: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The main properties of a DataHub page module
|
||||
"""
|
||||
|
||||
@ -0,0 +1,295 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.module;
|
||||
|
||||
import static com.linkedin.datahub.graphql.TestUtils.getMockAllowContext;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
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.DataHubPageModule;
|
||||
import com.linkedin.datahub.graphql.generated.DataHubPageModuleType;
|
||||
import com.linkedin.datahub.graphql.generated.LinkModuleParamsInput;
|
||||
import com.linkedin.datahub.graphql.generated.PageModuleParamsInput;
|
||||
import com.linkedin.datahub.graphql.generated.PageModuleScope;
|
||||
import com.linkedin.datahub.graphql.generated.RichTextModuleParamsInput;
|
||||
import com.linkedin.datahub.graphql.generated.UpsertPageModuleInput;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import com.linkedin.module.DataHubPageModuleParams;
|
||||
import com.linkedin.module.DataHubPageModuleProperties;
|
||||
import com.linkedin.module.RichTextModuleParams;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class UpsertPageModuleResolverTest {
|
||||
|
||||
private static final String TEST_MODULE_URN = "urn:li:dataHubPageModule:test-module";
|
||||
private static final String TEST_MODULE_NAME = "Test Module";
|
||||
private static final String TEST_RICH_TEXT_CONTENT = "Test content";
|
||||
|
||||
private PageModuleService mockService;
|
||||
private UpsertPageModuleResolver resolver;
|
||||
private DataFetchingEnvironment mockEnvironment;
|
||||
private QueryContext mockQueryContext;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
mockService = mock(PageModuleService.class);
|
||||
resolver = new UpsertPageModuleResolver(mockService);
|
||||
mockEnvironment = mock(DataFetchingEnvironment.class);
|
||||
mockQueryContext = getMockAllowContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleSuccessWithUrn() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setUrn(TEST_MODULE_URN);
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.RICH_TEXT);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
RichTextModuleParamsInput richTextParams = new RichTextModuleParamsInput();
|
||||
richTextParams.setContent(TEST_RICH_TEXT_CONTENT);
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
paramsInput.setRichTextParams(richTextParams);
|
||||
input.setParams(paramsInput);
|
||||
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
EntityResponse mockResponse = createMockEntityResponse(moduleUrn);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
when(mockService.upsertPageModule(any(), eq(TEST_MODULE_URN), any(), any(), any(), any()))
|
||||
.thenReturn(moduleUrn);
|
||||
when(mockService.getPageModuleEntityResponse(any(), eq(moduleUrn))).thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
CompletableFuture<DataHubPageModule> future = resolver.get(mockEnvironment);
|
||||
DataHubPageModule result = future.get();
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_MODULE_URN);
|
||||
assertEquals(result.getType().toString(), "DATAHUB_PAGE_MODULE");
|
||||
verify(mockService, times(1))
|
||||
.upsertPageModule(any(), eq(TEST_MODULE_URN), any(), any(), any(), any());
|
||||
verify(mockService, times(1)).getPageModuleEntityResponse(any(), eq(moduleUrn));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleSuccessWithGeneratedUrn() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.RICH_TEXT);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
RichTextModuleParamsInput richTextParams = new RichTextModuleParamsInput();
|
||||
richTextParams.setContent(TEST_RICH_TEXT_CONTENT);
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
paramsInput.setRichTextParams(richTextParams);
|
||||
input.setParams(paramsInput);
|
||||
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
EntityResponse mockResponse = createMockEntityResponse(moduleUrn);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
when(mockService.upsertPageModule(any(), eq(null), any(), any(), any(), any()))
|
||||
.thenReturn(moduleUrn);
|
||||
when(mockService.getPageModuleEntityResponse(any(), eq(moduleUrn))).thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
CompletableFuture<DataHubPageModule> future = resolver.get(mockEnvironment);
|
||||
DataHubPageModule result = future.get();
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_MODULE_URN);
|
||||
assertEquals(result.getType().toString(), "DATAHUB_PAGE_MODULE");
|
||||
verify(mockService, times(1)).upsertPageModule(any(), eq(null), any(), any(), any(), any());
|
||||
verify(mockService, times(1)).getPageModuleEntityResponse(any(), eq(moduleUrn));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleWithLinkParams() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.LINK);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
paramsInput.setLinkParams(new LinkModuleParamsInput());
|
||||
paramsInput.getLinkParams().setLinkUrn("urn:li:post:test-post");
|
||||
input.setParams(paramsInput);
|
||||
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
EntityResponse mockResponse = createMockEntityResponse(moduleUrn);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
when(mockService.upsertPageModule(any(), any(), any(), any(), any(), any()))
|
||||
.thenReturn(moduleUrn);
|
||||
when(mockService.getPageModuleEntityResponse(any(), eq(moduleUrn))).thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
CompletableFuture<DataHubPageModule> future = resolver.get(mockEnvironment);
|
||||
DataHubPageModule result = future.get();
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_MODULE_URN);
|
||||
verify(mockService, times(1)).upsertPageModule(any(), any(), any(), any(), any(), any());
|
||||
verify(mockService, times(1)).getPageModuleEntityResponse(any(), eq(moduleUrn));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleValidationFailureRichTextWithoutParams() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.RICH_TEXT);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
// Don't set rich text params
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
input.setParams(paramsInput);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(RuntimeException.class, () -> resolver.get(mockEnvironment).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleValidationFailureLinkWithoutParams() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.LINK);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
// Don't set link params
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
input.setParams(paramsInput);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(RuntimeException.class, () -> resolver.get(mockEnvironment).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleValidationFailureUnsupportedModuleType() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.ASSET_COLLECTION); // Unsupported type
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
input.setParams(paramsInput);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(RuntimeException.class, () -> resolver.get(mockEnvironment).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleValidationFailureRichTextWithWrongParams() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.RICH_TEXT);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
// Set link params instead of rich text params
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
paramsInput.setLinkParams(new LinkModuleParamsInput());
|
||||
paramsInput.getLinkParams().setLinkUrn("urn:li:post:test-post");
|
||||
input.setParams(paramsInput);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(RuntimeException.class, () -> resolver.get(mockEnvironment).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleValidationFailureLinkWithWrongParams() throws Exception {
|
||||
// Arrange
|
||||
UpsertPageModuleInput input = new UpsertPageModuleInput();
|
||||
input.setName(TEST_MODULE_NAME);
|
||||
input.setType(DataHubPageModuleType.LINK);
|
||||
input.setScope(PageModuleScope.PERSONAL);
|
||||
|
||||
// Set rich text params instead of link params
|
||||
PageModuleParamsInput paramsInput = new PageModuleParamsInput();
|
||||
RichTextModuleParamsInput richTextParams = new RichTextModuleParamsInput();
|
||||
richTextParams.setContent(TEST_RICH_TEXT_CONTENT);
|
||||
paramsInput.setRichTextParams(richTextParams);
|
||||
input.setParams(paramsInput);
|
||||
|
||||
when(mockEnvironment.getArgument("input")).thenReturn(input);
|
||||
when(mockEnvironment.getContext()).thenReturn(mockQueryContext);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(RuntimeException.class, () -> resolver.get(mockEnvironment).join());
|
||||
}
|
||||
|
||||
private EntityResponse createMockEntityResponse(Urn moduleUrn) {
|
||||
DataHubPageModuleProperties properties = new DataHubPageModuleProperties();
|
||||
properties.setName(TEST_MODULE_NAME);
|
||||
properties.setType(com.linkedin.module.DataHubPageModuleType.RICH_TEXT);
|
||||
|
||||
com.linkedin.module.PageModuleScope gmsScope = com.linkedin.module.PageModuleScope.PERSONAL;
|
||||
com.linkedin.module.DataHubPageModuleVisibility visibility =
|
||||
new com.linkedin.module.DataHubPageModuleVisibility();
|
||||
visibility.setScope(gmsScope);
|
||||
properties.setVisibility(visibility);
|
||||
|
||||
DataHubPageModuleParams params = new DataHubPageModuleParams();
|
||||
RichTextModuleParams richTextParams = new RichTextModuleParams();
|
||||
richTextParams.setContent(TEST_RICH_TEXT_CONTENT);
|
||||
params.setRichTextParams(richTextParams);
|
||||
properties.setParams(params);
|
||||
|
||||
AuditStamp auditStamp = new AuditStamp();
|
||||
auditStamp.setTime(System.currentTimeMillis());
|
||||
auditStamp.setActor(UrnUtils.getUrn("urn:li:corpuser:test-user"));
|
||||
properties.setCreated(auditStamp);
|
||||
properties.setLastModified(auditStamp);
|
||||
|
||||
EntityResponse response = new EntityResponse();
|
||||
response.setUrn(moduleUrn);
|
||||
|
||||
EnvelopedAspectMap aspectMap = new EnvelopedAspectMap();
|
||||
EnvelopedAspect aspect = new EnvelopedAspect();
|
||||
aspect.setValue(new com.linkedin.entity.Aspect(properties.data()));
|
||||
aspectMap.put(Constants.DATAHUB_PAGE_MODULE_PROPERTIES_ASPECT_NAME, aspect);
|
||||
response.setAspects(aspectMap);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@ -41,6 +41,7 @@ import com.linkedin.metadata.service.ERModelRelationshipService;
|
||||
import com.linkedin.metadata.service.FormService;
|
||||
import com.linkedin.metadata.service.LineageService;
|
||||
import com.linkedin.metadata.service.OwnershipTypeService;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import com.linkedin.metadata.service.PageTemplateService;
|
||||
import com.linkedin.metadata.service.QueryService;
|
||||
import com.linkedin.metadata.service.SettingsService;
|
||||
@ -214,6 +215,10 @@ public class GraphQLEngineFactory {
|
||||
@Qualifier("pageTemplateService")
|
||||
private PageTemplateService pageTemplateService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("pageModuleService")
|
||||
private PageModuleService pageModuleService;
|
||||
|
||||
@Bean(name = "graphQLEngine")
|
||||
@Nonnull
|
||||
protected GraphQLEngine graphQLEngine(
|
||||
@ -270,6 +275,7 @@ public class GraphQLEngineFactory {
|
||||
args.setDataProductService(dataProductService);
|
||||
args.setApplicationService(applicationService);
|
||||
args.setPageTemplateService(pageTemplateService);
|
||||
args.setPageModuleService(pageModuleService);
|
||||
args.setGraphQLConfiguration(configProvider.getGraphQL());
|
||||
args.setBusinessAttributeService(businessAttributeService);
|
||||
args.setChromeExtensionConfiguration(configProvider.getChromeExtension());
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package com.linkedin.gms.factory.module;
|
||||
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.metadata.service.PageModuleService;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class PageModuleServiceFactory {
|
||||
@Bean(name = "pageModuleService")
|
||||
@Nonnull
|
||||
protected PageModuleService getInstance(
|
||||
@Qualifier("entityClient") @Nonnull final EntityClient entityClient) {
|
||||
return new PageModuleService(entityClient);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package com.linkedin.metadata.service;
|
||||
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.entity.AspectUtils;
|
||||
import com.linkedin.metadata.key.DataHubPageModuleKey;
|
||||
import com.linkedin.metadata.utils.EntityKeyUtils;
|
||||
import com.linkedin.module.DataHubPageModuleParams;
|
||||
import com.linkedin.module.DataHubPageModuleProperties;
|
||||
import com.linkedin.module.DataHubPageModuleType;
|
||||
import com.linkedin.module.DataHubPageModuleVisibility;
|
||||
import com.linkedin.module.PageModuleScope;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import io.datahubproject.metadata.context.OperationContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PageModuleService {
|
||||
private final EntityClient entityClient;
|
||||
|
||||
public PageModuleService(@Nonnull EntityClient entityClient) {
|
||||
this.entityClient = entityClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts a DataHub page module. If the page module with the provided urn already exists, then it
|
||||
* will be overwritten.
|
||||
*
|
||||
* <p>This method assumes that authorization has already been verified at the calling layer.
|
||||
*
|
||||
* @return the URN of the new page module.
|
||||
*/
|
||||
public Urn upsertPageModule(
|
||||
@Nonnull OperationContext opContext,
|
||||
@Nullable final String urn,
|
||||
@Nonnull final String name,
|
||||
@Nonnull final DataHubPageModuleType type,
|
||||
@Nonnull final PageModuleScope scope,
|
||||
@Nonnull final DataHubPageModuleParams params) {
|
||||
Objects.requireNonNull(name, "name must not be null");
|
||||
Objects.requireNonNull(type, "type must not be null");
|
||||
Objects.requireNonNull(scope, "scope must not be null");
|
||||
Objects.requireNonNull(params, "params must not be null");
|
||||
|
||||
// 1. Optionally generate new page module urn
|
||||
Urn moduleUrn = null;
|
||||
if (urn != null) {
|
||||
moduleUrn = UrnUtils.getUrn(urn);
|
||||
} else {
|
||||
final String moduleId = UUID.randomUUID().toString();
|
||||
final DataHubPageModuleKey key = new DataHubPageModuleKey().setId(moduleId);
|
||||
moduleUrn =
|
||||
EntityKeyUtils.convertEntityKeyToUrn(key, Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME);
|
||||
}
|
||||
final AuditStamp nowAuditStamp = opContext.getAuditStamp();
|
||||
|
||||
// 2. Build Page Module Properties
|
||||
DataHubPageModuleProperties properties = new DataHubPageModuleProperties();
|
||||
DataHubPageModuleProperties existingProperties = getPageModuleProperties(opContext, moduleUrn);
|
||||
if (existingProperties != null) {
|
||||
properties = existingProperties;
|
||||
} else {
|
||||
// if creating a new page module, set the created stamp
|
||||
properties.setCreated(nowAuditStamp);
|
||||
}
|
||||
|
||||
properties.setName(name);
|
||||
properties.setType(type);
|
||||
|
||||
DataHubPageModuleVisibility visibility = new DataHubPageModuleVisibility();
|
||||
visibility.setScope(scope);
|
||||
properties.setVisibility(visibility);
|
||||
|
||||
properties.setParams(params);
|
||||
properties.setLastModified(nowAuditStamp);
|
||||
|
||||
// 3. Write changes to GMS
|
||||
try {
|
||||
final List<MetadataChangeProposal> aspectsToIngest = new ArrayList<>();
|
||||
aspectsToIngest.add(
|
||||
AspectUtils.buildMetadataChangeProposal(
|
||||
moduleUrn, Constants.DATAHUB_PAGE_MODULE_PROPERTIES_ASPECT_NAME, properties));
|
||||
entityClient.batchIngestProposals(opContext, aspectsToIngest, false);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
String.format("Failed to upsert PageModule with urn %s", moduleUrn), e);
|
||||
}
|
||||
return moduleUrn;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DataHubPageModuleProperties getPageModuleProperties(
|
||||
@Nonnull OperationContext opContext, @Nonnull final Urn moduleUrn) {
|
||||
Objects.requireNonNull(moduleUrn, "moduleUrn must not be null");
|
||||
final EntityResponse response = getPageModuleEntityResponse(opContext, moduleUrn);
|
||||
if (response != null
|
||||
&& response
|
||||
.getAspects()
|
||||
.containsKey(Constants.DATAHUB_PAGE_MODULE_PROPERTIES_ASPECT_NAME)) {
|
||||
return new DataHubPageModuleProperties(
|
||||
response
|
||||
.getAspects()
|
||||
.get(Constants.DATAHUB_PAGE_MODULE_PROPERTIES_ASPECT_NAME)
|
||||
.getValue()
|
||||
.data());
|
||||
}
|
||||
// No aspect found
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EntityResponse getPageModuleEntityResponse(
|
||||
@Nonnull OperationContext opContext, @Nonnull final Urn moduleUrn) {
|
||||
try {
|
||||
return entityClient.getV2(
|
||||
opContext, Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME, moduleUrn, null, false);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
String.format("Failed to retrieve PageModule with urn %s", moduleUrn), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
package com.linkedin.metadata.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.module.DataHubPageModuleParams;
|
||||
import com.linkedin.module.DataHubPageModuleProperties;
|
||||
import com.linkedin.module.DataHubPageModuleType;
|
||||
import com.linkedin.module.DataHubPageModuleVisibility;
|
||||
import com.linkedin.module.PageModuleScope;
|
||||
import com.linkedin.module.RichTextModuleParams;
|
||||
import io.datahubproject.metadata.context.OperationContext;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class PageModuleServiceTest {
|
||||
|
||||
private static final String TEST_MODULE_URN = "urn:li:dataHubPageModule:test-module";
|
||||
private static final String TEST_MODULE_NAME = "Test Module";
|
||||
private static final String TEST_RICH_TEXT_CONTENT = "Test content";
|
||||
|
||||
@Mock private EntityClient mockEntityClient;
|
||||
@Mock private OperationContext mockOpContext;
|
||||
|
||||
private PageModuleService service;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
service = new PageModuleService(mockEntityClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleSuccessWithUrn() throws Exception {
|
||||
// Arrange
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
DataHubPageModuleType type = DataHubPageModuleType.RICH_TEXT;
|
||||
PageModuleScope scope = PageModuleScope.PERSONAL;
|
||||
DataHubPageModuleParams params = createTestParams();
|
||||
|
||||
when(mockOpContext.getAuditStamp()).thenReturn(createTestAuditStamp());
|
||||
when(mockEntityClient.batchIngestProposals(any(), any(), eq(false))).thenReturn(null);
|
||||
|
||||
// Act
|
||||
Urn result =
|
||||
service.upsertPageModule(
|
||||
mockOpContext, TEST_MODULE_URN, TEST_MODULE_NAME, type, scope, params);
|
||||
|
||||
// Assert
|
||||
assertEquals(result, moduleUrn);
|
||||
verify(mockEntityClient, times(1)).batchIngestProposals(any(), any(), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleSuccessWithGeneratedUrn() throws Exception {
|
||||
// Arrange
|
||||
DataHubPageModuleType type = DataHubPageModuleType.RICH_TEXT;
|
||||
PageModuleScope scope = PageModuleScope.PERSONAL;
|
||||
DataHubPageModuleParams params = createTestParams();
|
||||
|
||||
when(mockOpContext.getAuditStamp()).thenReturn(createTestAuditStamp());
|
||||
when(mockEntityClient.batchIngestProposals(any(), any(), eq(false))).thenReturn(null);
|
||||
|
||||
// Act
|
||||
Urn result =
|
||||
service.upsertPageModule(mockOpContext, null, TEST_MODULE_NAME, type, scope, params);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getEntityType(), "dataHubPageModule");
|
||||
verify(mockEntityClient, times(1)).batchIngestProposals(any(), any(), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpsertPageModuleFailure() throws Exception {
|
||||
// Arrange
|
||||
DataHubPageModuleType type = DataHubPageModuleType.RICH_TEXT;
|
||||
PageModuleScope scope = PageModuleScope.PERSONAL;
|
||||
DataHubPageModuleParams params = createTestParams();
|
||||
|
||||
when(mockOpContext.getAuditStamp()).thenReturn(createTestAuditStamp());
|
||||
when(mockEntityClient.batchIngestProposals(any(), any(), eq(false)))
|
||||
.thenThrow(new RuntimeException("Test exception"));
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> {
|
||||
service.upsertPageModule(
|
||||
mockOpContext, TEST_MODULE_URN, TEST_MODULE_NAME, type, scope, params);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPageModulePropertiesSuccess() throws Exception {
|
||||
// Arrange
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
DataHubPageModuleProperties expectedProperties = createTestModuleProperties();
|
||||
EntityResponse mockResponse = createMockEntityResponse(moduleUrn, expectedProperties);
|
||||
|
||||
when(mockEntityClient.getV2(
|
||||
any(),
|
||||
eq(Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME),
|
||||
eq(moduleUrn),
|
||||
eq(null),
|
||||
eq(false)))
|
||||
.thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
DataHubPageModuleProperties result = service.getPageModuleProperties(mockOpContext, moduleUrn);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getName(), TEST_MODULE_NAME);
|
||||
assertEquals(result.getType(), DataHubPageModuleType.RICH_TEXT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPageModulePropertiesNotFound() throws Exception {
|
||||
// Arrange
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
|
||||
when(mockEntityClient.getV2(
|
||||
any(),
|
||||
eq(Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME),
|
||||
eq(moduleUrn),
|
||||
eq(null),
|
||||
eq(false)))
|
||||
.thenReturn(null);
|
||||
|
||||
// Act
|
||||
DataHubPageModuleProperties result = service.getPageModuleProperties(mockOpContext, moduleUrn);
|
||||
|
||||
// Assert
|
||||
assertEquals(result, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPageModuleEntityResponseSuccess() throws Exception {
|
||||
// Arrange
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
EntityResponse expectedResponse =
|
||||
createMockEntityResponse(moduleUrn, createTestModuleProperties());
|
||||
|
||||
when(mockEntityClient.getV2(
|
||||
any(),
|
||||
eq(Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME),
|
||||
eq(moduleUrn),
|
||||
eq(null),
|
||||
eq(false)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
// Act
|
||||
EntityResponse result = service.getPageModuleEntityResponse(mockOpContext, moduleUrn);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), moduleUrn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPageModuleEntityResponseFailure() throws Exception {
|
||||
// Arrange
|
||||
Urn moduleUrn = UrnUtils.getUrn(TEST_MODULE_URN);
|
||||
|
||||
when(mockEntityClient.getV2(
|
||||
any(),
|
||||
eq(Constants.DATAHUB_PAGE_MODULE_ENTITY_NAME),
|
||||
eq(moduleUrn),
|
||||
eq(null),
|
||||
eq(false)))
|
||||
.thenThrow(new RuntimeException("Test exception"));
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> {
|
||||
service.getPageModuleEntityResponse(mockOpContext, moduleUrn);
|
||||
});
|
||||
}
|
||||
|
||||
private DataHubPageModuleParams createTestParams() {
|
||||
DataHubPageModuleParams params = new DataHubPageModuleParams();
|
||||
RichTextModuleParams richTextParams = new RichTextModuleParams();
|
||||
richTextParams.setContent(TEST_RICH_TEXT_CONTENT);
|
||||
params.setRichTextParams(richTextParams);
|
||||
return params;
|
||||
}
|
||||
|
||||
private DataHubPageModuleProperties createTestModuleProperties() {
|
||||
DataHubPageModuleProperties properties = new DataHubPageModuleProperties();
|
||||
properties.setName(TEST_MODULE_NAME);
|
||||
properties.setType(DataHubPageModuleType.RICH_TEXT);
|
||||
|
||||
DataHubPageModuleVisibility visibility = new DataHubPageModuleVisibility();
|
||||
visibility.setScope(PageModuleScope.PERSONAL);
|
||||
properties.setVisibility(visibility);
|
||||
|
||||
properties.setParams(createTestParams());
|
||||
properties.setCreated(createTestAuditStamp());
|
||||
properties.setLastModified(createTestAuditStamp());
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private AuditStamp createTestAuditStamp() {
|
||||
AuditStamp auditStamp = new AuditStamp();
|
||||
auditStamp.setTime(System.currentTimeMillis());
|
||||
auditStamp.setActor(UrnUtils.getUrn("urn:li:corpuser:test-user"));
|
||||
return auditStamp;
|
||||
}
|
||||
|
||||
private EntityResponse createMockEntityResponse(
|
||||
Urn moduleUrn, DataHubPageModuleProperties properties) {
|
||||
EntityResponse response = new EntityResponse();
|
||||
response.setUrn(moduleUrn);
|
||||
|
||||
EnvelopedAspectMap aspectMap = new EnvelopedAspectMap();
|
||||
EnvelopedAspect aspect = new EnvelopedAspect();
|
||||
aspect.setValue(new com.linkedin.entity.Aspect(properties.data()));
|
||||
aspectMap.put(Constants.DATAHUB_PAGE_MODULE_PROPERTIES_ASPECT_NAME, aspect);
|
||||
response.setAspects(aspectMap);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user