Skip to content

Commit 95fc9c8

Browse files
hannesfostieDeRaukantstormnagl-stripejeffschoner-stripe
authored
Merge upstream (#5)
* Pass config to the error handler instead of using the global config * Fix example tests * Make the config property in the workflow context publicly readable * [Fix] Retryer GRPC error lookup (coinbase#109) * Fix issue with GRPC error lookup in Retryer * Rename spec file for retryer to contain _spec * [Feature] Add id and domain to workflow context's metadata (coinbase#110) * Start syncing id and domain on workflow context metadata * Fixed tests Co-authored-by: DeRauk Gibble <derauk.gibble@coinbase.com> * Explicit docker-compose project name (coinbase#114) * Add YARD documentation for Temporal::Client (coinbase#113) * Add YARD documentation for Temporal::Client * Add yard gem * Fix @option tag * Typo fix * Add signal arguments to start_workflow (support for signal_with_start) (coinbase#112) * Add signal arguments to start_workflow (to support signal_with_start) * Move signal arguments to the options hash * PR feedback * Fix merge error * Extend #wait_for to take multiple futures and a condition block (coinbase#111) * Differentiate TARGET_WILDCARD and WILDCARD, allow comparison with EventTarget objects (coinbase#118) * Turn off schedule_to_start activity timeout by default (coinbase#119) * Separate options from keyword args in #start_workflow (coinbase#117) * Separate options from keyword args in #start_workflow * fixup! Separate options from keyword args in #start_workflow * Surface additional workflow metadata on workflow context (coinbase#120) * Refactor metadata generation * Make task queue available on workflow metadata, add example test * Expose workflow start time metadata * Add memos (coinbase#121) * Add describe_namespace (coinbase#122) * Add describe_namespace * Feedback * Improve header serialization and propagation (coinbase#124) * [Fix] Non-started activity cancellation (coinbase#125) * Fix event target map entry for ACTIVITY_CANCELED event * Fix cancellation of a non-started activity * fixup! Fix event target map entry for ACTIVITY_CANCELED event Co-authored-by: DeRauk Gibble <derauk.gibble@coinbase.com> Co-authored-by: DeRauk Gibble <derauk@gmail.com> Co-authored-by: Anthony Dmitriyev <antstorm@gmail.com> Co-authored-by: nagl-stripe <86737162+nagl-stripe@users.noreply.github.com> Co-authored-by: jeffschoner-stripe <63118764+jeffschoner-stripe@users.noreply.github.com> Co-authored-by: Drew Hoskins <37816070+drewhoskins-stripe@users.noreply.github.com>
1 parent cccf233 commit 95fc9c8

File tree

65 files changed

+1390
-228
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1390
-228
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# Ruby worker for Temporal
1+
# Ruby SDK for Temporal
22

33
[![Coverage Status](https://coveralls.io/repos/github/coinbase/temporal-ruby/badge.svg?branch=master)](https://coveralls.io/github/coinbase/temporal-ruby?branch=master)
44

55
<img src="./assets/temporal_logo.png" width="250" align="right" alt="Temporal" />
66

77
A pure Ruby library for defining and running Temporal workflows and activities.
88

9-
To find more about Temporal please visit <https://temporal.io/>.
9+
To find more about Temporal itself please visit <https://temporal.io/>.
1010

1111

1212
## Getting Started

examples/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
COMPOSE_PROJECT_NAME=temporal-ruby-examples

examples/bin/worker

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ worker.register_workflow(HelloWorldWorkflow)
3131
worker.register_workflow(LocalHelloWorldWorkflow)
3232
worker.register_workflow(LongWorkflow)
3333
worker.register_workflow(LoopWorkflow)
34+
worker.register_workflow(MetadataWorkflow)
3435
worker.register_workflow(ParentWorkflow)
3536
worker.register_workflow(ProcessFileWorkflow)
3637
worker.register_workflow(QuickTimeoutWorkflow)
@@ -39,9 +40,11 @@ worker.register_workflow(ReleaseWorkflow)
3940
worker.register_workflow(ResultWorkflow)
4041
worker.register_workflow(SerialHelloWorldWorkflow)
4142
worker.register_workflow(SideEffectWorkflow)
43+
worker.register_workflow(SignalWithStartWorkflow)
4244
worker.register_workflow(SimpleTimerWorkflow)
4345
worker.register_workflow(TimeoutWorkflow)
4446
worker.register_workflow(TripBookingWorkflow)
47+
worker.register_workflow(WaitForWorkflow)
4548

4649
worker.register_activity(AsyncActivity)
4750
worker.register_activity(EchoActivity)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'workflows/long_workflow'
2+
3+
describe 'Activity cancellation' do
4+
let(:workflow_id) { SecureRandom.uuid }
5+
6+
it 'cancels a running activity' do
7+
run_id = Temporal.start_workflow(LongWorkflow, options: { workflow_id: workflow_id })
8+
9+
# Signal workflow after starting, allowing it to schedule the first activity
10+
sleep 0.5
11+
Temporal.signal_workflow(LongWorkflow, :CANCEL, workflow_id, run_id)
12+
13+
result = Temporal.await_workflow_result(
14+
LongWorkflow,
15+
workflow_id: workflow_id,
16+
run_id: run_id,
17+
)
18+
19+
expect(result).to be_a(LongRunningActivity::Canceled)
20+
expect(result.message).to eq('cancel activity request received')
21+
end
22+
23+
it 'cancels a non-started activity' do
24+
# Workflow is started with a signal which will cancel an activity before it has started
25+
run_id = Temporal.start_workflow(LongWorkflow, options: {
26+
workflow_id: workflow_id,
27+
signal_name: :CANCEL
28+
})
29+
30+
result = Temporal.await_workflow_result(
31+
LongWorkflow,
32+
workflow_id: workflow_id,
33+
run_id: run_id,
34+
)
35+
36+
expect(result).to be_a(Temporal::ActivityCanceled)
37+
expect(result.message).to eq('ACTIVITY_ID_NOT_STARTED')
38+
end
39+
end

examples/spec/integration/await_workflow_result_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@
9595
run_id = Temporal.start_workflow(
9696
LoopWorkflow,
9797
2, # it continues as new if this arg is > 1
98-
{ options: { workflow_id: workflow_id } },
98+
options: {
99+
workflow_id: workflow_id,
100+
},
99101
)
100102

101103
expect do
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'workflows/loop_workflow'
2+
3+
describe LoopWorkflow do
4+
it 'workflow continues as new into a new run' do
5+
workflow_id = SecureRandom.uuid
6+
memo = {
7+
'my-memo' => 'foo',
8+
}
9+
headers = {
10+
'my-header' => 'bar',
11+
}
12+
run_id = Temporal.start_workflow(
13+
LoopWorkflow,
14+
2, # it continues as new if this arg is > 1
15+
options: {
16+
workflow_id: workflow_id,
17+
memo: memo,
18+
headers: headers,
19+
},
20+
)
21+
22+
# First run will throw because it continued as new
23+
next_run_id = nil
24+
expect do
25+
Temporal.await_workflow_result(
26+
LoopWorkflow,
27+
workflow_id: workflow_id,
28+
run_id: run_id,
29+
)
30+
end.to raise_error(Temporal::WorkflowRunContinuedAsNew) do |error|
31+
next_run_id = error.new_run_id
32+
end
33+
34+
expect(next_run_id).to_not eq(nil)
35+
36+
# Second run will not throw because it returns rather than continues as new.
37+
final_result = Temporal.await_workflow_result(
38+
LoopWorkflow,
39+
workflow_id: workflow_id,
40+
run_id: next_run_id,
41+
)
42+
43+
expect(final_result[:count]).to eq(1)
44+
45+
# memo and headers should be copied to the next run automatically
46+
expect(final_result[:memo]).to eq(memo)
47+
expect(final_result[:headers]).to eq(headers)
48+
end
49+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require 'temporal/errors'
2+
3+
describe 'Temporal.describe_namespace' do
4+
it 'returns a value' do
5+
description = 'Namespace for temporal-ruby integration test'
6+
begin
7+
Temporal.register_namespace('a_test_namespace', description)
8+
rescue Temporal::NamespaceAlreadyExistsFailure
9+
end
10+
result = Temporal.describe_namespace('a_test_namespace')
11+
expect(result).to be_an_instance_of(Temporal::Api::WorkflowService::V1::DescribeNamespaceResponse)
12+
expect(result.namespace_info.name).to eq('a_test_namespace')
13+
expect(result.namespace_info.state).to eq(:NAMESPACE_STATE_REGISTERED)
14+
expect(result.namespace_info.description).to eq(description)
15+
end
16+
end
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
require 'workflows/metadata_workflow'
2+
3+
describe MetadataWorkflow do
4+
subject { described_class }
5+
6+
it 'gets task queue from running workflow' do
7+
workflow_id = 'task-queue-' + SecureRandom.uuid
8+
run_id = Temporal.start_workflow(
9+
subject,
10+
options: { workflow_id: workflow_id }
11+
)
12+
13+
actual_result = Temporal.await_workflow_result(
14+
subject,
15+
workflow_id: workflow_id,
16+
run_id: run_id,
17+
)
18+
19+
expect(actual_result.task_queue).to eq(Temporal.configuration.task_queue)
20+
end
21+
22+
it 'workflow can retrieve its headers' do
23+
workflow_id = 'header_test_wf-' + SecureRandom.uuid
24+
25+
run_id = Temporal.start_workflow(
26+
MetadataWorkflow,
27+
options: {
28+
workflow_id: workflow_id,
29+
headers: { 'foo' => 'bar' },
30+
}
31+
)
32+
33+
actual_result = Temporal.await_workflow_result(
34+
MetadataWorkflow,
35+
workflow_id: workflow_id,
36+
run_id: run_id,
37+
)
38+
expect(actual_result.headers).to eq({ 'foo' => 'bar' })
39+
end
40+
41+
it 'workflow can retrieve its run started at' do
42+
workflow_id = 'started_at_test_wf-' + SecureRandom.uuid
43+
44+
run_id = Temporal.start_workflow(
45+
MetadataWorkflow,
46+
options: { workflow_id: workflow_id }
47+
)
48+
49+
actual_result = Temporal.await_workflow_result(
50+
MetadataWorkflow,
51+
workflow_id: workflow_id,
52+
run_id: run_id,
53+
)
54+
expect(Time.now - actual_result.run_started_at).to be_between(0, 30)
55+
end
56+
57+
it 'gets memo from workflow execution info' do
58+
workflow_id = 'memo_execution_test_wf-' + SecureRandom.uuid
59+
run_id = Temporal.start_workflow(subject, options: { workflow_id: workflow_id, memo: { 'foo' => 'bar' } })
60+
61+
actual_result = Temporal.await_workflow_result(
62+
subject,
63+
workflow_id: workflow_id,
64+
run_id: run_id,
65+
)
66+
expect(actual_result.memo['foo']).to eq('bar')
67+
68+
expect(Temporal.fetch_workflow_execution_info(
69+
'ruby-samples', workflow_id, nil
70+
).memo).to eq({ 'foo' => 'bar' })
71+
end
72+
73+
it 'gets memo from workflow context with no memo' do
74+
workflow_id = 'memo_context_no_memo_test_wf-' + SecureRandom.uuid
75+
76+
run_id = Temporal.start_workflow(
77+
subject,
78+
options: { workflow_id: workflow_id }
79+
)
80+
81+
actual_result = Temporal.await_workflow_result(
82+
subject,
83+
workflow_id: workflow_id,
84+
run_id: run_id,
85+
)
86+
expect(actual_result.memo).to eq({})
87+
expect(Temporal.fetch_workflow_execution_info(
88+
'ruby-samples', workflow_id, nil
89+
).memo).to eq({})
90+
end
91+
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require 'workflows/signal_with_start_workflow'
2+
3+
describe 'signal with start' do
4+
5+
it 'signals at workflow start time' do
6+
workflow_id = SecureRandom.uuid
7+
run_id = Temporal.start_workflow(
8+
SignalWithStartWorkflow,
9+
'signal_name',
10+
0.1,
11+
options: {
12+
workflow_id: workflow_id,
13+
signal_name: 'signal_name',
14+
signal_input: 'expected value',
15+
}
16+
)
17+
18+
result = Temporal.await_workflow_result(
19+
SignalWithStartWorkflow,
20+
workflow_id: workflow_id,
21+
run_id: run_id,
22+
)
23+
24+
expect(result).to eq('expected value') # the workflow should return the signal value
25+
end
26+
27+
it 'signals at workflow start time with name only' do
28+
workflow_id = SecureRandom.uuid
29+
run_id = Temporal.start_workflow(
30+
SignalWithStartWorkflow,
31+
'signal_name',
32+
0.1,
33+
options: {
34+
workflow_id: workflow_id,
35+
signal_name: 'signal_name',
36+
}
37+
)
38+
39+
result = Temporal.await_workflow_result(
40+
SignalWithStartWorkflow,
41+
workflow_id: workflow_id,
42+
run_id: run_id,
43+
)
44+
45+
expect(result).to eq(nil) # the workflow should return the signal value
46+
end
47+
48+
it 'does not launch a new workflow when signaling a running workflow through signal_with_start' do
49+
workflow_id = SecureRandom.uuid
50+
run_id = Temporal.start_workflow(
51+
SignalWithStartWorkflow,
52+
'signal_name',
53+
10,
54+
options: {
55+
workflow_id: workflow_id,
56+
signal_name: 'signal_name',
57+
signal_input: 'expected value',
58+
}
59+
)
60+
61+
second_run_id = Temporal.start_workflow(
62+
SignalWithStartWorkflow,
63+
'signal_name',
64+
0.1,
65+
options: {
66+
workflow_id: workflow_id,
67+
signal_name: 'signal_name',
68+
signal_input: 'expected value',
69+
}
70+
)
71+
72+
# If the run ids are the same, then we didn't start a new workflow
73+
expect(second_run_id).to eq(run_id)
74+
end
75+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'workflows/wait_for_workflow'
2+
3+
describe WaitForWorkflow do
4+
5+
it 'signals at workflow start time' do
6+
workflow_id = SecureRandom.uuid
7+
run_id = Temporal.start_workflow(
8+
WaitForWorkflow,
9+
10, # number of echo activities to run
10+
2, # max activity parallelism
11+
'signal_name',
12+
options: { workflow_id: workflow_id }
13+
)
14+
15+
Temporal.signal_workflow(WaitForWorkflow, 'signal_name', workflow_id, run_id)
16+
17+
result = Temporal.await_workflow_result(
18+
WaitForWorkflow,
19+
workflow_id: workflow_id,
20+
run_id: run_id,
21+
)
22+
23+
expect(result.length).to eq(3)
24+
expect(result[:signal]).to eq(true)
25+
expect(result[:timer]).to eq(true)
26+
expect(result[:activity]).to eq(true)
27+
end
28+
end

0 commit comments

Comments
 (0)