diff --git a/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/FormInfoPatchBuilder.java b/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/FormInfoPatchBuilder.java new file mode 100644 index 00000000000000..13d9c07b04aeef --- /dev/null +++ b/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/FormInfoPatchBuilder.java @@ -0,0 +1,154 @@ +package com.linkedin.metadata.aspect.patch.builder; + +import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance; +import static com.linkedin.metadata.Constants.*; + +import com.datahub.util.RecordUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.linkedin.form.FormPrompt; +import com.linkedin.form.FormType; +import com.linkedin.metadata.aspect.patch.PatchOperationType; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutableTriple; + +public class FormInfoPatchBuilder extends AbstractMultiFieldPatchBuilder { + + public static final String PATH_DELIM = "/"; + public static final String NAME_FIELD = "name"; + public static final String DESCRIPTION_FIELD = "description"; + public static final String TYPE_FIELD = "type"; + public static final String PROMPTS_FIELD = "prompts"; + public static final String ACTORS_FIELD = "actors"; + public static final String OWNERS_FIELD = "owners"; + public static final String USERS_FIELD = "users"; + public static final String GROUPS_FIELD = "groups"; + + public FormInfoPatchBuilder setName(@Nonnull String name) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), PATH_DELIM + NAME_FIELD, instance.textNode(name))); + return this; + } + + public FormInfoPatchBuilder setDescription(@Nullable String description) { + if (description == null) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.REMOVE.getValue(), PATH_DELIM + DESCRIPTION_FIELD, null)); + } else { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + DESCRIPTION_FIELD, + instance.textNode(description))); + } + return this; + } + + public FormInfoPatchBuilder setType(@Nonnull FormType formType) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + TYPE_FIELD, + instance.textNode(formType.toString()))); + return this; + } + + public FormInfoPatchBuilder addPrompt(@Nonnull FormPrompt prompt) { + try { + ObjectNode promptNode = + (ObjectNode) new ObjectMapper().readTree(RecordUtils.toJsonString(prompt)); + pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + PROMPTS_FIELD + PATH_DELIM + prompt.getId(), + promptNode)); + return this; + } catch (JsonProcessingException e) { + throw new IllegalArgumentException( + "Failed to add prompt, failed to parse provided aspect json.", e); + } + } + + public FormInfoPatchBuilder addPrompts(@Nonnull List prompts) { + try { + prompts.forEach(this::addPrompt); + return this; + } catch (Exception e) { + throw new IllegalArgumentException("Failed to add prompts.", e); + } + } + + public FormInfoPatchBuilder removePrompt(@Nonnull String promptId) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.REMOVE.getValue(), + PATH_DELIM + PROMPTS_FIELD + PATH_DELIM + promptId, + null)); + return this; + } + + public FormInfoPatchBuilder removePrompts(@Nonnull List promptIds) { + promptIds.forEach(this::removePrompt); + return this; + } + + public FormInfoPatchBuilder setOwnershipForm(boolean isOwnershipForm) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + ACTORS_FIELD + PATH_DELIM + OWNERS_FIELD, + instance.booleanNode(isOwnershipForm))); + return this; + } + + public FormInfoPatchBuilder addAssignedUser(@Nonnull String userUrn) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + ACTORS_FIELD + PATH_DELIM + USERS_FIELD + PATH_DELIM + userUrn, + instance.textNode(userUrn))); + return this; + } + + public FormInfoPatchBuilder removeAssignedUser(@Nonnull String userUrn) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.REMOVE.getValue(), + PATH_DELIM + ACTORS_FIELD + PATH_DELIM + USERS_FIELD + PATH_DELIM + userUrn, + instance.textNode(userUrn))); + return this; + } + + public FormInfoPatchBuilder addAssignedGroup(@Nonnull String groupUrn) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), + PATH_DELIM + ACTORS_FIELD + PATH_DELIM + GROUPS_FIELD + PATH_DELIM + groupUrn, + instance.textNode(groupUrn))); + return this; + } + + public FormInfoPatchBuilder removeAssignedGroup(@Nonnull String groupUrn) { + this.pathValues.add( + ImmutableTriple.of( + PatchOperationType.REMOVE.getValue(), + PATH_DELIM + ACTORS_FIELD + PATH_DELIM + GROUPS_FIELD + PATH_DELIM + groupUrn, + instance.textNode(groupUrn))); + return this; + } + + @Override + protected String getAspectName() { + return FORM_INFO_ASPECT_NAME; + } + + @Override + protected String getEntityType() { + return FORM_ENTITY_NAME; + } +} diff --git a/metadata-integration/java/datahub-client/src/test/java/datahub/client/patch/PatchTest.java b/metadata-integration/java/datahub-client/src/test/java/datahub/client/patch/PatchTest.java index 46d1481836fe72..1d7063c81da882 100644 --- a/metadata-integration/java/datahub-client/src/test/java/datahub/client/patch/PatchTest.java +++ b/metadata-integration/java/datahub-client/src/test/java/datahub/client/patch/PatchTest.java @@ -18,6 +18,9 @@ import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.UrnUtils; import com.linkedin.dataset.DatasetLineageType; +import com.linkedin.form.FormPrompt; +import com.linkedin.form.FormPromptType; +import com.linkedin.form.StructuredPropertyParams; import com.linkedin.metadata.aspect.patch.builder.ChartInfoPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.DashboardInfoPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.DataFlowInfoPatchBuilder; @@ -25,6 +28,7 @@ import com.linkedin.metadata.aspect.patch.builder.DataJobInputOutputPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.DatasetPropertiesPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.EditableSchemaMetadataPatchBuilder; +import com.linkedin.metadata.aspect.patch.builder.FormInfoPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.OwnershipPatchBuilder; import com.linkedin.metadata.aspect.patch.builder.UpstreamLineagePatchBuilder; import com.linkedin.metadata.graph.LineageDirection; @@ -37,6 +41,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.Arrays; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.junit.Ignore; @@ -641,4 +646,70 @@ public void testLocalDashboardInfoRemove() { System.out.println(Arrays.asList(e.getStackTrace())); } } + + @Test + @Ignore + public void testLocalFormInfoAdd() { + RestEmitter restEmitter = new RestEmitter(RestEmitterConfig.builder().build()); + try { + FormPrompt newPrompt = + new FormPrompt() + .setId("1234") + .setTitle("First Prompt") + .setType(FormPromptType.STRUCTURED_PROPERTY) + .setRequired(true) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + FormPrompt newPrompt2 = + new FormPrompt() + .setId("abcd") + .setTitle("Second Prompt") + .setType(FormPromptType.FIELDS_STRUCTURED_PROPERTY) + .setRequired(false) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + + MetadataChangeProposal formInfoPatch = + new FormInfoPatchBuilder() + .urn(UrnUtils.getUrn("urn:li:form:123456")) + .addPrompts(List.of(newPrompt, newPrompt2)) + .setName("Metadata Initiative 2024 (edited)") + .setDescription("Edited description") + .setOwnershipForm(true) + .addAssignedUser("urn:li:corpuser:admin") + .addAssignedGroup("urn:li:corpGroup:jdoe") + .build(); + Future response = restEmitter.emit(formInfoPatch); + + System.out.println(response.get().getResponseContent()); + + } catch (IOException | ExecutionException | InterruptedException e) { + System.out.println(Arrays.asList(e.getStackTrace())); + } + } + + @Test + @Ignore + public void testLocalFormInfoRemove() { + RestEmitter restEmitter = new RestEmitter(RestEmitterConfig.builder().build()); + try { + MetadataChangeProposal formInfoPatch = + new FormInfoPatchBuilder() + .urn(UrnUtils.getUrn("urn:li:form:123456")) + .removePrompts(List.of("1234", "abcd")) + .setName("Metadata Initiative 2024 (edited - again)") + .setDescription(null) + .removeAssignedUser("urn:li:corpuser:admin") + .removeAssignedGroup("urn:li:corpGroup:jdoe") + .build(); + Future response = restEmitter.emit(formInfoPatch); + + System.out.println(response.get().getResponseContent()); + + } catch (IOException | ExecutionException | InterruptedException e) { + System.out.println(Arrays.asList(e.getStackTrace())); + } + } } diff --git a/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormCreate.java b/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormCreate.java new file mode 100644 index 00000000000000..5451d431b3b994 --- /dev/null +++ b/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormCreate.java @@ -0,0 +1,68 @@ +package io.datahubproject.examples; + +import com.linkedin.common.urn.UrnUtils; +import com.linkedin.form.FormActorAssignment; +import com.linkedin.form.FormInfo; +import com.linkedin.form.FormPrompt; +import com.linkedin.form.FormPromptArray; +import com.linkedin.form.FormPromptType; +import com.linkedin.form.FormType; +import com.linkedin.form.StructuredPropertyParams; +import datahub.client.MetadataWriteResponse; +import datahub.client.rest.RestEmitter; +import datahub.event.MetadataChangeProposalWrapper; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class FormCreate { + + private FormCreate() {} + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + FormPromptArray prompts = new FormPromptArray(); + FormPrompt prompt1 = + new FormPrompt() + .setId("1") + .setTitle("First Prompt") + .setType(FormPromptType.STRUCTURED_PROPERTY) + .setRequired(true) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + FormPrompt prompt2 = + new FormPrompt() + .setId("2") + .setTitle("Second Prompt") + .setType(FormPromptType.FIELDS_STRUCTURED_PROPERTY) + .setRequired(false) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + prompts.add(prompt1); + prompts.add(prompt2); + + FormInfo formInfo = + new FormInfo() + .setName("Metadata Initiative 2024") + .setDescription("Please respond to this form for metadata compliance purposes.") + .setType(FormType.VERIFICATION) + .setPrompts(prompts) + .setActors(new FormActorAssignment().setOwners(true)); + + MetadataChangeProposalWrapper mcpw = + MetadataChangeProposalWrapper.builder() + .entityType("form") + .entityUrn("urn:li:form:metadata_initiative_1") + .upsert() + .aspect(formInfo) + .aspectName("formInfo") + .build(); + + String token = ""; + RestEmitter emitter = RestEmitter.create(b -> b.server("http://localhost:8080").token(token)); + Future response = emitter.emit(mcpw, null); + System.out.println(response.get().getResponseContent()); + } +} diff --git a/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormUpdate.java b/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormUpdate.java new file mode 100644 index 00000000000000..d986f8d403ef65 --- /dev/null +++ b/metadata-integration/java/examples/src/main/java/io/datahubproject/examples/FormUpdate.java @@ -0,0 +1,57 @@ +package io.datahubproject.examples; + +import com.linkedin.common.urn.Urn; +import com.linkedin.common.urn.UrnUtils; +import com.linkedin.form.FormPrompt; +import com.linkedin.form.FormPromptType; +import com.linkedin.form.StructuredPropertyParams; +import com.linkedin.metadata.aspect.patch.builder.FormInfoPatchBuilder; +import com.linkedin.mxe.MetadataChangeProposal; +import datahub.client.MetadataWriteResponse; +import datahub.client.rest.RestEmitter; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class FormUpdate { + + private FormUpdate() {} + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + FormPrompt newPrompt = + new FormPrompt() + .setId("1234") + .setTitle("First Prompt") + .setType(FormPromptType.STRUCTURED_PROPERTY) + .setRequired(true) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + FormPrompt newPrompt2 = + new FormPrompt() + .setId("abcd") + .setTitle("Second Prompt") + .setType(FormPromptType.FIELDS_STRUCTURED_PROPERTY) + .setRequired(false) + .setStructuredPropertyParams( + new StructuredPropertyParams() + .setUrn(UrnUtils.getUrn("urn:li:structuredProperty:property1"))); + + Urn formUrn = UrnUtils.getUrn("urn:li:form:metadata_initiative_1"); + FormInfoPatchBuilder formInfoPatchBuilder = + new FormInfoPatchBuilder() + .urn(formUrn) + .addPrompts(List.of(newPrompt, newPrompt2)) + .setName("Metadata Initiative 2024 (edited)") + .setDescription("Edited description") + .setOwnershipForm(true); + MetadataChangeProposal mcp = formInfoPatchBuilder.build(); + + String token = ""; + RestEmitter emitter = RestEmitter.create(b -> b.server("http://localhost:8080").token(token)); + Future response = emitter.emit(mcp, null); + System.out.println(response.get().getResponseContent()); + } +}