diff --git a/temporalio/Rakefile b/temporalio/Rakefile index 73972bc2..81523c4b 100644 --- a/temporalio/Rakefile +++ b/temporalio/Rakefile @@ -94,7 +94,7 @@ Rake::Task[:build].enhance([:copy_parent_files]) do end task :rust_lint do - sh 'cargo', 'clippy', '--', '-Dwarnings' + sh 'cargo', 'clippy', '-p', 'temporalio_bridge', '--no-deps', '--', '-Dwarnings' sh 'cargo', 'fmt', '--check' end diff --git a/temporalio/extra/proto_gen.rb b/temporalio/extra/proto_gen.rb index 68ac2206..22985407 100644 --- a/temporalio/extra/proto_gen.rb +++ b/temporalio/extra/proto_gen.rb @@ -95,6 +95,7 @@ def generate_import_helper_files require 'temporalio/api/errordetails/v1/message' require 'temporalio/api/export/v1/message' require 'temporalio/api/operatorservice' + require 'temporalio/api/sdk/v1/workflow_metadata' require 'temporalio/api/workflowservice' module Temporalio diff --git a/temporalio/lib/temporalio/api.rb b/temporalio/lib/temporalio/api.rb index 621b1a18..a1ee262d 100644 --- a/temporalio/lib/temporalio/api.rb +++ b/temporalio/lib/temporalio/api.rb @@ -5,6 +5,7 @@ require 'temporalio/api/errordetails/v1/message' require 'temporalio/api/export/v1/message' require 'temporalio/api/operatorservice' +require 'temporalio/api/sdk/v1/workflow_metadata' require 'temporalio/api/workflowservice' module Temporalio diff --git a/temporalio/lib/temporalio/client.rb b/temporalio/lib/temporalio/client.rb index 51fdb2d8..696aba55 100644 --- a/temporalio/lib/temporalio/client.rb +++ b/temporalio/lib/temporalio/client.rb @@ -194,6 +194,12 @@ def operator_service # @param args [Array] Arguments to the workflow. # @param id [String] Unique identifier for the workflow execution. # @param task_queue [String] Task queue to run the workflow on. + # @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear in + # CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental. + # @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can + # be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be + # updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is currently + # experimental. # @param execution_timeout [Float, nil] Total workflow execution timeout in seconds including retries and continue # as new. # @param run_timeout [Float, nil] Timeout of a single workflow run in seconds. @@ -220,6 +226,8 @@ def start_workflow( *args, id:, task_queue:, + static_summary: nil, + static_details: nil, execution_timeout: nil, run_timeout: nil, task_timeout: nil, @@ -238,6 +246,8 @@ def start_workflow( args:, workflow_id: id, task_queue:, + static_summary:, + static_details:, execution_timeout:, run_timeout:, task_timeout:, @@ -260,6 +270,12 @@ def start_workflow( # @param args [Array] Arguments to the workflow. # @param id [String] Unique identifier for the workflow execution. # @param task_queue [String] Task queue to run the workflow on. + # @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear in + # CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental. + # @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can + # be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be + # updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is currently + # experimental. # @param execution_timeout [Float, nil] Total workflow execution timeout in seconds including retries and continue # as new. # @param run_timeout [Float, nil] Timeout of a single workflow run in seconds. @@ -287,6 +303,8 @@ def execute_workflow( *args, id:, task_queue:, + static_summary: nil, + static_details: nil, execution_timeout: nil, run_timeout: nil, task_timeout: nil, @@ -305,6 +323,8 @@ def execute_workflow( *args, id:, task_queue:, + static_summary:, + static_details:, execution_timeout:, run_timeout:, task_timeout:, diff --git a/temporalio/lib/temporalio/client/interceptor.rb b/temporalio/lib/temporalio/client/interceptor.rb index 0a556624..5a3ced80 100644 --- a/temporalio/lib/temporalio/client/interceptor.rb +++ b/temporalio/lib/temporalio/client/interceptor.rb @@ -23,6 +23,8 @@ def intercept_client(next_interceptor) :args, :workflow_id, :task_queue, + :static_summary, + :static_details, :execution_timeout, :run_timeout, :task_timeout, diff --git a/temporalio/lib/temporalio/client/schedule.rb b/temporalio/lib/temporalio/client/schedule.rb index 0da47070..95e71e04 100644 --- a/temporalio/lib/temporalio/client/schedule.rb +++ b/temporalio/lib/temporalio/client/schedule.rb @@ -167,6 +167,8 @@ def _to_proto(data_converter) :args, :id, :task_queue, + :static_summary, + :static_details, :execution_timeout, :run_timeout, :task_timeout, @@ -186,6 +188,14 @@ def _to_proto(data_converter) # @return [String] Unique identifier for the workflow execution. # @!attribute task_queue # @return [String] Task queue to run the workflow on. + # @!attribute static_summary + # @return [String, nil] Fixed single-line summary for this workflow execution that may appear in CLI/UI. + # This can be in single-line Temporal markdown format. This is currently experimental. + # @!attribute static_details + # @return [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can be in + # Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be + # updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is + # currently experimental. # @!attribute execution_timeout # @return [Float, nil] Total workflow execution timeout in seconds including retries and continue as new. # @!attribute run_timeout @@ -212,6 +222,12 @@ class << self # @param args [Array] Arguments to the workflow. # @param id [String] Unique identifier for the workflow execution. # @param task_queue [String] Task queue to run the workflow on. + # @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear + # in CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental. + # @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. + # This can be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow + # that cannot be updated. For details that can be updated, use {Workflow.current_details=} within the + # workflow. This is currently experimental. # @param execution_timeout [Float, nil] Total workflow execution timeout in seconds including retries and # continue as new. # @param run_timeout [Float, nil] Timeout of a single workflow run in seconds. @@ -225,6 +241,8 @@ def new( *args, id:, task_queue:, + static_summary: nil, + static_details: nil, execution_timeout: nil, run_timeout: nil, task_timeout: nil, @@ -238,6 +256,8 @@ def new( args:, id:, task_queue:, + static_summary:, + static_details:, execution_timeout:, run_timeout:, task_timeout:, @@ -251,11 +271,14 @@ def new( # @!visibility private def self._from_proto(raw_info, data_converter) + (summary, details) = Internal::ProtoUtils.from_user_metadata(raw_info.user_metadata, data_converter) StartWorkflow.new( raw_info.workflow_type.name, *data_converter.from_payloads(raw_info.input), id: raw_info.workflow_id, task_queue: raw_info.task_queue.name, + static_summary: summary, + static_details: details, execution_timeout: Internal::ProtoUtils.duration_to_seconds(raw_info.workflow_execution_timeout), run_timeout: Internal::ProtoUtils.duration_to_seconds(raw_info.workflow_run_timeout), task_timeout: Internal::ProtoUtils.duration_to_seconds(raw_info.workflow_task_timeout), @@ -280,7 +303,8 @@ def _to_proto(data_converter) retry_policy: retry_policy&._to_proto, memo: Internal::ProtoUtils.memo_to_proto(memo, data_converter), search_attributes: search_attributes&._to_proto, - header: Internal::ProtoUtils.headers_to_proto(headers, data_converter) + header: Internal::ProtoUtils.headers_to_proto(headers, data_converter), + user_metadata: Internal::ProtoUtils.to_user_metadata(static_summary, static_details, data_converter) ) ) end diff --git a/temporalio/lib/temporalio/client/with_start_workflow_operation.rb b/temporalio/lib/temporalio/client/with_start_workflow_operation.rb index 478227e7..8d667a10 100644 --- a/temporalio/lib/temporalio/client/with_start_workflow_operation.rb +++ b/temporalio/lib/temporalio/client/with_start_workflow_operation.rb @@ -12,6 +12,8 @@ class WithStartWorkflowOperation :args, :id, :task_queue, + :static_summary, + :static_details, :execution_timeout, :run_timeout, :task_timeout, @@ -41,6 +43,8 @@ def initialize( *args, id:, task_queue:, + static_summary: nil, + static_details: nil, execution_timeout: nil, run_timeout: nil, task_timeout: nil, @@ -58,6 +62,8 @@ def initialize( args:, id:, task_queue:, + static_summary:, + static_details:, execution_timeout:, run_timeout:, task_timeout:, diff --git a/temporalio/lib/temporalio/client/workflow_execution.rb b/temporalio/lib/temporalio/client/workflow_execution.rb index f9d7ecc2..7f3d30d3 100644 --- a/temporalio/lib/temporalio/client/workflow_execution.rb +++ b/temporalio/lib/temporalio/client/workflow_execution.rb @@ -93,6 +93,25 @@ class Description < WorkflowExecution def initialize(raw_description, data_converter) super(raw_description.workflow_execution_info, data_converter) @raw_description = raw_description + @data_converter = data_converter + end + + # @return [String, nil] Static summary configured on the workflow. This is currently experimental. + def static_summary + user_metadata.first + end + + # @return [String, nil] Static details configured on the workflow. This is currently experimental. + def static_details + user_metadata.last + end + + private + + def user_metadata + @user_metadata ||= Internal::ProtoUtils.from_user_metadata( + @raw_description.execution_config&.user_metadata, @data_converter + ) end end end diff --git a/temporalio/lib/temporalio/internal/client/implementation.rb b/temporalio/lib/temporalio/internal/client/implementation.rb index 174afc1b..06242a53 100644 --- a/temporalio/lib/temporalio/internal/client/implementation.rb +++ b/temporalio/lib/temporalio/internal/client/implementation.rb @@ -69,6 +69,9 @@ def start_workflow(input) search_attributes: input.search_attributes&._to_proto, workflow_start_delay: ProtoUtils.seconds_to_duration(input.start_delay), request_eager_execution: input.request_eager_start, + user_metadata: ProtoUtils.to_user_metadata( + input.static_summary, input.static_details, @client.data_converter + ), header: ProtoUtils.headers_to_proto(input.headers, @client.data_converter) ) @@ -319,6 +322,9 @@ def _start_workflow_request_from_with_start_options(klass, start_options) memo: ProtoUtils.memo_to_proto(start_options.memo, @client.data_converter), search_attributes: start_options.search_attributes&._to_proto, workflow_start_delay: ProtoUtils.seconds_to_duration(start_options.start_delay), + user_metadata: ProtoUtils.to_user_metadata( + start_options.static_summary, start_options.static_details, @client.data_converter + ), header: ProtoUtils.headers_to_proto(start_options.headers, @client.data_converter) ) end diff --git a/temporalio/lib/temporalio/internal/proto_utils.rb b/temporalio/lib/temporalio/internal/proto_utils.rb index 172617a1..6ebfa33c 100644 --- a/temporalio/lib/temporalio/internal/proto_utils.rb +++ b/temporalio/lib/temporalio/internal/proto_utils.rb @@ -122,6 +122,22 @@ def self.reserved_name?(name) name.start_with?('__temporal_') || name == '__stack_trace' || name == '__enhanced_stack_trace' end + def self.to_user_metadata(summary, details, converter) + return nil if (!summary || summary.empty?) && (!details || details.empty?) + + metadata = Temporalio::Api::Sdk::V1::UserMetadata.new + metadata.summary = converter.to_payload(summary) if summary && !summary.empty? + metadata.details = converter.to_payload(details) if details && !details.empty? + metadata + end + + def self.from_user_metadata(metadata, converter) + [ + (converter.from_payload(metadata.summary) if metadata&.summary), #: String? + (converter.from_payload(metadata.details) if metadata&.details) #: String? + ] + end + class LazyMemo def initialize(raw_memo, converter) @raw_memo = raw_memo diff --git a/temporalio/lib/temporalio/internal/worker/workflow_instance.rb b/temporalio/lib/temporalio/internal/worker/workflow_instance.rb index 1add6a16..9840dc04 100644 --- a/temporalio/lib/temporalio/internal/worker/workflow_instance.rb +++ b/temporalio/lib/temporalio/internal/worker/workflow_instance.rb @@ -56,6 +56,7 @@ def self.new_completion_with_failure(run_id:, error:, failure_converter:, payloa :failure_converter, :cancellation, :continue_as_new_suggested, :current_history_length, :current_history_size, :replaying, :random, :signal_handlers, :query_handlers, :update_handlers, :context_frozen + attr_accessor :current_details def initialize(details) # Initialize general state @@ -386,9 +387,10 @@ def apply_signal(job) def apply_query(job) schedule do # If it's a built-in, run it without interceptors, otherwise do normal behavior - # TODO(cretz): __temporal_workflow_metadata result = if job.query_type == '__stack_trace' scheduler.stack_trace + elsif job.query_type == '__temporal_workflow_metadata' + workflow_metadata else # Get query definition, falling back to dynamic if not present and not reserved defn = query_handlers[job.query_type] @@ -678,6 +680,30 @@ def convert_args(payload_array:, method_name:, raw_args:, ignore_first_param: fa end end + def workflow_metadata + Temporalio::Api::Sdk::V1::WorkflowMetadata.new( + definition: Temporalio::Api::Sdk::V1::WorkflowDefinition.new( + type: info.workflow_type, + query_definitions: query_handlers.values.map do |defn| + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: defn.name || '', description: defn.description || '' + ) + end, + signal_definitions: signal_handlers.values.map do |defn| + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: defn.name || '', description: defn.description || '' + ) + end, + update_definitions: update_handlers.values.map do |defn| + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: defn.name || '', description: defn.description || '' + ) + end + ), + current_details: current_details || '' + ) + end + def scoped_logger_info @scoped_logger_info ||= { attempt: info.attempt, diff --git a/temporalio/lib/temporalio/internal/worker/workflow_instance/context.rb b/temporalio/lib/temporalio/internal/worker/workflow_instance/context.rb index eb094970..b66b6827 100644 --- a/temporalio/lib/temporalio/internal/worker/workflow_instance/context.rb +++ b/temporalio/lib/temporalio/internal/worker/workflow_instance/context.rb @@ -32,6 +32,16 @@ def continue_as_new_suggested @instance.continue_as_new_suggested end + def current_details + @instance.current_details || '' + end + + def current_details=(details) + raise 'Details must be a String' unless details.nil? || details.is_a?(String) + + @instance.current_details = (details || '') + end + def current_history_length @instance.current_history_length end @@ -52,6 +62,7 @@ def execute_activity( activity, *args, task_queue:, + summary:, schedule_to_close_timeout:, schedule_to_start_timeout:, start_to_close_timeout:, @@ -67,6 +78,7 @@ def execute_activity( activity:, args:, task_queue: task_queue || info.task_queue, + summary:, schedule_to_close_timeout:, schedule_to_start_timeout:, start_to_close_timeout:, @@ -191,6 +203,8 @@ def start_child_workflow( *args, id:, task_queue:, + static_summary:, + static_details:, cancellation:, cancellation_type:, parent_close_policy:, @@ -209,6 +223,8 @@ def start_child_workflow( args:, id:, task_queue:, + static_summary:, + static_details:, cancellation:, cancellation_type:, parent_close_policy:, diff --git a/temporalio/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb b/temporalio/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb index 6dc3ee50..5825b602 100644 --- a/temporalio/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +++ b/temporalio/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb @@ -84,7 +84,8 @@ def execute_activity(input) retry_policy: input.retry_policy&._to_proto, cancellation_type: input.cancellation_type, do_not_eagerly_execute: input.disable_eager_execution - ) + ), + user_metadata: ProtoUtils.to_user_metadata(input.summary, nil, @instance.payload_converter) ) ) seq @@ -300,7 +301,8 @@ def sleep(input) start_timer: Bridge::Api::WorkflowCommands::StartTimer.new( seq:, start_to_fire_timeout: ProtoUtils.seconds_to_duration(duration) - ) + ), + user_metadata: ProtoUtils.to_user_metadata(input.summary, nil, @instance.payload_converter) ) ) @instance.pending_timers[seq] = Fiber.current @@ -354,6 +356,9 @@ def start_child_workflow(input) memo: ProtoUtils.memo_to_proto_hash(input.memo, @instance.payload_converter), search_attributes: input.search_attributes&._to_proto_hash, cancellation_type: input.cancellation_type + ), + user_metadata: ProtoUtils.to_user_metadata( + input.static_summary, input.static_details, @instance.payload_converter ) ) ) diff --git a/temporalio/lib/temporalio/worker/interceptor.rb b/temporalio/lib/temporalio/worker/interceptor.rb index b5acc4a2..0662c4a5 100644 --- a/temporalio/lib/temporalio/worker/interceptor.rb +++ b/temporalio/lib/temporalio/worker/interceptor.rb @@ -206,6 +206,7 @@ def handle_update(input) :activity, :args, :task_queue, + :summary, :schedule_to_close_timeout, :schedule_to_start_timeout, :start_to_close_timeout, @@ -270,6 +271,8 @@ def handle_update(input) :args, :id, :task_queue, + :static_summary, + :static_details, :cancellation, :cancellation_type, :parent_close_policy, diff --git a/temporalio/lib/temporalio/workflow.rb b/temporalio/lib/temporalio/workflow.rb index c13da19c..8a20bcce 100644 --- a/temporalio/lib/temporalio/workflow.rb +++ b/temporalio/lib/temporalio/workflow.rb @@ -38,6 +38,24 @@ def self.continue_as_new_suggested _current.continue_as_new_suggested end + # Get current details for this workflow that may appear in UI/CLI. Unlike static details set at start, this value + # can be updated throughout the life of the workflow. This can be in Temporal markdown format and can span multiple + # lines. This is currently experimental. + # + # @return [String] Current details. Default is empty string. + def self.current_details + _current.current_details + end + + # Set current details for this workflow that may appear in UI/CLI. Unlike static details set at start, this value + # can be updated throughout the life of the workflow. This can be in Temporal markdown format and can span multiple + # lines. This is currently experimental. + # + # @param details [String] Current details. Can use empty string to unset. + def self.current_details=(details) + _current.current_details = details + end + # @return [Integer] Current number of events in history. This value is the current history event count up until the # current task. Note, this value may not be up to date when accessed in a query. def self.current_history_length @@ -77,6 +95,8 @@ def self.deprecate_patch(patch_id) # @param activity [Class, Symbol, String] Activity definition class or activity name. # @param args [Array] Arguments to the activity. # @param task_queue [String] Task queue to run the activity on. Defaults to the current workflow's task queue. + # @param summary [String, nil] Single-line summary for this activity that may appear in CLI/UI. This can be in + # single-line Temporal markdown format. This is currently experimental. # @param schedule_to_close_timeout [Float, nil] Max amount of time the activity can take from first being scheduled # to being completed before it times out. This is inclusive of all retries. # @param schedule_to_start_timeout [Float, nil] Max amount of time the activity can take to be started from first @@ -107,6 +127,7 @@ def self.execute_activity( activity, *args, task_queue: info.task_queue, + summary: nil, schedule_to_close_timeout: nil, schedule_to_start_timeout: nil, start_to_close_timeout: nil, @@ -119,7 +140,7 @@ def self.execute_activity( ) _current.execute_activity( activity, *args, - task_queue:, schedule_to_close_timeout:, schedule_to_start_timeout:, start_to_close_timeout:, + task_queue:, summary:, schedule_to_close_timeout:, schedule_to_start_timeout:, start_to_close_timeout:, heartbeat_timeout:, retry_policy:, cancellation:, cancellation_type:, activity_id:, disable_eager_execution: ) end @@ -130,6 +151,8 @@ def self.execute_child_workflow( *args, id: random.uuid, task_queue: info.task_queue, + static_summary: nil, + static_details: nil, cancellation: Workflow.cancellation, cancellation_type: ChildWorkflowCancellationType::WAIT_CANCELLATION_COMPLETED, parent_close_policy: ParentClosePolicy::TERMINATE, @@ -144,8 +167,9 @@ def self.execute_child_workflow( ) start_child_workflow( workflow, *args, - id:, task_queue:, cancellation:, cancellation_type:, parent_close_policy:, execution_timeout:, run_timeout:, - task_timeout:, id_reuse_policy:, retry_policy:, cron_schedule:, memo:, search_attributes: + id:, task_queue:, static_summary:, static_details:, cancellation:, cancellation_type:, + parent_close_policy:, execution_timeout:, run_timeout:, task_timeout:, id_reuse_policy:, + retry_policy:, cron_schedule:, memo:, search_attributes: ).result end @@ -304,7 +328,7 @@ def self.signal_handlers # value cannot be negative. Since Temporal timers are server-side, timer resolution may not end up as precise as # system timers. # @param summary [String, nil] A simple string identifying this timer that may be visible in UI/CLI. While it can be - # normal text, it is best to treat as a timer ID. + # normal text, it is best to treat as a timer ID. This is currently experimental. # @param cancellation [Cancellation] Cancellation for this timer. # @raise [Error::CanceledError] Sleep canceled. def self.sleep(duration, summary: nil, cancellation: Workflow.cancellation) @@ -317,6 +341,12 @@ def self.sleep(duration, summary: nil, cancellation: Workflow.cancellation) # @param args [Array] Arguments to the workflow. # @param id [String] Unique identifier for the workflow execution. Defaults to a new UUID from {random}. # @param task_queue [String] Task queue to run the workflow on. Defaults to the current workflow's task queue. + # @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear in + # CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental. + # @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can + # be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be + # updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is currently + # experimental. # @param cancellation [Cancellation] Cancellation to apply to the child workflow. How cancellation is treated is # based on `cancellation_type`. This defaults to the workflow's cancellation. # @param cancellation_type [ChildWorkflowCancellationType] How the child workflow will react to cancellation. @@ -339,6 +369,8 @@ def self.start_child_workflow( *args, id: random.uuid, task_queue: info.task_queue, + static_summary: nil, + static_details: nil, cancellation: Workflow.cancellation, cancellation_type: ChildWorkflowCancellationType::WAIT_CANCELLATION_COMPLETED, parent_close_policy: ParentClosePolicy::TERMINATE, @@ -353,8 +385,9 @@ def self.start_child_workflow( ) _current.start_child_workflow( workflow, *args, - id:, task_queue:, cancellation:, cancellation_type:, parent_close_policy:, execution_timeout:, run_timeout:, - task_timeout:, id_reuse_policy:, retry_policy:, cron_schedule:, memo:, search_attributes: + id:, task_queue:, static_summary:, static_details:, cancellation:, cancellation_type:, + parent_close_policy:, execution_timeout:, run_timeout:, task_timeout:, id_reuse_policy:, + retry_policy:, cron_schedule:, memo:, search_attributes: ) end @@ -367,8 +400,8 @@ def self.start_child_workflow( # exception. # @param message [String] Message to use for timeout exception. Defaults to "execution expired" like # {::Timeout.timeout}. - # @param summary [String] Timer summer for the timer created by this timeout. This is backed by {sleep} so see that - # method for details. + # @param summary [String] Timer summary for the timer created by this timeout. This is backed by {sleep} so see that + # method for details. This is currently experimental. # # @yield Block to run with a timeout. # @return [Object] The result of the block. diff --git a/temporalio/lib/temporalio/workflow/definition.rb b/temporalio/lib/temporalio/workflow/definition.rb index ee172820..d24da3bd 100644 --- a/temporalio/lib/temporalio/workflow/definition.rb +++ b/temporalio/lib/temporalio/workflow/definition.rb @@ -72,7 +72,9 @@ def workflow_failure_exception_type(*types) # `attr_accessor`. If a writer is needed alongside this, use `attr_writer`. # # @param attr_names [Array] Attributes to expose. - def workflow_query_attr_reader(*attr_names) + # @param description [String, nil] Description that may appear in CLI/UI, applied to each query handler + # implicitly created. This is currently experimental. + def workflow_query_attr_reader(*attr_names, description: nil) @workflow_queries ||= {} attr_names.each do |attr_name| raise 'Expected attr to be a symbol' unless attr_name.is_a?(Symbol) @@ -84,7 +86,7 @@ def workflow_query_attr_reader(*attr_names) end # Just run this as if done manually - workflow_query + workflow_query(description:) define_method(attr_name) { instance_variable_get("@#{attr_name}") } end end @@ -101,6 +103,8 @@ def workflow_init(value = true) # rubocop:disable Style/OptionalBooleanParameter # values. # # @param name [String, Symbol, nil] Override the default name. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param dynamic [Boolean] If true, make the signal dynamic. This means it receives all other signals without # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often # it is useful to have the second parameter be `*args` and `raw_args` be true. @@ -110,19 +114,22 @@ def workflow_init(value = true) # rubocop:disable Style/OptionalBooleanParameter # when the workflow ends. The default warns, but this can be disabled. def workflow_signal( name: nil, + description: nil, dynamic: false, raw_args: false, unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON ) raise 'Cannot provide name if dynamic is true' if name && dynamic - self.pending_handler_details = { type: :signal, name:, dynamic:, raw_args:, unfinished_policy: } + self.pending_handler_details = { type: :signal, name:, description:, dynamic:, raw_args:, unfinished_policy: } end # Mark the next method as a workflow query with a default name as the name of the method. Queries can not have # any side effects, meaning they should never mutate state or try to wait on anything. # # @param name [String, Symbol, nil] Override the default name. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param dynamic [Boolean] If true, make the query dynamic. This means it receives all other queries without # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often # it is useful to have the second parameter be `*args` and `raw_args` be true. @@ -130,18 +137,21 @@ def workflow_signal( # {Converters::RawValue} which is a raw payload wrapper, convertible with {Workflow.payload_converter}. def workflow_query( name: nil, + description: nil, dynamic: false, raw_args: false ) raise 'Cannot provide name if dynamic is true' if name && dynamic - self.pending_handler_details = { type: :query, name:, dynamic:, raw_args: } + self.pending_handler_details = { type: :query, name:, description:, dynamic:, raw_args: } end # Mark the next method as a workflow update with a default name as the name of the method. Updates can return # values. Separate validation methods can be provided via {workflow_update_validator}. # # @param name [String, Symbol, nil] Override the default name. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param dynamic [Boolean] If true, make the update dynamic. This means it receives all other updates without # handlers. This cannot have a name override since it is nameless. The first parameter will be the name. Often # it is useful to have the second parameter be `*args` and `raw_args` be true. @@ -151,13 +161,14 @@ def workflow_query( # when the workflow ends. The default warns, but this can be disabled. def workflow_update( name: nil, + description: nil, dynamic: false, raw_args: false, unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON ) raise 'Cannot provide name if dynamic is true' if name && dynamic - self.pending_handler_details = { type: :update, name:, dynamic:, raw_args:, unfinished_policy: } + self.pending_handler_details = { type: :update, name:, description:, dynamic:, raw_args:, unfinished_policy: } end # Mark the next method as a workflow update validator to the given update method. The validator is expected to @@ -226,6 +237,7 @@ def self.method_added(method_name) [Signal.new( name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s, to_invoke: method_name, + description: handler[:description], raw_args: handler[:raw_args], unfinished_policy: handler[:unfinished_policy] ), @workflow_signals, [@workflow_queries, @workflow_updates]] @@ -233,12 +245,14 @@ def self.method_added(method_name) [Query.new( name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s, to_invoke: method_name, + description: handler[:description], raw_args: handler[:raw_args] ), @workflow_queries, [@workflow_signals, @workflow_updates]] when :update [Update.new( name: handler[:dynamic] ? nil : (handler[:name] || method_name).to_s, to_invoke: method_name, + description: handler[:description], raw_args: handler[:raw_args], unfinished_policy: handler[:unfinished_policy] ), @workflow_updates, [@workflow_signals, @workflow_queries]] @@ -443,7 +457,7 @@ def name # A signal definition. This is usually built as a result of a {Definition.workflow_signal} method, but can be # manually created to set at runtime on {Workflow.signal_handlers}. class Signal - attr_reader :name, :to_invoke, :raw_args, :unfinished_policy + attr_reader :name, :to_invoke, :description, :raw_args, :unfinished_policy # @!visibility private def self._name_from_parameter(signal) @@ -462,17 +476,21 @@ def self._name_from_parameter(signal) # # @param name [String, nil] Name or nil if dynamic. # @param to_invoke [Symbol, Proc] Method name or proc to invoke. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param raw_args [Boolean] Whether the parameters should be raw values. # @param unfinished_policy [HandlerUnfinishedPolicy] How the workflow reacts when this handler is still running # on workflow completion. def initialize( name:, to_invoke:, + description: nil, raw_args: false, unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON ) @name = name @to_invoke = to_invoke + @description = description @raw_args = raw_args @unfinished_policy = unfinished_policy Internal::ProtoUtils.assert_non_reserved_name(name) @@ -482,7 +500,7 @@ def initialize( # A query definition. This is usually built as a result of a {Definition.workflow_query} method, but can be # manually created to set at runtime on {Workflow.query_handlers}. class Query - attr_reader :name, :to_invoke, :raw_args + attr_reader :name, :to_invoke, :description, :raw_args # @!visibility private def self._name_from_parameter(query) @@ -501,14 +519,18 @@ def self._name_from_parameter(query) # # @param name [String, nil] Name or nil if dynamic. # @param to_invoke [Symbol, Proc] Method name or proc to invoke. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param raw_args [Boolean] Whether the parameters should be raw values. def initialize( name:, to_invoke:, + description: nil, raw_args: false ) @name = name @to_invoke = to_invoke + @description = description @raw_args = raw_args Internal::ProtoUtils.assert_non_reserved_name(name) end @@ -517,7 +539,7 @@ def initialize( # An update definition. This is usually built as a result of a {Definition.workflow_update} method, but can be # manually created to set at runtime on {Workflow.update_handlers}. class Update - attr_reader :name, :to_invoke, :raw_args, :unfinished_policy, :validator_to_invoke + attr_reader :name, :to_invoke, :description, :raw_args, :unfinished_policy, :validator_to_invoke # @!visibility private def self._name_from_parameter(update) @@ -536,6 +558,8 @@ def self._name_from_parameter(update) # # @param name [String, nil] Name or nil if dynamic. # @param to_invoke [Symbol, Proc] Method name or proc to invoke. + # @param description [String, nil] Description for this handler that may appear in CLI/UI. This is currently + # experimental. # @param raw_args [Boolean] Whether the parameters should be raw values. # @param unfinished_policy [HandlerUnfinishedPolicy] How the workflow reacts when this handler is still running # on workflow completion. @@ -543,12 +567,14 @@ def self._name_from_parameter(update) def initialize( name:, to_invoke:, + description: nil, raw_args: false, unfinished_policy: HandlerUnfinishedPolicy::WARN_AND_ABANDON, validator_to_invoke: nil ) @name = name @to_invoke = to_invoke + @description = description @raw_args = raw_args @unfinished_policy = unfinished_policy @validator_to_invoke = validator_to_invoke @@ -560,6 +586,7 @@ def _with_validator_to_invoke(validator_to_invoke) Update.new( name:, to_invoke:, + description:, raw_args:, unfinished_policy:, validator_to_invoke: diff --git a/temporalio/sig/temporalio/client.rbs b/temporalio/sig/temporalio/client.rbs index d80aae2d..89a4ba10 100644 --- a/temporalio/sig/temporalio/client.rbs +++ b/temporalio/sig/temporalio/client.rbs @@ -62,6 +62,8 @@ module Temporalio *Object? args, id: String, task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?execution_timeout: duration?, ?run_timeout: duration?, ?task_timeout: duration?, @@ -81,6 +83,8 @@ module Temporalio *Object? args, id: String, task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?execution_timeout: duration?, ?run_timeout: duration?, ?task_timeout: duration?, diff --git a/temporalio/sig/temporalio/client/interceptor.rbs b/temporalio/sig/temporalio/client/interceptor.rbs index cb27f0e2..6e1d4ebd 100644 --- a/temporalio/sig/temporalio/client/interceptor.rbs +++ b/temporalio/sig/temporalio/client/interceptor.rbs @@ -8,6 +8,8 @@ module Temporalio attr_reader args: Array[Object?] attr_reader workflow_id: String attr_reader task_queue: String + attr_reader static_summary: String? + attr_reader static_details: String? attr_reader execution_timeout: duration? attr_reader run_timeout: duration? attr_reader task_timeout: duration? @@ -27,6 +29,8 @@ module Temporalio args: Array[Object?], workflow_id: String, task_queue: String, + static_summary: String?, + static_details: String?, execution_timeout: duration?, run_timeout: duration?, task_timeout: duration?, diff --git a/temporalio/sig/temporalio/client/schedule.rbs b/temporalio/sig/temporalio/client/schedule.rbs index 43f8bfce..44014961 100644 --- a/temporalio/sig/temporalio/client/schedule.rbs +++ b/temporalio/sig/temporalio/client/schedule.rbs @@ -66,6 +66,8 @@ module Temporalio attr_reader args: Array[Object?] attr_reader id: String attr_reader task_queue: String + attr_reader static_summary: String? + attr_reader static_details: String? attr_reader execution_timeout: duration? attr_reader run_timeout: duration? attr_reader task_timeout: duration? @@ -79,6 +81,8 @@ module Temporalio *Object? args, id: String, task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?execution_timeout: duration?, ?run_timeout: duration?, ?task_timeout: duration?, diff --git a/temporalio/sig/temporalio/client/with_start_workflow_operation.rbs b/temporalio/sig/temporalio/client/with_start_workflow_operation.rbs index 997ec95c..36b97c17 100644 --- a/temporalio/sig/temporalio/client/with_start_workflow_operation.rbs +++ b/temporalio/sig/temporalio/client/with_start_workflow_operation.rbs @@ -6,6 +6,8 @@ module Temporalio attr_reader args: Array[Object?] attr_reader id: String attr_reader task_queue: String + attr_reader static_summary: String? + attr_reader static_details: String? attr_reader execution_timeout: duration? attr_reader run_timeout: duration? attr_reader task_timeout: duration? @@ -23,6 +25,8 @@ module Temporalio args: Array[Object?], id: String, task_queue: String, + static_summary: String?, + static_details: String?, execution_timeout: duration?, run_timeout: duration?, task_timeout: duration?, @@ -44,6 +48,8 @@ module Temporalio *Object? args, id: String, task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?execution_timeout: duration?, ?run_timeout: duration?, ?task_timeout: duration?, diff --git a/temporalio/sig/temporalio/client/workflow_execution.rbs b/temporalio/sig/temporalio/client/workflow_execution.rbs index 8047a1c8..4fc83350 100644 --- a/temporalio/sig/temporalio/client/workflow_execution.rbs +++ b/temporalio/sig/temporalio/client/workflow_execution.rbs @@ -23,6 +23,11 @@ module Temporalio attr_reader raw_description: untyped def initialize: (untyped raw_description, Converters::DataConverter data_converter) -> void + + def static_summary: -> String? + def static_details: -> String? + + private def user_metadata: -> [String?, String?] end end end diff --git a/temporalio/sig/temporalio/internal/proto_utils.rbs b/temporalio/sig/temporalio/internal/proto_utils.rbs index 17d40d5f..8163528b 100644 --- a/temporalio/sig/temporalio/internal/proto_utils.rbs +++ b/temporalio/sig/temporalio/internal/proto_utils.rbs @@ -62,6 +62,17 @@ module Temporalio def self.assert_non_reserved_name: (String | Symbol | nil name) -> void def self.reserved_name?: (String | Symbol | nil name) -> bool + def self.to_user_metadata: ( + String? summary, + String? details, + Converters::DataConverter | Converters::PayloadConverter converter + ) -> untyped + + def self.from_user_metadata: ( + untyped metadata, + Converters::DataConverter | Converters::PayloadConverter converter + ) -> [String?, String?] + class LazyMemo def initialize: ( untyped? raw_memo, diff --git a/temporalio/sig/temporalio/internal/worker/workflow_instance.rbs b/temporalio/sig/temporalio/internal/worker/workflow_instance.rbs index 19716d40..06b83ce4 100644 --- a/temporalio/sig/temporalio/internal/worker/workflow_instance.rbs +++ b/temporalio/sig/temporalio/internal/worker/workflow_instance.rbs @@ -34,6 +34,8 @@ module Temporalio attr_reader update_handlers: Hash[String?, Workflow::Definition::Update] attr_reader context_frozen: bool + attr_accessor current_details: String? + def initialize: (Details details) -> void def activate: (untyped activation) -> untyped @@ -90,6 +92,8 @@ module Temporalio ?ignore_first_param: bool ) -> Array[Object?] + def workflow_metadata: -> untyped + def scoped_logger_info: -> Hash[Symbol, Object?] def warn_on_any_unfinished_handlers: -> void diff --git a/temporalio/sig/temporalio/internal/worker/workflow_instance/context.rbs b/temporalio/sig/temporalio/internal/worker/workflow_instance/context.rbs index 552fab8d..3307df42 100644 --- a/temporalio/sig/temporalio/internal/worker/workflow_instance/context.rbs +++ b/temporalio/sig/temporalio/internal/worker/workflow_instance/context.rbs @@ -11,6 +11,9 @@ module Temporalio def continue_as_new_suggested: -> bool + def current_details: -> String + def current_details=: (String? details) -> void + def current_history_length: -> Integer def current_history_size: -> Integer @@ -23,6 +26,7 @@ module Temporalio singleton(Activity::Definition) | Symbol | String activity, *Object? args, task_queue: String, + summary: String?, schedule_to_close_timeout: duration?, schedule_to_start_timeout: duration?, start_to_close_timeout: duration?, @@ -86,6 +90,8 @@ module Temporalio *Object? args, id: String, task_queue: String, + static_summary: String?, + static_details: String?, cancellation: Cancellation, cancellation_type: Workflow::ChildWorkflowCancellationType::enum, parent_close_policy: Workflow::ParentClosePolicy::enum, diff --git a/temporalio/sig/temporalio/worker/interceptor.rbs b/temporalio/sig/temporalio/worker/interceptor.rbs index 702a0d73..731fa8f1 100644 --- a/temporalio/sig/temporalio/worker/interceptor.rbs +++ b/temporalio/sig/temporalio/worker/interceptor.rbs @@ -132,6 +132,7 @@ module Temporalio attr_reader activity: singleton(Temporalio::Activity::Definition) | Symbol | String attr_reader args: Array[Object?] attr_reader task_queue: String + attr_reader summary: String? attr_reader schedule_to_close_timeout: duration? attr_reader schedule_to_start_timeout: duration? attr_reader start_to_close_timeout: duration? @@ -147,6 +148,7 @@ module Temporalio activity: singleton(Temporalio::Activity::Definition) | Symbol | String, args: Array[Object?], task_queue: String, + summary: String?, schedule_to_close_timeout: duration?, schedule_to_start_timeout: duration?, start_to_close_timeout: duration?, @@ -247,6 +249,8 @@ module Temporalio attr_reader args: Array[Object?] attr_reader id: String attr_reader task_queue: String + attr_reader static_summary: String? + attr_reader static_details: String? attr_reader cancellation: Cancellation attr_reader cancellation_type: Temporalio::Workflow::ChildWorkflowCancellationType::enum attr_reader parent_close_policy: Temporalio::Workflow::ParentClosePolicy::enum @@ -265,6 +269,8 @@ module Temporalio args: Array[Object?], id: String, task_queue: String, + static_summary: String?, + static_details: String?, cancellation: Cancellation, cancellation_type: Temporalio::Workflow::ChildWorkflowCancellationType::enum, parent_close_policy: Temporalio::Workflow::ParentClosePolicy::enum, diff --git a/temporalio/sig/temporalio/workflow.rbs b/temporalio/sig/temporalio/workflow.rbs index 2fded9bd..cbc75e24 100644 --- a/temporalio/sig/temporalio/workflow.rbs +++ b/temporalio/sig/temporalio/workflow.rbs @@ -6,6 +6,9 @@ module Temporalio def self.continue_as_new_suggested: -> bool + def self.current_details: -> String + def self.current_details=: (String? details) -> void + def self.current_history_length: -> Integer def self.current_history_size: -> Integer @@ -16,6 +19,7 @@ module Temporalio singleton(Activity::Definition) | Symbol | String activity, *Object? args, ?task_queue: String, + ?summary: String?, ?schedule_to_close_timeout: duration?, ?schedule_to_start_timeout: duration?, ?start_to_close_timeout: duration?, @@ -32,6 +36,8 @@ module Temporalio *Object? args, ?id: String, ?task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?cancellation: Cancellation, ?cancellation_type: ChildWorkflowCancellationType::enum, ?parent_close_policy: ParentClosePolicy::enum, @@ -93,6 +99,8 @@ module Temporalio *Object? args, ?id: String, ?task_queue: String, + ?static_summary: String?, + ?static_details: String?, ?cancellation: Cancellation, ?cancellation_type: Workflow::ChildWorkflowCancellationType::enum, ?parent_close_policy: Workflow::ParentClosePolicy::enum, diff --git a/temporalio/sig/temporalio/workflow/definition.rbs b/temporalio/sig/temporalio/workflow/definition.rbs index 5fd87d15..f4928943 100644 --- a/temporalio/sig/temporalio/workflow/definition.rbs +++ b/temporalio/sig/temporalio/workflow/definition.rbs @@ -5,12 +5,13 @@ module Temporalio def self.workflow_dynamic: (?bool value) -> void def self.workflow_raw_args: (?bool value) -> void def self.workflow_failure_exception_type: (*singleton(Exception) types) -> void - def self.workflow_query_attr_reader: (*Symbol attr_names) -> void + def self.workflow_query_attr_reader: (*Symbol attr_names, ?description: String?) -> void def self.workflow_init: (?bool value) -> void def self.workflow_signal: ( ?name: String | Symbol | nil, + ?description: String?, ?dynamic: bool, ?raw_args: bool, ?unfinished_policy: HandlerUnfinishedPolicy::enum @@ -18,12 +19,14 @@ module Temporalio def self.workflow_query: ( ?name: String | Symbol | nil, + ?description: String?, ?dynamic: bool, ?raw_args: bool ) -> void def self.workflow_update: ( ?name: String | Symbol | nil, + ?description: String?, ?dynamic: bool, ?raw_args: bool, ?unfinished_policy: HandlerUnfinishedPolicy::enum @@ -75,6 +78,7 @@ module Temporalio class Signal attr_reader name: String? attr_reader to_invoke: Symbol | Proc + attr_reader description: String? attr_reader raw_args: bool attr_reader unfinished_policy: HandlerUnfinishedPolicy::enum @@ -83,6 +87,7 @@ module Temporalio def initialize: ( name: String?, to_invoke: Symbol | Proc, + ?description: String?, ?raw_args: bool, ?unfinished_policy: HandlerUnfinishedPolicy::enum ) -> void @@ -91,6 +96,7 @@ module Temporalio class Query attr_reader name: String? attr_reader to_invoke: Symbol | Proc + attr_reader description: String? attr_reader raw_args: bool def self._name_from_parameter: (Workflow::Definition::Query | String | Symbol) -> String @@ -98,6 +104,7 @@ module Temporalio def initialize: ( name: String?, to_invoke: Symbol | Proc, + ?description: String?, ?raw_args: bool ) -> void end @@ -105,6 +112,7 @@ module Temporalio class Update attr_reader name: String? attr_reader to_invoke: Symbol | Proc + attr_reader description: String? attr_reader raw_args: bool attr_reader unfinished_policy: HandlerUnfinishedPolicy::enum attr_reader validator_to_invoke: Symbol | Proc | nil @@ -114,6 +122,7 @@ module Temporalio def initialize: ( name: String?, to_invoke: Symbol | Proc, + ?description: String?, ?raw_args: bool, ?unfinished_policy: HandlerUnfinishedPolicy::enum, ?validator_to_invoke: Symbol | Proc | nil diff --git a/temporalio/test/client_schedule_test.rb b/temporalio/test/client_schedule_test.rb index cf9de672..7a36d302 100644 --- a/temporalio/test/client_schedule_test.rb +++ b/temporalio/test/client_schedule_test.rb @@ -19,6 +19,8 @@ def test_basics # rubocop:disable Metrics/AbcSize { actions: [{ result: { value: 'some-result' } }] }, id: "wf-#{SecureRandom.uuid}", task_queue:, + static_summary: 'my-summary', + static_details: 'my-details', execution_timeout: 1.23, memo: { 'memokey1' => 'memoval1' } ) @@ -73,6 +75,8 @@ def test_basics # rubocop:disable Metrics/AbcSize assert_instance_of Temporalio::Client::Schedule::Action::StartWorkflow, desc_action assert_equal action.workflow, desc_action.workflow assert_equal 'some-result', desc_action.args.first['actions'].first['result']['value'] # steep:ignore + assert_equal 'my-summary', desc_action.static_summary + assert_equal 'my-details', desc_action.static_details assert_equal action.execution_timeout, desc_action.execution_timeout assert_equal({ 'memokey1' => 'memoval1' }, desc_action.memo) assert_equal({ 'memokey2' => 'memoval2' }, desc.memo) diff --git a/temporalio/test/worker_workflow_handler_test.rb b/temporalio/test/worker_workflow_handler_test.rb index 94493f1c..595aaee3 100644 --- a/temporalio/test/worker_workflow_handler_test.rb +++ b/temporalio/test/worker_workflow_handler_test.rb @@ -764,6 +764,60 @@ def test_update_with_start_cancel end end + def test_update_with_start_start_fail + # Run worker + worker = Temporalio::Worker.new( + client: env.client, + task_queue: "tq-#{SecureRandom.uuid}", + workflows: [UpdateWithStartWorkflow] + ) + worker.run do + id = "wf-#{SecureRandom.uuid}" + # Start the workflow + env.client.start_workflow(UpdateWithStartWorkflow, 123, id:, task_queue: worker.task_queue) + # Try to update-with-start on already existing, confirm call and getting handle fail + start_workflow_operation = Temporalio::Client::WithStartWorkflowOperation.new( + UpdateWithStartWorkflow, 123, + id:, task_queue: worker.task_queue, id_conflict_policy: Temporalio::WorkflowIDConflictPolicy::FAIL + ) + assert_raises(Temporalio::Error::WorkflowAlreadyStartedError) do + env.client.start_update_with_start_workflow( + UpdateWithStartWorkflow.increment_counter, 456, + wait_for_stage: Temporalio::Client::WorkflowUpdateWaitStage::ACCEPTED, start_workflow_operation: + ) + end + assert_raises(Temporalio::Error::WorkflowAlreadyStartedError) do + start_workflow_operation.workflow_handle + end + end + end + + def test_update_with_start_user_metadata + # Run worker + worker = Temporalio::Worker.new( + client: env.client, + task_queue: "tq-#{SecureRandom.uuid}", + workflows: [UpdateWithStartWorkflow] + ) + worker.run do + id = "wf-#{SecureRandom.uuid}" + start_workflow_operation = Temporalio::Client::WithStartWorkflowOperation.new( + UpdateWithStartWorkflow, 123, + id:, task_queue: worker.task_queue, + static_summary: 'my-summary', static_details: 'my-details', + id_conflict_policy: Temporalio::WorkflowIDConflictPolicy::FAIL + ) + # Run and confirm metadata present + env.client.start_update_with_start_workflow( + UpdateWithStartWorkflow.increment_counter, 456, + wait_for_stage: Temporalio::Client::WorkflowUpdateWaitStage::ACCEPTED, start_workflow_operation: + ) + desc = start_workflow_operation.workflow_handle.describe + assert_equal 'my-summary', desc.static_summary + assert_equal 'my-details', desc.static_details + end + end + class SignalWithStartWorkflow < Temporalio::Workflow::Definition workflow_query_attr_reader :events @@ -818,4 +872,28 @@ def test_signal_with_start assert_equal %w[signal workflow-start signal-0 signal-1 signal-2], handle.query(SignalWithStartWorkflow.events) end end + + def test_signal_with_start_user_metadata + # Run worker + worker = Temporalio::Worker.new( + client: env.client, + task_queue: "tq-#{SecureRandom.uuid}", + workflows: [SignalWithStartWorkflow] + ) + worker.run do + # Newly started + id = "wf-#{SecureRandom.uuid}" + start_workflow_operation = Temporalio::Client::WithStartWorkflowOperation.new( + SignalWithStartWorkflow, 'workflow-start', + id:, task_queue: worker.task_queue, + static_summary: 'my-summary', static_details: 'my-details' + ) + handle = env.client.signal_with_start_workflow( + SignalWithStartWorkflow.add_event, 'signal', start_workflow_operation: + ) + desc = handle.describe + assert_equal 'my-summary', desc.static_summary + assert_equal 'my-details', desc.static_details + end + end end diff --git a/temporalio/test/worker_workflow_test.rb b/temporalio/test/worker_workflow_test.rb index 9e4e5492..b3fbe553 100644 --- a/temporalio/test/worker_workflow_test.rb +++ b/temporalio/test/worker_workflow_test.rb @@ -1976,6 +1976,140 @@ def test_reserved_names end end + class UserMetadataWorkflow < Temporalio::Workflow::Definition + def execute(return_immediately) + return 'done' if return_immediately + + # Timer, activity, and child with metadata + Temporalio::Workflow.sleep(0.1, summary: 'my-timer') + + # Timeout over wait condition + begin + Temporalio::Workflow.timeout(0.1, summary: 'my-timeout-timer') { Temporalio::Workflow.wait_condition { false } } + raise 'Did not timeout' + rescue Timeout::Error + # Ignore + end + + # Activity + Temporalio::Workflow.execute_activity( + UserMetadataActivity, + start_to_close_timeout: 30, + summary: 'my-activity' + ) + + # Child + Temporalio::Workflow.execute_child_workflow( + UserMetadataWorkflow, true, + static_summary: 'my-child', static_details: 'my-child-details' + ) + end + end + + class UserMetadataActivity < Temporalio::Activity::Definition + def execute + 'done' + end + end + + def test_user_metadata + execute_workflow(UserMetadataWorkflow, false, activities: [UserMetadataActivity]) do |handle| + assert_equal 'done', handle.result + # Check history + events = handle.fetch_history.events + timers = events.select(&:timer_started_event_attributes) + assert_equal '"my-timer"', timers.first.user_metadata.summary.data + assert_equal '"my-timeout-timer"', timers.last.user_metadata.summary.data + assert_equal '"my-activity"', events.find(&:activity_task_scheduled_event_attributes).user_metadata.summary.data + child = events.find(&:start_child_workflow_execution_initiated_event_attributes) + assert_equal '"my-child"', child.user_metadata.summary.data + assert_equal '"my-child-details"', child.user_metadata.details.data + + # Describe the child and confirm metadata + child_desc = env.client.workflow_handle( + child.start_child_workflow_execution_initiated_event_attributes.workflow_id + ).describe + assert_equal 'my-child', child_desc.static_summary + assert_equal 'my-child-details', child_desc.static_details + end + end + + class WorkflowMetadataWorkflow < Temporalio::Workflow::Definition + workflow_query_attr_reader :continue, description: 'continue description' + + def execute + Temporalio::Workflow.current_details = 'initial current details' + Temporalio::Workflow.signal_handlers['some manual signal'] = Temporalio::Workflow::Definition::Signal.new( + name: 'some manual signal', + to_invoke: proc {}, + description: 'some manual signal description' + ) + Temporalio::Workflow.wait_condition { @continue } + Temporalio::Workflow.current_details = 'final current details' + end + + workflow_signal + def some_signal; end + + workflow_signal name: 'some signal', description: 'some signal description' + def some_other_signal; end + + workflow_query description: 'some query description', dynamic: true + def some_query(name, *args); end + + workflow_update description: 'some update description' + def some_update + @continue = true + end + + workflow_update name: 'some update' + def some_other_update; end + end + + def test_workflow_metadata + execute_workflow(WorkflowMetadataWorkflow) do |handle| + # Check workflow metadata + assert_equal Temporalio::Api::Sdk::V1::WorkflowMetadata.new( + definition: Temporalio::Api::Sdk::V1::WorkflowDefinition.new( + type: 'WorkflowMetadataWorkflow', + query_definitions: [ + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'continue', description: 'continue description' + ), + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + description: 'some query description' + ) + ], + signal_definitions: [ + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'some_signal' + ), + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'some signal', description: 'some signal description' + ), + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'some manual signal', description: 'some manual signal description' + ) + ], + update_definitions: [ + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'some_update', description: 'some update description' + ), + Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new( + name: 'some update' + ) + ] + ), + current_details: 'initial current details' + ), handle.query(:__temporal_workflow_metadata) + + # Complete and check final details + handle.execute_update(WorkflowMetadataWorkflow.some_update) + handle.result + assert_equal 'final current details', handle.query(:__temporal_workflow_metadata).current_details + end + end + # TODO(cretz): To test # * Common # * Eager workflow start