-
Notifications
You must be signed in to change notification settings - Fork 223
NEXUS-485: Support Workflow Update as a Nexus Operation #2945
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ public class LinkConverter { | |
|
|
||
| private static final Logger log = LoggerFactory.getLogger(LinkConverter.class); | ||
|
|
||
| private static final String temporalUrlScheme = "temporal"; | ||
| private static final String linkPathFormat = "temporal:///namespaces/%s/workflows/%s/%s/history"; | ||
| private static final String nexusOperationLinkPathFormat = | ||
| "temporal:///namespaces/%s/nexus-operations/%s/%s/details"; | ||
|
|
@@ -35,6 +36,7 @@ public class LinkConverter { | |
| Link.WorkflowEvent.getDescriptor().getFullName(); | ||
| private static final String nexusOperationLinkType = | ||
| Link.NexusOperation.getDescriptor().getFullName(); | ||
| private static final String workflowLinkType = Link.Workflow.getDescriptor().getFullName(); | ||
|
|
||
| public static io.temporal.api.nexus.v1.Link workflowEventToNexusLink(Link.WorkflowEvent we) { | ||
| try { | ||
|
|
@@ -93,6 +95,24 @@ public static io.temporal.api.nexus.v1.Link workflowEventToNexusLink(Link.Workfl | |
| return null; | ||
| } | ||
|
|
||
| public static io.temporal.api.nexus.v1.Link workflowLinkToNexusLink(Link.Workflow w) { | ||
| try { | ||
| String namespace = URLEncoder.encode(w.getNamespace(), StandardCharsets.UTF_8.toString()); | ||
| String workflowId = | ||
| URLEncoder.encode(w.getWorkflowId(), StandardCharsets.UTF_8.toString()) | ||
| .replace("+", "%20"); // handle workflowIds supporting spaces | ||
| String runId = URLEncoder.encode(w.getRunId(), StandardCharsets.UTF_8.toString()); | ||
| String url = String.format(linkPathFormat, namespace, workflowId, runId); | ||
| return io.temporal.api.nexus.v1.Link.newBuilder() | ||
| .setUrl(url) | ||
| .setType(workflowLinkType) | ||
| .build(); | ||
| } catch (Exception e) { | ||
| log.error("Failed to encode Nexus link URL", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| public static Link nexusLinkToWorkflowEvent(io.temporal.api.nexus.v1.Link nexusLink) { | ||
| Link.Builder link = Link.newBuilder(); | ||
| try { | ||
|
|
@@ -166,6 +186,44 @@ public static Link nexusLinkToWorkflowEvent(io.temporal.api.nexus.v1.Link nexusL | |
| return link.build(); | ||
| } | ||
|
|
||
| public static Link nexusLinkToWorkflowLink(io.temporal.api.nexus.v1.Link nexusLink) { | ||
| Link.Builder link = Link.newBuilder(); | ||
| try { | ||
| URI uri = new URI(nexusLink.getUrl()); | ||
| log.debug("Parsing nexus link URL: {}", uri.getRawPath()); | ||
| if (!uri.getScheme().equals(temporalUrlScheme)) { | ||
| log.error("Failed to parse Nexus link URL: invalid scheme: {}", uri.getScheme()); | ||
| return null; | ||
| } | ||
| StringTokenizer st = new StringTokenizer(uri.getRawPath(), "/"); | ||
| // maybe add constants for "namespaces", "workflows" too | ||
| if (!st.nextToken().equals("namespaces")) { | ||
| log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath()); | ||
| return null; | ||
| } | ||
| String namespace = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString()); | ||
| if (!st.nextToken().equals("workflows")) { | ||
| log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath()); | ||
| return null; | ||
| } | ||
| String workflowID = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString()); | ||
| if (!st.hasMoreTokens()) { | ||
| log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath()); | ||
| return null; | ||
| } | ||
| String runID = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString()); | ||
| link.setWorkflow( | ||
| Link.Workflow.newBuilder() | ||
| .setNamespace(namespace) | ||
| .setWorkflowId(workflowID) | ||
| .setRunId(runID)); | ||
| } catch (Exception e) { | ||
| log.error("Failed to parse Nexus link URL", e); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might still want to log the bad URL, like you did above |
||
| return null; | ||
| } | ||
| return link.build(); | ||
| } | ||
|
|
||
| /** | ||
| * Dispatches on the oneof variant of {@code commonLink} and converts to the matching {@link | ||
| * io.temporal.api.nexus.v1.Link}. Returns {@code null} if no variant is set or encoding fails. | ||
|
|
@@ -177,6 +235,9 @@ public static io.temporal.api.nexus.v1.Link linkToNexusLink(Link commonLink) { | |
| if (commonLink.hasNexusOperation()) { | ||
| return nexusOperationToNexusLink(commonLink.getNexusOperation()); | ||
| } | ||
| if (commonLink.hasWorkflow()) { | ||
| return workflowLinkToNexusLink(commonLink.getWorkflow()); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
|
|
@@ -192,6 +253,9 @@ public static Link nexusLinkToLink(io.temporal.api.nexus.v1.Link nexusLink) { | |
| if (nexusOperationLinkType.equals(type)) { | ||
| return nexusLinkToNexusOperation(nexusLink); | ||
| } | ||
| if (workflowLinkType.equals(type)) { | ||
| return nexusLinkToWorkflowLink(nexusLink); | ||
| } | ||
| log.warn("ignoring unsupported nexus link type: {}", type); | ||
| return null; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package io.temporal.internal.nexus; | ||
|
|
||
| import io.temporal.common.Experimental; | ||
| import java.util.Map; | ||
|
|
||
| /** Container for an in-flight Nexus operation metadata. */ | ||
| @Experimental | ||
| public final class NexusOperationMetadata { | ||
| public final String requestId; | ||
| public final String callbackUrl; | ||
| public final Map<String, String> callbackHeaders; | ||
|
|
||
| public String operationToken; | ||
| public boolean operationCompleted; | ||
|
|
||
| public NexusOperationMetadata( | ||
| String requestId, String callbackUrl, Map<String, String> callbackHeaders) { | ||
| this.requestId = requestId; | ||
| this.callbackUrl = callbackUrl; | ||
| this.callbackHeaders = callbackHeaders; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,22 +18,49 @@ public class OperationToken { | |
| @JsonProperty("wid") | ||
| private final String workflowId; | ||
|
|
||
| @JsonProperty("rid") | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| // only set for updates and activities | ||
| private final String runId; | ||
|
|
||
| @JsonProperty("uid") | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| // only set for updates | ||
| private final String updateId; | ||
|
|
||
| public OperationToken( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could consider splitting the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thought about it with a base class that does common encode/decodes but left it as overloads give hints and the remaining is mostly similar |
||
| @JsonProperty("t") Integer type, | ||
| @JsonProperty("ns") String namespace, | ||
| @JsonProperty("wid") String workflowId, | ||
| @JsonProperty("rid") String runId, | ||
| @JsonProperty("uid") String updateId, | ||
| @JsonProperty("v") Integer version) { | ||
| this.type = OperationTokenType.fromValue(type); | ||
| this.namespace = namespace; | ||
| this.workflowId = workflowId; | ||
| this.runId = runId; | ||
| this.updateId = updateId; | ||
| this.version = version; | ||
| } | ||
|
|
||
| /** Generate a token for a workflow run operation */ | ||
| public OperationToken(OperationTokenType type, String namespace, String workflowId) { | ||
| this.type = type; | ||
| this.namespace = namespace; | ||
| this.workflowId = workflowId; | ||
| this.version = null; | ||
| this.runId = null; | ||
| this.updateId = null; | ||
| } | ||
|
|
||
| /** Generate a token for a workflow update operation */ | ||
| public OperationToken(String namespace, String workflowId, String runId, String updateId) { | ||
| this.type = OperationTokenType.WORKFLOW_UPDATE; | ||
| this.namespace = namespace; | ||
| this.workflowId = workflowId; | ||
| this.runId = runId; | ||
| this.updateId = updateId; | ||
| this.version = null; | ||
| } | ||
|
|
||
| public Integer getVersion() { | ||
|
|
@@ -51,4 +78,12 @@ public String getNamespace() { | |
| public String getWorkflowId() { | ||
| return workflowId; | ||
| } | ||
|
|
||
| public String getUpdateId() { | ||
| return updateId; | ||
| } | ||
|
|
||
| public String getRunId() { | ||
| return runId; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String.format can throw IllegalFormatException which is uncaught - should you just catch Exception instead of limiting it?