ruote 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +166 -1
- data/CREDITS.txt +36 -17
- data/LICENSE.txt +1 -1
- data/README.rdoc +1 -7
- data/Rakefile +38 -29
- data/TODO.txt +93 -52
- data/lib/ruote-fs.rb +3 -0
- data/lib/ruote.rb +5 -1
- data/lib/ruote/context.rb +140 -35
- data/lib/ruote/dashboard.rb +1247 -0
- data/lib/ruote/{engine → dboard}/process_error.rb +22 -2
- data/lib/ruote/dboard/process_status.rb +587 -0
- data/lib/ruote/engine.rb +6 -871
- data/lib/ruote/exp/command.rb +7 -2
- data/lib/ruote/exp/commanded.rb +2 -2
- data/lib/ruote/exp/condition.rb +38 -13
- data/lib/ruote/exp/fe_add_branches.rb +1 -1
- data/lib/ruote/exp/fe_apply.rb +1 -1
- data/lib/ruote/exp/fe_await.rb +357 -0
- data/lib/ruote/exp/fe_cancel_process.rb +17 -3
- data/lib/ruote/exp/fe_command.rb +8 -4
- data/lib/ruote/exp/fe_concurrence.rb +218 -18
- data/lib/ruote/exp/fe_concurrent_iterator.rb +71 -10
- data/lib/ruote/exp/fe_cron.rb +3 -10
- data/lib/ruote/exp/fe_cursor.rb +14 -4
- data/lib/ruote/exp/fe_define.rb +3 -1
- data/lib/ruote/exp/fe_echo.rb +1 -1
- data/lib/ruote/exp/fe_equals.rb +1 -1
- data/lib/ruote/exp/fe_error.rb +1 -1
- data/lib/ruote/exp/fe_filter.rb +163 -4
- data/lib/ruote/exp/fe_forget.rb +21 -4
- data/lib/ruote/exp/fe_given.rb +1 -1
- data/lib/ruote/exp/fe_if.rb +1 -1
- data/lib/ruote/exp/fe_inc.rb +102 -35
- data/lib/ruote/exp/fe_iterator.rb +47 -12
- data/lib/ruote/exp/fe_listen.rb +96 -11
- data/lib/ruote/exp/fe_lose.rb +31 -4
- data/lib/ruote/exp/fe_noop.rb +1 -1
- data/lib/ruote/exp/fe_on_error.rb +109 -0
- data/lib/ruote/exp/fe_once.rb +10 -19
- data/lib/ruote/exp/fe_participant.rb +90 -28
- data/lib/ruote/exp/fe_read.rb +69 -0
- data/lib/ruote/exp/fe_redo.rb +3 -2
- data/lib/ruote/exp/fe_ref.rb +57 -27
- data/lib/ruote/exp/fe_registerp.rb +1 -3
- data/lib/ruote/exp/fe_reserve.rb +1 -1
- data/lib/ruote/exp/fe_restore.rb +6 -6
- data/lib/ruote/exp/fe_save.rb +12 -19
- data/lib/ruote/exp/fe_sequence.rb +38 -2
- data/lib/ruote/exp/fe_set.rb +143 -40
- data/lib/ruote/exp/{fe_let.rb → fe_stall.rb} +7 -38
- data/lib/ruote/exp/fe_subprocess.rb +8 -2
- data/lib/ruote/exp/fe_that.rb +1 -1
- data/lib/ruote/exp/fe_undo.rb +40 -4
- data/lib/ruote/exp/fe_unregisterp.rb +1 -3
- data/lib/ruote/exp/fe_wait.rb +12 -25
- data/lib/ruote/exp/{flowexpression.rb → flow_expression.rb} +375 -229
- data/lib/ruote/exp/iterator.rb +2 -2
- data/lib/ruote/exp/merge.rb +78 -17
- data/lib/ruote/exp/ro_attributes.rb +46 -36
- data/lib/ruote/exp/ro_filters.rb +34 -8
- data/lib/ruote/exp/ro_on_x.rb +431 -0
- data/lib/ruote/exp/ro_persist.rb +19 -7
- data/lib/ruote/exp/ro_timers.rb +123 -0
- data/lib/ruote/exp/ro_variables.rb +90 -29
- data/lib/ruote/fei.rb +57 -3
- data/lib/ruote/fs.rb +3 -0
- data/lib/ruote/id/mnemo_wfid_generator.rb +30 -7
- data/lib/ruote/id/wfid_generator.rb +17 -38
- data/lib/ruote/log/default_history.rb +23 -9
- data/lib/ruote/log/fancy_printing.rb +265 -0
- data/lib/ruote/log/storage_history.rb +23 -13
- data/lib/ruote/log/wait_logger.rb +224 -17
- data/lib/ruote/observer.rb +82 -0
- data/lib/ruote/part/block_participant.rb +65 -28
- data/lib/ruote/part/code_participant.rb +81 -0
- data/lib/ruote/part/engine_participant.rb +7 -2
- data/lib/ruote/part/local_participant.rb +221 -21
- data/lib/ruote/part/no_op_participant.rb +1 -1
- data/lib/ruote/part/null_participant.rb +1 -1
- data/lib/ruote/part/participant.rb +50 -0
- data/lib/ruote/part/rev_participant.rb +178 -0
- data/lib/ruote/part/smtp_participant.rb +2 -2
- data/lib/ruote/part/storage_participant.rb +228 -60
- data/lib/ruote/part/template.rb +1 -1
- data/lib/ruote/participant.rb +2 -0
- data/lib/ruote/reader.rb +205 -68
- data/lib/ruote/reader/json.rb +49 -0
- data/lib/ruote/reader/radial.rb +303 -0
- data/lib/ruote/reader/ruby_dsl.rb +44 -9
- data/lib/ruote/reader/xml.rb +11 -8
- data/lib/ruote/receiver/base.rb +98 -45
- data/lib/ruote/storage/base.rb +104 -35
- data/lib/ruote/storage/composite_storage.rb +50 -60
- data/lib/ruote/storage/fs_storage.rb +25 -34
- data/lib/ruote/storage/hash_storage.rb +38 -36
- data/lib/ruote/svc/dispatch_pool.rb +104 -35
- data/lib/ruote/svc/dollar_sub.rb +10 -8
- data/lib/ruote/svc/error_handler.rb +108 -52
- data/lib/ruote/svc/expression_map.rb +3 -3
- data/lib/ruote/svc/participant_list.rb +160 -55
- data/lib/ruote/svc/tracker.rb +31 -31
- data/lib/ruote/svc/treechecker.rb +28 -16
- data/lib/ruote/tree_dot.rb +1 -1
- data/lib/ruote/util/deep.rb +143 -0
- data/lib/ruote/util/filter.rb +125 -18
- data/lib/ruote/util/hashdot.rb +15 -13
- data/lib/ruote/util/look.rb +1 -1
- data/lib/ruote/util/lookup.rb +60 -22
- data/lib/ruote/util/misc.rb +63 -18
- data/lib/ruote/util/mpatch.rb +53 -0
- data/lib/ruote/util/ometa.rb +1 -2
- data/lib/ruote/util/process_observer.rb +177 -0
- data/lib/ruote/util/subprocess.rb +1 -1
- data/lib/ruote/util/time.rb +2 -2
- data/lib/ruote/util/tree.rb +64 -2
- data/lib/ruote/version.rb +3 -2
- data/lib/ruote/worker.rb +421 -92
- data/lib/ruote/workitem.rb +157 -22
- data/ruote.gemspec +15 -9
- data/test/bm/ci.rb +0 -2
- data/test/bm/ici.rb +0 -2
- data/test/bm/load_26c.rb +0 -3
- data/test/bm/mega.rb +0 -2
- data/test/functional/base.rb +57 -43
- data/test/functional/concurrent_base.rb +16 -13
- data/test/functional/ct_0_concurrence.rb +7 -11
- data/test/functional/ct_1_iterator.rb +9 -11
- data/test/functional/ct_2_cancel.rb +28 -17
- data/test/functional/eft_0_flow_expression.rb +35 -0
- data/test/functional/eft_10_cancel_process.rb +1 -1
- data/test/functional/eft_11_wait.rb +13 -13
- data/test/functional/eft_12_listen.rb +199 -66
- data/test/functional/eft_13_iterator.rb +95 -29
- data/test/functional/eft_14_cursor.rb +74 -24
- data/test/functional/eft_15_loop.rb +7 -7
- data/test/functional/eft_16_if.rb +1 -1
- data/test/functional/eft_17_equals.rb +1 -1
- data/test/functional/eft_18_concurrent_iterator.rb +156 -68
- data/test/functional/eft_19_reserve.rb +15 -15
- data/test/functional/eft_1_echo.rb +1 -1
- data/test/functional/eft_20_save.rb +51 -9
- data/test/functional/eft_21_restore.rb +1 -1
- data/test/functional/eft_22_noop.rb +1 -1
- data/test/functional/eft_23_apply.rb +1 -1
- data/test/functional/eft_24_add_branches.rb +7 -8
- data/test/functional/eft_25_command.rb +1 -1
- data/test/functional/eft_26_error.rb +11 -11
- data/test/functional/eft_27_inc.rb +111 -67
- data/test/functional/eft_28_once.rb +16 -16
- data/test/functional/eft_29_cron.rb +9 -9
- data/test/functional/eft_2_sequence.rb +23 -4
- data/test/functional/eft_30_ref.rb +36 -24
- data/test/functional/eft_31_registerp.rb +24 -24
- data/test/functional/eft_32_lose.rb +46 -20
- data/test/functional/eft_34_given.rb +1 -1
- data/test/functional/eft_35_filter.rb +161 -7
- data/test/functional/eft_36_read.rb +97 -0
- data/test/functional/{eft_0_process_definition.rb → eft_37_process_definition.rb} +4 -4
- data/test/functional/eft_38_on_error.rb +195 -0
- data/test/functional/eft_39_stall.rb +35 -0
- data/test/functional/eft_3_participant.rb +77 -22
- data/test/functional/eft_40_await.rb +297 -0
- data/test/functional/eft_4_set.rb +110 -11
- data/test/functional/eft_5_subprocess.rb +27 -5
- data/test/functional/eft_6_concurrence.rb +299 -60
- data/test/functional/eft_7_forget.rb +24 -22
- data/test/functional/eft_8_undo.rb +52 -15
- data/test/functional/eft_9_redo.rb +18 -20
- data/test/functional/ft_0_worker.rb +122 -13
- data/test/functional/ft_10_dollar.rb +77 -16
- data/test/functional/ft_11_recursion.rb +9 -9
- data/test/functional/ft_12_launchitem.rb +7 -9
- data/test/functional/ft_13_variables.rb +125 -22
- data/test/functional/ft_14_re_apply.rb +112 -56
- data/test/functional/ft_15_timeout.rb +64 -33
- data/test/functional/ft_16_participant_params.rb +59 -6
- data/test/functional/ft_17_conditional.rb +68 -2
- data/test/functional/ft_18_kill.rb +48 -30
- data/test/functional/ft_19_participant_code.rb +67 -0
- data/test/functional/ft_1_process_status.rb +222 -150
- data/test/functional/ft_20_storage_participant.rb +445 -44
- data/test/functional/ft_21_forget.rb +21 -26
- data/test/functional/ft_22_process_definitions.rb +8 -6
- data/test/functional/ft_23_load_defs.rb +29 -5
- data/test/functional/ft_24_block_participant.rb +199 -20
- data/test/functional/ft_25_receiver.rb +98 -46
- data/test/functional/ft_26_participant_rtimeout.rb +34 -26
- data/test/functional/ft_27_var_indirection.rb +40 -5
- data/test/functional/ft_28_null_noop_participants.rb +5 -5
- data/test/functional/ft_29_part_template.rb +2 -2
- data/test/functional/ft_2_errors.rb +106 -74
- data/test/functional/ft_30_smtp_participant.rb +7 -7
- data/test/functional/ft_31_part_blocking.rb +11 -11
- data/test/functional/ft_32_scope.rb +50 -0
- data/test/functional/ft_33_participant_subprocess_priority.rb +3 -3
- data/test/functional/ft_34_cursor_rewind.rb +14 -14
- data/test/functional/ft_35_add_service.rb +67 -9
- data/test/functional/ft_36_storage_history.rb +92 -24
- data/test/functional/ft_37_default_history.rb +35 -23
- data/test/functional/ft_38_participant_more.rb +189 -32
- data/test/functional/ft_39_wait_for.rb +25 -25
- data/test/functional/ft_3_participant_registration.rb +235 -107
- data/test/functional/ft_40_wait_logger.rb +105 -18
- data/test/functional/ft_41_participants.rb +13 -12
- data/test/functional/ft_42_storage_copy.rb +12 -12
- data/test/functional/ft_43_participant_on_reply.rb +85 -11
- data/test/functional/ft_44_var_participant.rb +5 -5
- data/test/functional/ft_45_participant_accept.rb +3 -3
- data/test/functional/ft_46_launch_single.rb +17 -17
- data/test/functional/ft_47_wfids.rb +41 -0
- data/test/functional/ft_48_lose.rb +19 -25
- data/test/functional/ft_49_engine_on_error.rb +54 -70
- data/test/functional/ft_4_cancel.rb +84 -26
- data/test/functional/ft_50_engine_config.rb +4 -4
- data/test/functional/ft_51_misc.rb +12 -12
- data/test/functional/ft_52_case.rb +17 -17
- data/test/functional/ft_53_engine_on_terminate.rb +18 -21
- data/test/functional/ft_54_patterns.rb +18 -16
- data/test/functional/ft_55_engine_participant.rb +55 -55
- data/test/functional/ft_56_filter_attribute.rb +90 -52
- data/test/functional/ft_57_rev_participant.rb +252 -0
- data/test/functional/ft_58_workitem.rb +150 -0
- data/test/functional/ft_59_pause.rb +329 -0
- data/test/functional/ft_5_on_error.rb +430 -77
- data/test/functional/ft_60_code_participant.rb +65 -0
- data/test/functional/ft_61_trailing_fields.rb +34 -0
- data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
- data/test/functional/ft_63_participants_221.rb +458 -0
- data/test/functional/ft_64_stash.rb +41 -0
- data/test/functional/ft_65_timers.rb +313 -0
- data/test/functional/ft_66_flank.rb +133 -0
- data/test/functional/ft_67_radial_misc.rb +34 -0
- data/test/functional/ft_68_reput.rb +72 -0
- data/test/functional/ft_69_worker_info.rb +56 -0
- data/test/functional/ft_6_on_cancel.rb +189 -36
- data/test/functional/ft_70_take_and_discard_attributes.rb +94 -0
- data/test/functional/ft_71_retries.rb +144 -0
- data/test/functional/ft_72_on_terminate.rb +60 -0
- data/test/functional/ft_73_raise_msg.rb +107 -0
- data/test/functional/ft_74_respark.rb +106 -0
- data/test/functional/ft_75_context.rb +66 -0
- data/test/functional/ft_76_observer.rb +53 -0
- data/test/functional/ft_77_process_observer.rb +157 -0
- data/test/functional/ft_78_part_participant.rb +37 -0
- data/test/functional/ft_7_tags.rb +238 -50
- data/test/functional/ft_8_participant_consumption.rb +27 -21
- data/test/functional/ft_9_subprocesses.rb +48 -18
- data/test/functional/restart_base.rb +4 -6
- data/test/functional/rt_0_wait.rb +10 -10
- data/test/functional/rt_1_listen.rb +6 -6
- data/test/functional/rt_2_errors.rb +12 -12
- data/test/functional/rt_3_once.rb +17 -12
- data/test/functional/rt_4_cron.rb +17 -17
- data/test/functional/rt_5_timeout.rb +13 -13
- data/test/functional/signals.rb +103 -0
- data/test/functional/storage.rb +730 -0
- data/test/functional/storage_helper.rb +48 -35
- data/test/functional/test.rb +6 -2
- data/test/misc/idle.rb +21 -0
- data/test/misc/light.rb +29 -0
- data/test/path_helper.rb +1 -1
- data/test/test.rb +2 -5
- data/test/test_helper.rb +13 -0
- data/test/unit/test.rb +1 -4
- data/test/unit/ut_0_ruby_reader.rb +25 -9
- data/test/unit/ut_10_participants.rb +47 -0
- data/test/unit/ut_11_lookup.rb +59 -2
- data/test/unit/ut_12_wait_logger.rb +123 -0
- data/test/unit/ut_14_is_uri.rb +1 -1
- data/test/unit/ut_15_util.rb +1 -1
- data/test/unit/ut_16_reader.rb +136 -14
- data/test/unit/ut_17_merge.rb +155 -0
- data/test/unit/ut_19_part_template.rb +1 -1
- data/test/unit/ut_1_fei.rb +11 -2
- data/test/unit/ut_20_composite_storage.rb +27 -1
- data/test/unit/{ut_21_participant_list.rb → ut_21_svc_participant_list.rb} +2 -3
- data/test/unit/ut_22_filter.rb +231 -10
- data/test/unit/ut_23_svc_tracker.rb +48 -0
- data/test/unit/ut_24_radial_reader.rb +458 -0
- data/test/unit/ut_25_process_status.rb +143 -0
- data/test/unit/ut_26_deep.rb +131 -0
- data/test/unit/ut_2_dashboard.rb +114 -0
- data/test/unit/ut_3_worker.rb +54 -0
- data/test/unit/ut_4_expmap.rb +1 -1
- data/test/unit/ut_5_tree.rb +23 -23
- data/test/unit/ut_6_condition.rb +71 -29
- data/test/unit/ut_7_workitem.rb +18 -4
- data/test/unit/ut_8_tree_to_dot.rb +1 -1
- data/test/unit/ut_9_xml_reader.rb +1 -1
- metadata +142 -63
- data/jruby_issue.txt +0 -32
- data/lib/ruote/engine/process_status.rb +0 -403
- data/lib/ruote/log/pretty.rb +0 -165
- data/lib/ruote/log/test_logger.rb +0 -204
- data/lib/ruote/util/serializer.rb +0 -103
- data/phil.txt +0 -14
- data/test/functional/eft_33_let.rb +0 -31
- data/test/functional/ft_19_alias.rb +0 -33
- data/test/functional/ft_47_wfid_generator.rb +0 -54
- data/test/unit/storage.rb +0 -403
- data/test/unit/storages.rb +0 -37
- data/test/unit/ut_13_serializer.rb +0 -65
- data/test/unit/ut_18_engine.rb +0 -47
- data/test/unit/ut_3_wait_logger.rb +0 -39
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#--
|
|
2
|
-
# Copyright (c) 2005-
|
|
2
|
+
# Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
|
|
3
3
|
#
|
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
|
@@ -30,6 +30,12 @@ module Ruote
|
|
|
30
30
|
#
|
|
31
31
|
class ProcessError
|
|
32
32
|
|
|
33
|
+
# When this instance was returned by Ruote::Dashboard#ps or
|
|
34
|
+
# Ruote::Dashboard#process, this attribute will point to the flow
|
|
35
|
+
# expression where the error occurred.
|
|
36
|
+
#
|
|
37
|
+
attr_accessor :flow_expression
|
|
38
|
+
|
|
33
39
|
def initialize(h)
|
|
34
40
|
@h = h
|
|
35
41
|
end
|
|
@@ -46,6 +52,10 @@ module Ruote
|
|
|
46
52
|
@h['msg']
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
def details
|
|
56
|
+
@h['details']
|
|
57
|
+
end
|
|
58
|
+
|
|
49
59
|
def fei
|
|
50
60
|
Ruote::FlowExpressionId.new(msg['fei'])
|
|
51
61
|
end
|
|
@@ -73,6 +83,8 @@ module Ruote
|
|
|
73
83
|
@h
|
|
74
84
|
end
|
|
75
85
|
|
|
86
|
+
alias h to_h
|
|
87
|
+
|
|
76
88
|
# 'apply', 'reply', 'receive', ... Indicates in which "direction" the
|
|
77
89
|
# error occured.
|
|
78
90
|
#
|
|
@@ -86,12 +98,20 @@ module Ruote
|
|
|
86
98
|
@h['msg']['workitem'] && @h['msg']['workitem']['fields']
|
|
87
99
|
end
|
|
88
100
|
|
|
89
|
-
# Returns an instance of Ruote::Workitem
|
|
101
|
+
# Returns an instance of Ruote::Workitem (the workitem as it was
|
|
102
|
+
# at the error point)
|
|
90
103
|
#
|
|
91
104
|
def workitem
|
|
92
105
|
Ruote::Workitem.new(msg['workitem'])
|
|
93
106
|
end
|
|
94
107
|
|
|
108
|
+
# Returns an array of deviations (see the 'filter' expression) if the
|
|
109
|
+
# error is a Ruote::ValidationError.
|
|
110
|
+
#
|
|
111
|
+
def deviations
|
|
112
|
+
@h['deviations']
|
|
113
|
+
end
|
|
114
|
+
|
|
95
115
|
protected
|
|
96
116
|
|
|
97
117
|
def to_dot(opts)
|
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
# furnished to do so, subject to the following conditions:
|
|
10
|
+
#
|
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
|
12
|
+
# all copies or substantial portions of the Software.
|
|
13
|
+
#
|
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
# THE SOFTWARE.
|
|
21
|
+
#
|
|
22
|
+
# Made in Japan.
|
|
23
|
+
#++
|
|
24
|
+
|
|
25
|
+
require 'ruote/util/tree'
|
|
26
|
+
require 'ruote/dboard/process_error'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
module Ruote
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# A 'view' on the status of a process instance.
|
|
33
|
+
#
|
|
34
|
+
# Returned by the #process and the #processes methods of Ruote::Dashboard.
|
|
35
|
+
#
|
|
36
|
+
class ProcessStatus
|
|
37
|
+
|
|
38
|
+
# The expressions that compose the process instance.
|
|
39
|
+
#
|
|
40
|
+
attr_reader :expressions
|
|
41
|
+
|
|
42
|
+
# Returns the expression at the root of the process instance.
|
|
43
|
+
#
|
|
44
|
+
attr_reader :root_expression
|
|
45
|
+
|
|
46
|
+
# An array of the workitems currently in the storage participant for this
|
|
47
|
+
# process instance.
|
|
48
|
+
#
|
|
49
|
+
# Do not confuse with #workitems
|
|
50
|
+
#
|
|
51
|
+
attr_reader :stored_workitems
|
|
52
|
+
|
|
53
|
+
# An array of errors currently plaguing the process instance. Hopefully,
|
|
54
|
+
# this array is empty.
|
|
55
|
+
#
|
|
56
|
+
attr_reader :errors
|
|
57
|
+
|
|
58
|
+
# An array of schedules (open structs yielding information about the
|
|
59
|
+
# schedules of this process)
|
|
60
|
+
#
|
|
61
|
+
attr_reader :schedules
|
|
62
|
+
|
|
63
|
+
# TODO
|
|
64
|
+
#
|
|
65
|
+
attr_reader :trackers
|
|
66
|
+
|
|
67
|
+
# Called by Ruote::Dashboard#processes or Ruote::Dashboard#process.
|
|
68
|
+
#
|
|
69
|
+
def initialize(context, expressions, sworkitems, errors, schedules, trackers)
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# preparing data
|
|
73
|
+
|
|
74
|
+
@expressions = expressions.collect { |e|
|
|
75
|
+
Ruote::Exp::FlowExpression.from_h(context, e)
|
|
76
|
+
}.sort_by { |e|
|
|
77
|
+
e.fei.expid
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@stored_workitems = sworkitems.map { |h| Ruote::Workitem.new(h) }
|
|
81
|
+
|
|
82
|
+
@errors = errors.sort! { |a, b| a.fei.expid <=> b.fei.expid }
|
|
83
|
+
@schedules = schedules.sort! { |a, b| a['owner'].sid <=> b['owner'].sid }
|
|
84
|
+
|
|
85
|
+
@root_expression = root_expressions.first
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# linking errors and expressions for easy navigation
|
|
89
|
+
|
|
90
|
+
@errors.each do |err|
|
|
91
|
+
err.flow_expression = @expressions.find { |fexp| fexp.fei == err.fei }
|
|
92
|
+
err.flow_expression.error = err if err.flow_expression
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@trackers = trackers
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns a list of all the expressions that have no parent expression.
|
|
99
|
+
# The list is sorted with the deeper (closer to the original root) first.
|
|
100
|
+
#
|
|
101
|
+
def root_expressions
|
|
102
|
+
|
|
103
|
+
roots = @expressions.select { |e| e.h.parent_id == nil }
|
|
104
|
+
|
|
105
|
+
roots = roots.each_with_object({}) { |e, h|
|
|
106
|
+
h["#{e.h.fei['expid']}__#{e.h.fei['subid']}"] = e
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
roots.keys.sort.collect { |k| roots[k] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Given an expression id, returns the root (top ancestor) for its
|
|
113
|
+
# expression.
|
|
114
|
+
#
|
|
115
|
+
def root_expression_for(fei)
|
|
116
|
+
|
|
117
|
+
sfei = Ruote.sid(fei)
|
|
118
|
+
|
|
119
|
+
exp = @expressions.find { |fe| sfei == Ruote.sid(fe.fei) }
|
|
120
|
+
|
|
121
|
+
return nil unless exp
|
|
122
|
+
return exp if exp.parent_id.nil?
|
|
123
|
+
|
|
124
|
+
root_expression_for(exp.parent_id)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns the process variables set for this process instance.
|
|
128
|
+
#
|
|
129
|
+
# Returns nil if there is no defined root expression.
|
|
130
|
+
#
|
|
131
|
+
def variables
|
|
132
|
+
|
|
133
|
+
@root_expression && @root_expression.variables
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns a hash fei => variable_hash containing all the variable bindings
|
|
137
|
+
# (expression by expression) of the process instance.
|
|
138
|
+
#
|
|
139
|
+
def all_variables
|
|
140
|
+
|
|
141
|
+
return nil if @expressions.empty?
|
|
142
|
+
|
|
143
|
+
@expressions.each_with_object({}) do |exp, h|
|
|
144
|
+
h[exp.fei] = exp.variables if exp.variables
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Returns a hash tagname => fei of tags set at the root of the process
|
|
149
|
+
# instance.
|
|
150
|
+
#
|
|
151
|
+
# Returns nil if there is no defined root expression.
|
|
152
|
+
#
|
|
153
|
+
def tags
|
|
154
|
+
|
|
155
|
+
if variables
|
|
156
|
+
Hash[variables.select { |k, v| FlowExpressionId.is_a_fei?(v) }]
|
|
157
|
+
else
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Returns a hash tagname => array of feis of all the tags set in the process
|
|
163
|
+
# instance.
|
|
164
|
+
#
|
|
165
|
+
def all_tags
|
|
166
|
+
|
|
167
|
+
all_variables.remap do |(fei, vars), h|
|
|
168
|
+
vars.each { |k, v| (h[k] ||= []) << v if FlowExpressionId.is_a_fei?(v) }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns the list of "past tags", tags that have been entered and left.
|
|
173
|
+
#
|
|
174
|
+
# The list elements look like:
|
|
175
|
+
#
|
|
176
|
+
# [ full_tagname, fei_as_string, nil_or_left_status, variables ]
|
|
177
|
+
#
|
|
178
|
+
# For example:
|
|
179
|
+
#
|
|
180
|
+
# [ 'a', '0_1_0!8f233fb935c!20120106-jagitepi', nil, {} ]
|
|
181
|
+
#
|
|
182
|
+
# or
|
|
183
|
+
#
|
|
184
|
+
# [ 'stage0/stage1', '0_1_0!8fb935c666d!20120106-jagitepi', 'cancelling', nil ]
|
|
185
|
+
#
|
|
186
|
+
# The second to last entry is nil when the tag (its expression) replied
|
|
187
|
+
# normally, if it was cancelled or something else, the entry contains
|
|
188
|
+
# a string describing the reason ('cancelling' here).
|
|
189
|
+
# The last entry is the variables as they were at the tag point when
|
|
190
|
+
# the execution left the tag.
|
|
191
|
+
#
|
|
192
|
+
def past_tags
|
|
193
|
+
|
|
194
|
+
(@root_expression ?
|
|
195
|
+
@root_expression.variables['__past_tags__'] : nil
|
|
196
|
+
) || []
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns the unique identifier for this process instance.
|
|
200
|
+
#
|
|
201
|
+
def wfid
|
|
202
|
+
|
|
203
|
+
l = [ @expressions, @errors, @stored_workitems ].find { |l| l.any? }
|
|
204
|
+
|
|
205
|
+
l ? l.first.fei.wfid : nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# For a process
|
|
209
|
+
#
|
|
210
|
+
# Ruote.process_definition :name => 'review', :revision => '0.1' do
|
|
211
|
+
# author
|
|
212
|
+
# reviewer
|
|
213
|
+
# end
|
|
214
|
+
#
|
|
215
|
+
# will yield 'review'.
|
|
216
|
+
#
|
|
217
|
+
def definition_name
|
|
218
|
+
|
|
219
|
+
@root_expression && (
|
|
220
|
+
@root_expression.attribute('name') ||
|
|
221
|
+
@root_expression.attribute_text)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# For a process
|
|
225
|
+
#
|
|
226
|
+
# Ruote.process_definition :name => 'review', :revision => '0.1' do
|
|
227
|
+
# author
|
|
228
|
+
# reviewer
|
|
229
|
+
# end
|
|
230
|
+
#
|
|
231
|
+
# will yield '0.1'.
|
|
232
|
+
#
|
|
233
|
+
def definition_revision
|
|
234
|
+
|
|
235
|
+
@root_expression && (
|
|
236
|
+
@root_expression.attribute('revision') ||
|
|
237
|
+
@root_expression.attribute('rev'))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Returns the 'position' of the process.
|
|
241
|
+
#
|
|
242
|
+
# pdef = Ruote.process_definition do
|
|
243
|
+
# alpha :task => 'clean car'
|
|
244
|
+
# end
|
|
245
|
+
# wfid = engine.launch(pdef)
|
|
246
|
+
#
|
|
247
|
+
# sleep 0.500
|
|
248
|
+
#
|
|
249
|
+
# engine.process(wfid) # => [["0_0", "alpha", {"task"=>"clean car"}]]
|
|
250
|
+
#
|
|
251
|
+
# A process with concurrent branches will yield multiple 'positions'.
|
|
252
|
+
#
|
|
253
|
+
# It uses #workitems underneath.
|
|
254
|
+
#
|
|
255
|
+
# If you want to list all the expressions where the "flow currently is"
|
|
256
|
+
# regardless they are participant expressions or errors, look at the
|
|
257
|
+
# #leaves method.
|
|
258
|
+
#
|
|
259
|
+
def position
|
|
260
|
+
|
|
261
|
+
workitems.collect { |wi|
|
|
262
|
+
|
|
263
|
+
r = [ wi.fei.sid, wi.participant_name ]
|
|
264
|
+
|
|
265
|
+
params = (wi.fields['params'] || {}).dup
|
|
266
|
+
params.delete('ref')
|
|
267
|
+
|
|
268
|
+
if err = errors.find { |e| e.fei == wi.fei }
|
|
269
|
+
params['error'] = err.message
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
r << params
|
|
273
|
+
r
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Returns the expressions where the flow is currently, ak the leaves
|
|
278
|
+
# of the execution tree.
|
|
279
|
+
#
|
|
280
|
+
# Whereas #position only looks at participant expressions (and errors),
|
|
281
|
+
# #leaves looks at any expressions that is a leave (which has no
|
|
282
|
+
# child at this point).
|
|
283
|
+
#
|
|
284
|
+
# Returns an array of FlowExpression instances. (Note that they may
|
|
285
|
+
# have their attribute #error set).
|
|
286
|
+
#
|
|
287
|
+
def leaves
|
|
288
|
+
|
|
289
|
+
expressions.inject([]) { |a, exp|
|
|
290
|
+
a.select { |e| ! exp.ancestor?(e.fei) } + [ exp ]
|
|
291
|
+
}
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Returns the workitem as was applied at the root expression.
|
|
295
|
+
#
|
|
296
|
+
# Returns nil if no root expression could be found.
|
|
297
|
+
#
|
|
298
|
+
def root_workitem
|
|
299
|
+
|
|
300
|
+
return nil unless root_expression
|
|
301
|
+
|
|
302
|
+
Ruote::Workitem.new(root_expression.h.applied_workitem)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Returns a list of the workitems currently 'out' to participants
|
|
306
|
+
#
|
|
307
|
+
# For example, with an instance of
|
|
308
|
+
#
|
|
309
|
+
# Ruote.process_definition do
|
|
310
|
+
# concurrence do
|
|
311
|
+
# alpha :task => 'clean car'
|
|
312
|
+
# bravo :task => 'sell car'
|
|
313
|
+
# end
|
|
314
|
+
# end
|
|
315
|
+
#
|
|
316
|
+
# calling engine.process(wfid).workitems will yield two workitems
|
|
317
|
+
# (alpha and bravo).
|
|
318
|
+
#
|
|
319
|
+
# Warning : do not confuse the workitems here with the workitems held
|
|
320
|
+
# in a storage participant or equivalent.
|
|
321
|
+
#
|
|
322
|
+
def workitems
|
|
323
|
+
|
|
324
|
+
@expressions.select { |fexp|
|
|
325
|
+
#fexp.is_a?(Ruote::Exp::ParticipantExpression)
|
|
326
|
+
fexp.h.name == 'participant'
|
|
327
|
+
}.collect { |fexp|
|
|
328
|
+
Ruote::Workitem.new(fexp.h.applied_workitem)
|
|
329
|
+
}
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Returns a parseable UTC datetime string which indicates when the process
|
|
333
|
+
# was last active.
|
|
334
|
+
#
|
|
335
|
+
def last_active
|
|
336
|
+
|
|
337
|
+
@expressions.collect { |fexp| fexp.h.put_at }.max
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Returns the process definition tree as it was when this process instance
|
|
341
|
+
# was launched.
|
|
342
|
+
#
|
|
343
|
+
def original_tree
|
|
344
|
+
|
|
345
|
+
@root_expression && @root_expression.original_tree
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Returns a Time instance indicating when the process instance was launched.
|
|
349
|
+
#
|
|
350
|
+
def launched_time
|
|
351
|
+
|
|
352
|
+
@root_expression && @root_expression.created_time
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def to_s
|
|
356
|
+
|
|
357
|
+
'(' + [
|
|
358
|
+
"process_status wfid '#{wfid}'",
|
|
359
|
+
"expressions #{@expressions.size}",
|
|
360
|
+
"stored_workitems #{@stored_workitems.size}",
|
|
361
|
+
"errors #{@errors.size}",
|
|
362
|
+
"schedules #{@schedules.size}",
|
|
363
|
+
"trackers #{@trackers.size}"
|
|
364
|
+
].join(', ') + ')'
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def hinspect(indent, h)
|
|
368
|
+
|
|
369
|
+
if h
|
|
370
|
+
h.collect { |k, v|
|
|
371
|
+
s << "#{' ' * indent}#{k.inspect}: #{v.inspect}"
|
|
372
|
+
}.join("\n")
|
|
373
|
+
else
|
|
374
|
+
"#{' ' * indent}(nil)"
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def inspect
|
|
379
|
+
|
|
380
|
+
vars = variables rescue nil
|
|
381
|
+
avars = (all_variables || {}).remap { |(k, v), h| h[Ruote.sid(k)] = v }
|
|
382
|
+
|
|
383
|
+
s = [ "== #{self.class} ==" ]
|
|
384
|
+
s << ''
|
|
385
|
+
s << " wfid: #{wfid}"
|
|
386
|
+
s << " name: #{definition_name}"
|
|
387
|
+
s << " revision: #{definition_revision}"
|
|
388
|
+
s << " last_active: #{last_active}"
|
|
389
|
+
s << " launched_time: #{launched_time}"
|
|
390
|
+
s << ''
|
|
391
|
+
s << " expressions: #{@expressions.size}"
|
|
392
|
+
s << ''
|
|
393
|
+
@expressions.each do |e|
|
|
394
|
+
s << " #{e.fei.to_storage_id}"
|
|
395
|
+
s << " | #{e.name}"
|
|
396
|
+
s << " | _rev: #{e.h._rev.inspect}"
|
|
397
|
+
s << " | * #{e.state} *" if e.state
|
|
398
|
+
s << " | #{e.attributes.inspect}"
|
|
399
|
+
e.children.each do |ce|
|
|
400
|
+
s << " | . child-> #{Ruote.sid(ce)}"
|
|
401
|
+
end if e.children.any?
|
|
402
|
+
s << " | timers: #{e.h.timers.collect { |t| t[1] }}" if e.h.timers
|
|
403
|
+
s << " | (flanking)" if e.h.flanking
|
|
404
|
+
s << " `-parent--> #{e.h.parent_id ? e.parent_id.to_storage_id : 'nil'}"
|
|
405
|
+
end
|
|
406
|
+
s << ''
|
|
407
|
+
s << " schedules: #{@schedules.size}"
|
|
408
|
+
if @schedules.size > 0
|
|
409
|
+
@schedules.each do |sched|
|
|
410
|
+
s << " * #{sched['original']}"
|
|
411
|
+
s << " #{sched['flavour']} #{sched['at']}"
|
|
412
|
+
s << " #{sched['action']}"
|
|
413
|
+
s << " #{Ruote.sid(sched['target']) rescue '** no target **'}"
|
|
414
|
+
end
|
|
415
|
+
s << ''
|
|
416
|
+
end
|
|
417
|
+
s << " stored workitems: #{@stored_workitems.size}"
|
|
418
|
+
|
|
419
|
+
s << ''
|
|
420
|
+
s << " initial workitem fields:"
|
|
421
|
+
if @root_expression
|
|
422
|
+
s << hinspect(4, @root_expression.h.applied_workitem['fields'])
|
|
423
|
+
else
|
|
424
|
+
s << " (no root expression identified)"
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
s << ''
|
|
428
|
+
s << " variables:"; s << hinspect(4, vars)
|
|
429
|
+
s << ''
|
|
430
|
+
s << " all_variables:"; s << hinspect(4, avars)
|
|
431
|
+
s << ''
|
|
432
|
+
s << " errors: #{@errors.size}"
|
|
433
|
+
@errors.each do |e|
|
|
434
|
+
s << " ***"
|
|
435
|
+
s << " #{e.fei.to_storage_id} :" if e.fei
|
|
436
|
+
s << " action: #{e.action}"
|
|
437
|
+
s << " message: #{e.message}"
|
|
438
|
+
s << " trace:"
|
|
439
|
+
e.trace.split("\n").each do |line|
|
|
440
|
+
s << " #{line}"
|
|
441
|
+
end
|
|
442
|
+
s << " details:"
|
|
443
|
+
(e.details || '').split("\n").each do |line|
|
|
444
|
+
s << " #{line}"
|
|
445
|
+
end
|
|
446
|
+
if e.respond_to?(:deviations)
|
|
447
|
+
s << " deviations:"
|
|
448
|
+
(e.deviations || []).each do |line|
|
|
449
|
+
s << " #{line.inspect}"
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
s << " fields:"; s << hinspect(6, e.fields)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# TODO: add trackers
|
|
456
|
+
|
|
457
|
+
s.join("\n") + "\n"
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Returns a 'dot' representation of the process. A graph describing
|
|
461
|
+
# the tree of flow expressions that compose the process.
|
|
462
|
+
#
|
|
463
|
+
def to_dot(opts={})
|
|
464
|
+
|
|
465
|
+
s = [ "digraph \"process wfid #{wfid}\" {" ]
|
|
466
|
+
@expressions.each { |e| s.push(*e.send(:to_dot, opts)) }
|
|
467
|
+
@errors.each { |e| s.push(*e.send(:to_dot, opts)) }
|
|
468
|
+
s << '}'
|
|
469
|
+
|
|
470
|
+
s.join("\n")
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Outputs the process status as a hash (easily JSONifiable).
|
|
474
|
+
#
|
|
475
|
+
def to_h
|
|
476
|
+
|
|
477
|
+
%w[
|
|
478
|
+
expressions errors stored_workitems schedules trackers
|
|
479
|
+
].each_with_object({}) do |a, h|
|
|
480
|
+
|
|
481
|
+
k = a == 'stored_workitems' ? 'workitems' : a
|
|
482
|
+
|
|
483
|
+
v = self.send(a)
|
|
484
|
+
v = v.collect { |e| e.respond_to?(:h) ? e.h : e }
|
|
485
|
+
|
|
486
|
+
h[k] = v
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Returns the current version of the process definition tree. If no
|
|
491
|
+
# manipulation (gardening) was performed on the tree, this method yields
|
|
492
|
+
# the same result as the #original_tree method.
|
|
493
|
+
#
|
|
494
|
+
# Returns nil if there are no expressions (happens in the case of an
|
|
495
|
+
# orphan workitem)
|
|
496
|
+
#
|
|
497
|
+
def current_tree
|
|
498
|
+
|
|
499
|
+
return nil if @expressions.empty?
|
|
500
|
+
|
|
501
|
+
h = Ruote.decompose_tree(original_tree)
|
|
502
|
+
|
|
503
|
+
@expressions.sort { |e0, e1|
|
|
504
|
+
|
|
505
|
+
e0.fei.expid <=> e1.fei.expid
|
|
506
|
+
|
|
507
|
+
}.each { |e|
|
|
508
|
+
|
|
509
|
+
trigger = e.tree[1]['_triggered']
|
|
510
|
+
|
|
511
|
+
tree = if trigger && trigger != 'on_re_apply'
|
|
512
|
+
t = original_tree_from_parent(e).dup
|
|
513
|
+
t[1]['_triggered'] = trigger
|
|
514
|
+
t
|
|
515
|
+
else
|
|
516
|
+
e.tree
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
h.merge!(Ruote.decompose_tree(tree, e.fei.expid))
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
Ruote.recompose_tree(h)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Used by Ruote::Dashboard#process and #processes
|
|
526
|
+
#
|
|
527
|
+
def self.fetch(context, wfids, opts)
|
|
528
|
+
|
|
529
|
+
swfids = wfids.collect { |wfid| /!#{wfid}-\d+$/ }
|
|
530
|
+
|
|
531
|
+
batch = { 'id' => "#{Thread.current.object_id}-#{Time.now.to_f}" }
|
|
532
|
+
#
|
|
533
|
+
# some storages may optimize when they can distinguish
|
|
534
|
+
# which get_many fit in the same batch...
|
|
535
|
+
|
|
536
|
+
exps = context.storage.get_many(
|
|
537
|
+
'expressions', wfids, :batch => batch).compact
|
|
538
|
+
swis = context.storage.get_many(
|
|
539
|
+
'workitems', wfids, :batch => batch).compact
|
|
540
|
+
errs = context.storage.get_many(
|
|
541
|
+
'errors', wfids, :batch => batch).compact
|
|
542
|
+
schs = context.storage.get_many(
|
|
543
|
+
'schedules', swfids, :batch => batch).compact
|
|
544
|
+
#
|
|
545
|
+
# some slow storages need the compaction... couch...
|
|
546
|
+
|
|
547
|
+
errs = errs.collect { |err| ProcessError.new(err) }
|
|
548
|
+
schs = schs.collect { |sch| Ruote.schedule_to_h(sch) }
|
|
549
|
+
|
|
550
|
+
by_wfid = {}
|
|
551
|
+
as = lambda { [ [], [], [], [], [] ] }
|
|
552
|
+
|
|
553
|
+
exps.each { |exp| (by_wfid[exp['fei']['wfid']] ||= as.call)[0] << exp }
|
|
554
|
+
swis.each { |swi| (by_wfid[swi['fei']['wfid']] ||= as.call)[1] << swi }
|
|
555
|
+
errs.each { |err| (by_wfid[err.wfid] ||= as.call)[2] << err }
|
|
556
|
+
schs.each { |sch| (by_wfid[sch['wfid']] ||= as.call)[3] << sch }
|
|
557
|
+
# TODO: trackers
|
|
558
|
+
|
|
559
|
+
wfids = by_wfid.keys.sort
|
|
560
|
+
wfids = wfids.reverse if opts[:descending]
|
|
561
|
+
# re-adjust list of wfids, only take what was found
|
|
562
|
+
|
|
563
|
+
wfids.collect { |wfid|
|
|
564
|
+
info = by_wfid[wfid]
|
|
565
|
+
info ? self.new(context, *info) : nil
|
|
566
|
+
}.compact
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
#--
|
|
570
|
+
#def self.from_h(h)
|
|
571
|
+
# self.new(
|
|
572
|
+
# nil,
|
|
573
|
+
# *%w[ expressions workitems errors schedules trackers ].map { |k| h[k] })
|
|
574
|
+
#end
|
|
575
|
+
#++
|
|
576
|
+
|
|
577
|
+
protected
|
|
578
|
+
|
|
579
|
+
def original_tree_from_parent(e)
|
|
580
|
+
|
|
581
|
+
parent = @expressions.find { |exp| exp.fei == e.parent_id }
|
|
582
|
+
|
|
583
|
+
parent ? parent.tree[2][e.fei.child_id] : e.tree
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|