Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server): Pass UserTask User details to WorkerContext #1178

Merged
merged 9 commits into from
Dec 5, 2024
2 changes: 1 addition & 1 deletion docs/docs/08-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3228,7 +3228,7 @@ SDK, which allows the Task Method to determine where the TaskRun comes from.
| `node_run_id` | | [NodeRunId](#noderunid) | Is the NodeRun that the UserTaskRun belongs to. |
| `user_task_event_number` | | int32 | Is the index in the `events` field of the UserTaskRun that the TaskRun corresponds to. |
| `user_id` | optional| string | Is the user_id that the UserTaskRun is assigned to. Unset if UserTaskRun is not asigned to a specific user_id. |
| `user_group` | optional| string | Is the user_id that the UserTaskRun is assigned to. Unset if UserTaskRun is not asigned to a specific user_id. |
| `user_group` | optional| string | Is the user_group that the UserTaskRun is assigned to. Unset if UserTaskRun is not asigned to a specific user_group. |
<!-- end Fields -->
<!-- end HasFields -->

Expand Down
4 changes: 2 additions & 2 deletions schemas/littlehorse/user_tasks.proto
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ message UserTaskTriggerReference {
// asigned to a specific user_id.
optional string user_id = 3;

// Is the user_id that the UserTaskRun is assigned to. Unset if UserTaskRun is not
// asigned to a specific user_id.
// Is the user_group that the UserTaskRun is assigned to. Unset if UserTaskRun is not
// asigned to a specific user_group.
optional string user_group = 4;
}

Expand Down
4 changes: 2 additions & 2 deletions sdk-go/lhproto/user_tasks.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions sdk-js/src/proto/user_tasks.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk-python/littlehorse/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from .node_run_pb2 import *
from .object_id_pb2 import *
from .scheduled_wf_run_pb2 import *
from .service_pb2_grpc import *
from .service_pb2 import *
from .service_pb2_grpc import *
from .task_def_pb2 import *
from .task_run_pb2 import *
from .user_tasks_pb2 import *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ public Class<UserTaskTriggerReference> getProtoBaseClass() {
public UserTaskTriggerReference.Builder toProto() {
UserTaskTriggerReference.Builder out = UserTaskTriggerReference.newBuilder()
.setNodeRunId(nodeRunId.toProto())
.setUserTaskEventNumber(userTaskEventNumber)
.setUserId(this.userId)
.setUserGroup(this.userGroup);
.setUserTaskEventNumber(userTaskEventNumber);

if (userId != null) {
out.setUserId(this.userId);
}

if (userGroup != null) {
out.setUserGroup(this.userGroup);
}

return out;
}
Expand All @@ -54,8 +60,14 @@ public void initFrom(Message proto, ExecutionContext context) {
UserTaskTriggerReference p = (UserTaskTriggerReference) proto;
nodeRunId = LHSerializable.fromProto(p.getNodeRunId(), NodeRunIdModel.class, context);
userTaskEventNumber = p.getUserTaskEventNumber();
userId = p.getUserId();
userGroup = p.getUserGroup();

if (p.hasUserId()) {
userId = p.getUserId();
}

if (p.hasUserGroup()) {
userGroup = p.getUserGroup();
}
}

@Override
Expand Down
87 changes: 87 additions & 0 deletions server/src/test/java/e2e/UserTaskTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import io.littlehorse.sdk.common.proto.SaveUserTaskRunProgressRequest;
import io.littlehorse.sdk.common.proto.SaveUserTaskRunProgressRequest.SaveUserTaskRunAssignmentPolicy;
import io.littlehorse.sdk.common.proto.SearchWfRunRequest;
import io.littlehorse.sdk.common.proto.TaskRun;
import io.littlehorse.sdk.common.proto.TaskRunId;
import io.littlehorse.sdk.common.proto.TaskStatus;
import io.littlehorse.sdk.common.proto.UserTaskEvent;
import io.littlehorse.sdk.common.proto.UserTaskEvent.EventCase;
import io.littlehorse.sdk.common.proto.UserTaskRun;
Expand Down Expand Up @@ -62,6 +65,12 @@ public class UserTaskTest {
@LHWorkflow("cancel-user-task-on-deadline")
private Workflow userTaskCancelOnDeadline;

@LHWorkflow("schedule-reminder-task-without-user-fields-workflow")
private Workflow scheduleReminderTaskWithoutUserFields;

@LHWorkflow("worker-context-receives-user-details")
private Workflow workerContextReceivesUserDetails;

@LHUserTaskForm(USER_TASK_DEF_NAME)
private MyForm myForm = new MyForm();

Expand Down Expand Up @@ -296,6 +305,42 @@ void shouldExecuteBusinessExceptionHandlerWhenUserTaskGetsCancelOnDeadline() {
.start();
}

@Test
void shouldScheduleAndExecuteReminderTask() {
workflowVerifier
.prepareRun(scheduleReminderTaskWithoutUserFields)
.waitForStatus(ERROR, Duration.ofSeconds(6))
.thenVerifyNodeRun(0, 1, nodeRun -> {
UserTaskRunId userTaskId = nodeRun.getUserTask().getUserTaskRunId();
UserTaskRun userTaskRun = client.getUserTaskRun(userTaskId);
UserTaskEvent userTaskEvent = userTaskRun.getEvents(1);
TaskRunId taskRunId = userTaskEvent.getTaskExecuted().getTaskRun();
TaskRun taskRun = client.getTaskRun(taskRunId);
TaskStatus taskRunStatus = taskRun.getStatus();

Assertions.assertThat(taskRunStatus).isEqualTo(TaskStatus.TASK_SUCCESS);
})
.start();
}

@Test
void verifyWorkerContextHasUserIdOrUserGroup() {
workflowVerifier
.prepareRun(workerContextReceivesUserDetails)
.waitForStatus(ERROR, Duration.ofSeconds(6))
.thenVerifyNodeRun(0, 1, nodeRun -> {
UserTaskRunId userTaskId = nodeRun.getUserTask().getUserTaskRunId();
UserTaskRun userTaskRun = client.getUserTaskRun(userTaskId);
UserTaskEvent userTaskEvent = userTaskRun.getEvents(1);
TaskRunId taskRunId = userTaskEvent.getTaskExecuted().getTaskRun();
TaskRun taskRun = client.getTaskRun(taskRunId);
TaskStatus taskRunStatus = taskRun.getStatus();

Assertions.assertThat(taskRunStatus).isEqualTo(TaskStatus.TASK_SUCCESS);
})
.start();
}

@LHWorkflow("deadline-reassignment-workflow")
public Workflow buildDeadlineReassignmentWorkflow() {
return new WorkflowImpl("deadline-reassignment-workflow", entrypointThread -> {
Expand All @@ -315,6 +360,30 @@ public Workflow buildDeadlineReassignmentWorkflow() {
});
}

@LHWorkflow("schedule-reminder-task-without-user-fields-workflow")
public Workflow buildReminderTaskWorkflowWithUserGroupField() {
return new WorkflowImpl("reminder-task-without-user-fields-workflow", entrypointThread -> {
UserTaskOutput formOutput = entrypointThread.assignUserTask(USER_TASK_DEF_NAME, "jacob", null);

// Schedule a reminder immediately
entrypointThread.scheduleReminderTask(formOutput, 0, "reminder-task");

entrypointThread.cancelUserTaskRunAfter(formOutput, 5);
});
}

@LHWorkflow("worker-context-receives-user-details")
public Workflow workerContextReceivesUserDetails() {
return new WorkflowImpl("worker-context-receives-user-details", entrypointThread -> {
UserTaskOutput formOutput = entrypointThread.assignUserTask(USER_TASK_DEF_NAME, "jacob", null);

// Schedule a reminder immediately
entrypointThread.scheduleReminderTask(formOutput, 0, "verify-worker-context", "jacob", null);

entrypointThread.cancelUserTaskRunAfter(formOutput, 5);
});
}

@LHWorkflow("deadline-reassignment-workflow-user-without-group")
public Workflow buildDeadlineReassignmentWorkflowUserWithoutGroup() {
return new WorkflowImpl("deadline-reassignment-workflow-user-without-group", entrypointThread -> {
Expand Down Expand Up @@ -369,6 +438,24 @@ public String userTaskCanceled() {
public void doReminder(WorkerContext ctx) {
cache.put(ctx.getWfRunId().getId(), "hello there!");
}

@LHTaskMethod("verify-worker-context")
public void verifyWorkerContext(String userId, String userGroup, WorkerContext ctx) {
if (userId == null && userGroup == null) {
throw new IllegalStateException("At least one of userId or userGroup must be specified");
}

if (userId != null) {
if (!userId.equals(ctx.getUserId())) {
throw new IllegalStateException("WorkerContext UserId does not match expected value.");
}
}
if (userGroup != null) {
if (!userGroup.equals(ctx.getUserGroup())) {
throw new IllegalStateException("WorkerContext UserGroup does not match expected value.");
}
}
}
}

class MyForm {
Expand Down
Loading