diff --git a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java index fb498e28b..0f582a479 100644 --- a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java +++ b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java @@ -22,6 +22,7 @@ import com.google.common.base.Objects; import io.temporal.client.WorkflowClientOptions; +import io.temporal.common.Experimental; import io.temporal.common.MethodRetry; import io.temporal.common.RetryOptions; import io.temporal.common.VersioningIntent; @@ -64,6 +65,7 @@ public static final class Builder { private ActivityCancellationType cancellationType; private boolean disableEagerExecution; private VersioningIntent versioningIntent; + private String summary; private Builder() {} @@ -81,6 +83,7 @@ private Builder(ActivityOptions options) { this.cancellationType = options.cancellationType; this.disableEagerExecution = options.disableEagerExecution; this.versioningIntent = options.versioningIntent; + this.summary = options.summary; } /** @@ -243,6 +246,18 @@ public Builder setVersioningIntent(VersioningIntent versioningIntent) { return this; } + /** + * Single-line fixed summary for this activity that will appear in UI/CLI. This can be in + * single-line Temporal Markdown format. + * + *

Default is none/empty. + */ + @Experimental + public Builder setSummary(String summary) { + this.summary = summary; + return this; + } + public Builder mergeActivityOptions(ActivityOptions override) { if (override == null) { return this; @@ -274,6 +289,7 @@ public Builder mergeActivityOptions(ActivityOptions override) { if (override.versioningIntent != VersioningIntent.VERSIONING_INTENT_UNSPECIFIED) { this.versioningIntent = override.versioningIntent; } + this.summary = (override.summary == null) ? this.summary : override.summary; return this; } @@ -296,7 +312,8 @@ public ActivityOptions build() { contextPropagators, cancellationType, disableEagerExecution, - versioningIntent); + versioningIntent, + summary); } public ActivityOptions validateAndBuildWithDefaults() { @@ -312,7 +329,8 @@ public ActivityOptions validateAndBuildWithDefaults() { disableEagerExecution, versioningIntent == null ? VersioningIntent.VERSIONING_INTENT_UNSPECIFIED - : versioningIntent); + : versioningIntent, + summary); } } @@ -326,6 +344,7 @@ public ActivityOptions validateAndBuildWithDefaults() { private final ActivityCancellationType cancellationType; private final boolean disableEagerExecution; private final VersioningIntent versioningIntent; + private final String summary; private ActivityOptions( Duration heartbeatTimeout, @@ -337,7 +356,8 @@ private ActivityOptions( List contextPropagators, ActivityCancellationType cancellationType, boolean disableEagerExecution, - VersioningIntent versioningIntent) { + VersioningIntent versioningIntent, + String summary) { this.heartbeatTimeout = heartbeatTimeout; this.scheduleToStartTimeout = scheduleToStartTimeout; this.scheduleToCloseTimeout = scheduleToCloseTimeout; @@ -348,6 +368,7 @@ private ActivityOptions( this.cancellationType = cancellationType; this.disableEagerExecution = disableEagerExecution; this.versioningIntent = versioningIntent; + this.summary = summary; } /** @@ -417,6 +438,11 @@ public VersioningIntent getVersioningIntent() { return versioningIntent; } + @Experimental + public String getSummary() { + return summary; + } + public Builder toBuilder() { return new Builder(this); } @@ -435,7 +461,8 @@ public boolean equals(Object o) { && Objects.equal(retryOptions, that.retryOptions) && Objects.equal(contextPropagators, that.contextPropagators) && disableEagerExecution == that.disableEagerExecution - && versioningIntent == that.versioningIntent; + && versioningIntent == that.versioningIntent + && Objects.equal(summary, that.summary); } @Override @@ -450,7 +477,8 @@ public int hashCode() { contextPropagators, cancellationType, disableEagerExecution, - versioningIntent); + versioningIntent, + summary); } @Override @@ -477,6 +505,8 @@ public String toString() { + disableEagerExecution + ", versioningIntent=" + versioningIntent + + ", summary=" + + summary + '}'; } } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ActivityStateMachine.java b/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ActivityStateMachine.java index 7ca5b49c7..8a6d695ec 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ActivityStateMachine.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ActivityStateMachine.java @@ -35,6 +35,7 @@ import io.temporal.api.history.v1.ActivityTaskCompletedEventAttributes; import io.temporal.api.history.v1.ActivityTaskFailedEventAttributes; import io.temporal.api.history.v1.ActivityTaskTimedOutEventAttributes; +import io.temporal.api.sdk.v1.UserMetadata; import io.temporal.workflow.Functions; import java.util.Optional; import javax.annotation.Nonnull; @@ -54,6 +55,7 @@ final class ActivityStateMachine private final String activityId; private final ActivityType activityType; private final ActivityCancellationType cancellationType; + private UserMetadata userMetadata; private final Functions.Proc2, FailureResult> completionCallback; @@ -265,16 +267,21 @@ private ActivityStateMachine( this.activityId = scheduleAttr.getActivityId(); this.activityType = scheduleAttr.getActivityType(); this.cancellationType = parameters.getCancellationType(); + this.userMetadata = parameters.getMetadata(); this.completionCallback = completionCallback; explicitEvent(ExplicitEvent.SCHEDULE); } public void createScheduleActivityTaskCommand() { - addCommand( + Command.Builder command = Command.newBuilder() .setCommandType(CommandType.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK) - .setScheduleActivityTaskCommandAttributes(parameters.getAttributes()) - .build()); + .setScheduleActivityTaskCommandAttributes(parameters.getAttributes()); + if (userMetadata != null) { + command.setUserMetadata(userMetadata); + userMetadata = null; + } + addCommand(command.build()); parameters = null; // avoiding retaining large input for the duration of the activity } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ExecuteActivityParameters.java b/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ExecuteActivityParameters.java index b9ec661e9..3776f0ffb 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ExecuteActivityParameters.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/statemachines/ExecuteActivityParameters.java @@ -22,18 +22,22 @@ import io.temporal.activity.ActivityCancellationType; import io.temporal.api.command.v1.ScheduleActivityTaskCommandAttributes; +import io.temporal.api.sdk.v1.UserMetadata; import java.util.Objects; public class ExecuteActivityParameters { private final ScheduleActivityTaskCommandAttributes.Builder attributes; private final ActivityCancellationType cancellationType; + private final UserMetadata metadata; public ExecuteActivityParameters( ScheduleActivityTaskCommandAttributes.Builder attributes, - ActivityCancellationType cancellationType) { + ActivityCancellationType cancellationType, + UserMetadata metadata) { this.attributes = Objects.requireNonNull(attributes); this.cancellationType = Objects.requireNonNull(cancellationType); + this.metadata = metadata; } public ScheduleActivityTaskCommandAttributes.Builder getAttributes() { @@ -43,4 +47,8 @@ public ScheduleActivityTaskCommandAttributes.Builder getAttributes() { public ActivityCancellationType getCancellationType() { return cancellationType; } + + public UserMetadata getMetadata() { + return metadata; + } } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java index dbc77b144..1fcbf59d0 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java @@ -625,7 +625,11 @@ private ExecuteActivityParameters constructExecuteActivityParameters( replayContext.getTaskQueue().equals(options.getTaskQueue()))); } - return new ExecuteActivityParameters(attributes, options.getCancellationType()); + @Nullable + UserMetadata userMetadata = + makeUserMetaData(options.getSummary(), null, dataConverterWithCurrentWorkflowContext); + + return new ExecuteActivityParameters(attributes, options.getCancellationType(), userMetadata); } private ExecuteLocalActivityParameters constructExecuteLocalActivityParameters( diff --git a/temporal-sdk/src/test/java/io/temporal/internal/statemachines/ActivityStateMachineTest.java b/temporal-sdk/src/test/java/io/temporal/internal/statemachines/ActivityStateMachineTest.java index b360ff691..d2b6ed46e 100644 --- a/temporal-sdk/src/test/java/io/temporal/internal/statemachines/ActivityStateMachineTest.java +++ b/temporal-sdk/src/test/java/io/temporal/internal/statemachines/ActivityStateMachineTest.java @@ -93,7 +93,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> stateMachines.scheduleActivityTask(parameters, c)) @@ -173,7 +173,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> stateMachines.scheduleActivityTask(parameters, c)) @@ -254,7 +254,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> stateMachines.scheduleActivityTask(parameters, c)) @@ -340,7 +340,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -388,7 +388,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -490,7 +490,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -574,7 +574,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -680,7 +680,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -798,7 +798,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -918,7 +918,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -1015,7 +1015,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -1135,7 +1135,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -1244,7 +1244,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) @@ -1352,7 +1352,7 @@ public void buildWorkflow(AsyncWorkflowBuilder builder) { ScheduleActivityTaskCommandAttributes.newBuilder().setActivityId("id1"); ExecuteActivityParameters parameters = new ExecuteActivityParameters( - attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED); + attributes, ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, null); builder ., Failure>add2( (v, c) -> cancellationHandler = stateMachines.scheduleActivityTask(parameters, c)) diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/activityTests/ActivityMetadataTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/activityTests/ActivityMetadataTest.java new file mode 100644 index 000000000..309d012b1 --- /dev/null +++ b/temporal-sdk/src/test/java/io/temporal/workflow/activityTests/ActivityMetadataTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.temporal.workflow.activityTests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + +import io.temporal.activity.ActivityOptions; +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.api.history.v1.HistoryEvent; +import io.temporal.client.WorkflowStub; +import io.temporal.common.WorkflowExecutionHistory; +import io.temporal.common.converter.DefaultDataConverter; +import io.temporal.testing.internal.SDKTestWorkflowRule; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.shared.TestActivities; +import io.temporal.workflow.shared.TestWorkflows.TestWorkflow1; +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class ActivityMetadataTest { + + @Rule + public SDKTestWorkflowRule testWorkflowRule = + SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes(TestWorkflowImpl.class) + .setActivityImplementations(new TestActivities.TestActivitiesImpl()) + .build(); + + static final String activitySummary = "activity-summary"; + + @Before + public void checkRealServer() { + assumeTrue("skipping for test server", SDKTestWorkflowRule.useExternalService); + } + + @Test + public void testActivityWithMetaData() { + TestWorkflow1 stub = testWorkflowRule.newWorkflowStubTimeoutOptions(TestWorkflow1.class); + stub.execute(testWorkflowRule.getTaskQueue()); + + WorkflowExecution exec = WorkflowStub.fromTyped(stub).getExecution(); + WorkflowExecutionHistory workflowExecutionHistory = + testWorkflowRule.getWorkflowClient().fetchHistory(exec.getWorkflowId()); + List activityScheduledEvents = + workflowExecutionHistory.getEvents().stream() + .filter(HistoryEvent::hasActivityTaskScheduledEventAttributes) + .collect(Collectors.toList()); + assertEventMetadata(activityScheduledEvents.get(0), activitySummary, null); + } + + private void assertEventMetadata(HistoryEvent event, String summary, String details) { + if (summary != null) { + String describedSummary = + DefaultDataConverter.STANDARD_INSTANCE.fromPayload( + event.getUserMetadata().getSummary(), String.class, String.class); + assertEquals(summary, describedSummary); + } + if (details != null) { + String describedDetails = + DefaultDataConverter.STANDARD_INSTANCE.fromPayload( + event.getUserMetadata().getDetails(), String.class, String.class); + assertEquals(details, describedDetails); + } + } + + public static class TestWorkflowImpl implements TestWorkflow1 { + + private final TestActivities.VariousTestActivities activities = + Workflow.newActivityStub( + TestActivities.VariousTestActivities.class, + ActivityOptions.newBuilder() + .setSummary(activitySummary) + .setStartToCloseTimeout(Duration.ofSeconds(5)) + .build()); + + @Override + public String execute(String taskQueue) { + return activities.activity(); + } + } +}