Index: openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql,v diff -u -r1.9 -r1.10 --- openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql 11 Dec 2003 21:40:15 -0000 1.9 +++ openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql 9 Jan 2004 15:47:53 -0000 1.10 @@ -153,10 +153,12 @@ where action_id = p_action_id; -- use case object as context_id - select object_id + select top.object_id into v_case_object_id - from workflow_cases - where case_id = p_case_id; + from workflow_cases c, + workflow_cases top + where top.case_id = c.top_case_id + and c.case_id = p_case_id; -- build the unique name if p_item_id is not null then Index: openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql,v diff -u -r1.22 -r1.23 --- openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 8 Jan 2004 16:00:40 -0000 1.22 +++ openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 9 Jan 2004 15:47:53 -0000 1.23 @@ -412,27 +412,28 @@ create sequence workflow_cases_seq; create table workflow_cases ( - case_id integer - constraint workflow_cases_pk - primary key, - workflow_id integer - constraint wf_cases_workflow_id_nn - not null - constraint wf_cases_workflow_id_fk - references workflows(workflow_id) - on delete cascade, - object_id integer - constraint wf_cases_object_id_nn - not null - constraint wf_cases_object_id_fk - references acs_objects(object_id) - on delete cascade - constraint wf_cases_object_id_un - unique + case_id integer + constraint workflow_cases_pk + primary key, + workflow_id integer + constraint wf_cases_workflow_id_nn + not null + constraint wf_cases_workflow_id_fk + references workflows(workflow_id) + on delete cascade, + object_id integer + constraint wf_cases_object_id_fk + references acs_objects(object_id) + on delete cascade, -- the object which this case is about, e.g. the acs-object for a bug-tracker bug + top_case_id integer + constraint wf_cases_top_case_id_fk + references workflow_cases(case_id) + on delete cascade ); create index workflow_cases_workflow_id on workflow_cases (workflow_id); +create index workflow_cases_top_case_id on workflow_cases (top_case_id); create table workflow_case_role_party_map ( case_id integer @@ -489,7 +490,22 @@ create index wf_case_enbl_act_action_idx on workflow_case_enabled_actions(action_id); create index wf_case_enbl_act_state_idx on workflow_case_enabled_actions(enabled_state); +create table workflow_case_parent_action( + case_id integer + constraint wf_case_child_cases_case_fk + references workflow_cases + constraint wf_case_child_cases_case_pk + primary key, + parent_enabled_action_id + integer + constraint wf_case_child_cases_en_act_fk + references workflow_case_enabled_actions + constraint wf_case_child_cases_en_act_nn + not null +); +create index wf_cs_child_cs_en_act_idx on workflow_case_parent_action(parent_enabled_action_id); + --------------------------------- -- Deputies --------------------------------- Index: openacs-4/packages/workflow/sql/postgresql/upgrade/upgrade-1.2-2.0d1.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/upgrade/upgrade-1.2-2.0d1.sql,v diff -u -r1.5 -r1.6 --- openacs-4/packages/workflow/sql/postgresql/upgrade/upgrade-1.2-2.0d1.sql 8 Jan 2004 16:00:41 -0000 1.5 +++ openacs-4/packages/workflow/sql/postgresql/upgrade/upgrade-1.2-2.0d1.sql 9 Jan 2004 15:47:53 -0000 1.6 @@ -96,3 +96,116 @@ If per role, we create just one child workflow, with the exact same parties that are in the parent_role. If more than one child_role has a mapping_type other than per_role, the cartesian product of these roles will be created. '; + +create table workflow_case_parent_action( + case_id integer + constraint wf_case_child_cases_case_fk + references workflow_cases + constraint wf_case_child_cases_case_pk + primary key, + parent_enabled_action_id + integer + constraint wf_case_child_cases_en_act_fk + references workflow_case_enabled_actions + constraint wf_case_child_cases_en_act_nn + not null +); + +create index wf_cs_child_cs_en_act_idx on workflow_case_parent_action(parent_enabled_action_id); +alter table workflow_cases add + top_case_id integer + constraint wf_cases_top_case_id_fk + references workflow_cases(case_id) + on delete cascade; +update workflow_cases set top_case_id = case_id; + + +-- object_id can now be null, and doesn't have to be unique +-- (since we're going to have plenty of rows with null object_id) +alter table workflow_cases drop constraint wf_cases_object_id_un; +alter table workflow_cases alter object_id drop not null; + +-- Find case's object_id from the top_case_id +create or replace function workflow_case_log_entry__new ( + integer, -- entry_id + varchar, -- content_type + integer, -- case_id + integer, -- action_id + varchar, -- comment + varchar, -- comment_mime_type + integer, -- creation_user + varchar -- creation_ip +) returns integer as ' +declare + p_item_id alias for $1; + p_content_type alias for $2; + p_case_id alias for $3; + p_action_id alias for $4; + p_comment alias for $5; + p_comment_mime_type alias for $6; + p_creation_user alias for $7; + p_creation_ip alias for $8; + + v_name varchar; + v_action_short_name varchar; + v_action_pretty_past_tense varchar; + v_case_object_id integer; + v_item_id integer; + v_revision_id integer; +begin + select short_name, pretty_past_tense + into v_action_short_name, v_action_pretty_past_tense + from workflow_actions + where action_id = p_action_id; + + -- use case object as context_id + select top.object_id + into v_case_object_id + from workflow_cases c, + workflow_cases top + where top.case_id = c.top_case_id + and c.case_id = p_case_id; + + -- build the unique name + if p_item_id is not null then + v_item_id := p_item_id; + else + select nextval + into v_item_id + from acs_object_id_seq; + end if; + v_name := v_action_short_name || '' '' || v_item_id; + + v_item_id := content_item__new ( + v_item_id, -- item_id + v_name, -- name + v_case_object_id, -- parent_id + v_action_pretty_past_tense, -- title + now(), -- creation_date + p_creation_user, -- creation_user + v_case_object_id, -- context_id + p_creation_ip, -- creation_ip + ''t'', -- is_live + p_comment_mime_type, -- mime_type + p_comment, -- text + ''text'', -- storage_type + ''t'', -- security_inherit_p + ''CR_FILES'', -- storage_area_key + ''content_item'', -- item_subtype + p_content_type -- content_type + ); + + -- insert the row into the single-column entry revision table + select content_item__get_live_revision (v_item_id) + into v_revision_id; + + insert into workflow_case_log_rev (entry_rev_id) + values (v_revision_id); + + -- insert into workflow-case-log + insert into workflow_case_log (entry_id, case_id, action_id) + values (v_item_id, p_case_id, p_action_id); + + -- return id of newly created item + return v_item_id; +end;' language 'plpgsql'; Index: openacs-4/packages/workflow/tcl/action-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/action-procs-oracle.xql,v diff -u -r1.6 -r1.7 --- openacs-4/packages/workflow/tcl/action-procs-oracle.xql 9 Jan 2004 10:27:24 -0000 1.6 +++ openacs-4/packages/workflow/tcl/action-procs-oracle.xql 9 Jan 2004 15:47:53 -0000 1.7 @@ -46,7 +46,8 @@ a.description, a.description_mime_type, a.child_workflow_id, - (select short_name from workflows where workflow_id = a.child_workflow_id) as child_workflow + (select short_name from workflows where workflow_id = a.child_workflow_id) as child_workflow, + a.timeout_seconds from workflow_actions a, workflow_fsm_actions fa where a.workflow_id = :workflow_id Index: openacs-4/packages/workflow/tcl/action-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/action-procs-postgresql.xql,v diff -u -r1.8 -r1.9 --- openacs-4/packages/workflow/tcl/action-procs-postgresql.xql 9 Jan 2004 10:27:24 -0000 1.8 +++ openacs-4/packages/workflow/tcl/action-procs-postgresql.xql 9 Jan 2004 15:47:53 -0000 1.9 @@ -36,7 +36,8 @@ a.description, a.description_mime_type, a.child_workflow_id, - (select short_name from workflows where workflow_id = a.child_workflow_id) as child_workflow + (select short_name from workflows where workflow_id = a.child_workflow_id) as child_workflow, + extract(seconds from a.timeout) as timeout_seconds from workflow_actions a left outer join workflow_fsm_actions fa on (a.action_id = fa.action_id) where a.workflow_id = :workflow_id Index: openacs-4/packages/workflow/tcl/action-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/action-procs.tcl,v diff -u -r1.24 -r1.25 --- openacs-4/packages/workflow/tcl/action-procs.tcl 9 Jan 2004 10:27:24 -0000 1.24 +++ openacs-4/packages/workflow/tcl/action-procs.tcl 9 Jan 2004 15:47:53 -0000 1.25 @@ -1353,11 +1353,8 @@ @author Peter Marklund } { - return [workflow::action::get_all_info_not_cached \ - -workflow_id $workflow_id] - return [util_memoize [list workflow::action::get_all_info_not_cached \ - -workflow_id $workflow_id] [workflow::cache_timeout]] + -workflow_id $workflow_id] [workflow::cache_timeout]] } ad_proc -private workflow::action::get_all_info_not_cached { @@ -1378,7 +1375,6 @@ array set action_data {} set action_ids [list] db_foreach action_info {} -column_array action_row { - # Cache the mapping action_id -> workflow_id util_memoize_seed \ [list workflow::action::get_workflow_id_not_cached -action_id $action_row(action_id)] \ Index: openacs-4/packages/workflow/tcl/case-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/case-procs-oracle.xql,v diff -u -r1.6 -r1.7 --- openacs-4/packages/workflow/tcl/case-procs-oracle.xql 11 Dec 2003 21:40:16 -0000 1.6 +++ openacs-4/packages/workflow/tcl/case-procs-oracle.xql 9 Jan 2004 15:47:53 -0000 1.7 @@ -5,17 +5,20 @@ select c.case_id, + c.top_case_id, c.workflow_id, - c.object_id, + top.object_id, s.state_id, s.short_name as state_short_name, s.pretty_name as pretty_state, s.hide_fields as state_hide_fields from workflow_cases c, + workflow_cases top, workflow_case_fsm cfsm, workflow_fsm_states s where c.case_id = :case_id and cfsm.case_id = c.case_id + and top.case_id = c.top_case_id and s.state_id (+) = cfsm.current_state @@ -99,8 +102,7 @@ - select a.action_id, - a.timeout_seconds + select a.action_id from workflow_cases c, workflow_actions a where c.case_id = :case_id Index: openacs-4/packages/workflow/tcl/case-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/case-procs-postgresql.xql,v diff -u -r1.5 -r1.6 --- openacs-4/packages/workflow/tcl/case-procs-postgresql.xql 18 Nov 2003 17:57:57 -0000 1.5 +++ openacs-4/packages/workflow/tcl/case-procs-postgresql.xql 9 Jan 2004 15:47:53 -0000 1.6 @@ -5,17 +5,25 @@ select c.case_id, + c.top_case_id, c.workflow_id, - c.object_id, + top.object_id, s.state_id, s.short_name as state_short_name, s.pretty_name as pretty_state, - s.hide_fields as state_hide_fields + s.hide_fields as state_hide_fields, + parent.parent_enabled_action_id, + (select case_id + from workflow_case_enabled_actions + where enabled_action_id = parent.parent_enabled_action_id) as parent_case_id from workflow_cases c, + workflow_cases top, workflow_case_fsm cfsm left outer join - workflow_fsm_states s on (s.state_id = cfsm.current_state) + workflow_fsm_states s on (s.state_id = cfsm.current_state) left outer join + workflow_case_parent_action parent using (case_id) where c.case_id = :case_id and cfsm.case_id = c.case_id + and top.case_id = c.top_case_id @@ -63,8 +71,7 @@ - select a.action_id, - extract(seconds from a.timeout) as timeout_seconds + select a.action_id from workflow_cases c, workflow_actions a where c.case_id = :case_id Index: openacs-4/packages/workflow/tcl/case-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/case-procs.tcl,v diff -u -r1.20 -r1.21 --- openacs-4/packages/workflow/tcl/case-procs.tcl 7 Jan 2004 13:41:27 -0000 1.20 +++ openacs-4/packages/workflow/tcl/case-procs.tcl 9 Jan 2004 15:47:53 -0000 1.21 @@ -22,24 +22,46 @@ ad_proc -private workflow::case::insert { {-workflow_id:required} {-object_id:required} + {-parent_enabled_action_id {}} + {-top_case_id {}} } { Internal procedure that creates a new workflow case in the database. Should not be used by applications. Use workflow::case::new instead. - @param object_id The object_id which the case is about - @param workflow_short_name The short_name of the workflow. - @return The case_id of the case. Returns the empty string if no case could be found. + @param object_id The object_id which the case is about + @param workflow_id The ID of the workflow. + + @param parent_enabled_action_id + The ID of an enabled_action in the parent, if this is a child case + + @param top_case_id If this is a child case, this must be the ID of the case at the top + of the tree. + + @return The case_id of the case. Returns the empty string if no case could be found. + @see workflow::case::new @author Lars Pind (lars@collaboraid.biz) } { - set case_id [db_nextval "workflow_cases_seq"] - db_transaction { + set case_id [db_nextval "workflow_cases_seq"] + + if { [empty_string_p $top_case_id] } { + if { ![empty_string_p $parent_enabled_action_id] } { + error "You cannot create a child case without specifying the top_case_id." + } + set top_case_id $case_id + } + # Create the case db_dml insert_case {} + # Create the mapping + if { [exists_and_not_null parent_enabled_action_id] } { + db_dml insert_case_parent_map {} + } + # Initialize the FSM state to NULL db_dml insert_case_fsm {} } @@ -48,19 +70,35 @@ } ad_proc -public workflow::case::new { - {-workflow_id:required} - {-object_id:required} + {-no_notification:boolean} + -workflow_id:required + {-object_id {}} + {-parent_enabled_action_id {}} + {-top_case_id {}} {-comment {}} {-comment_mime_type {}} - {-user_id} + -user_id + -assignment } { Start a new case for this workflow and object. - @param object_id The object_id which the case is about - @param workflow_short_name The short_name of the workflow. - @param comment_mime_type html, plain or pre - @return The case_id of the case. Returns the empty string if no case could be found. + @param object_id The object_id which the case is about + @param workflow_id The ID of the workflow for the case. + + @param comment_mime_type text/html, text/plain, text/pre, text/enhanced. + + @param assignment Array-list of role_short_name and list of party_ids to assign + to roles before starting. + + @param parent_enabled_action_id + The ID of an enabled_action in the parent, if this is a child case + + @param top_case_id If this is a child case, this must be the ID of the case at the top + of the tree. + + @return The case_id of the case. + @author Lars Pind (lars@collaboraid.biz) } { if { ![exists_and_not_null user_id] } { @@ -70,8 +108,19 @@ db_transaction { # Insert the case - set case_id [insert -workflow_id $workflow_id -object_id $object_id] + set case_id [insert \ + -workflow_id $workflow_id \ + -object_id $object_id \ + -parent_enabled_action_id $parent_enabled_action_id \ + -top_case_id $top_case_id] + # Assign roles + if { [exists_and_not_null assignment] } { + array set assignment_array $assignment + workflow::case::role::assign -case_id $case_id -array assignment_array + } + + # Initial action set action_id [workflow::get_element -workflow_id $workflow_id -element initial_action_id] if { [empty_string_p $action_id] } { @@ -87,12 +136,13 @@ # Execute the initial action workflow::case::action::execute \ - -case_id $case_id \ - -action_id $action_id \ - -comment $comment \ - -comment_mime_type $comment_mime_type \ - -user_id $user_id \ - -initial + -no_notification=$no_notification_p \ + -case_id $case_id \ + -action_id $action_id \ + -comment $comment \ + -comment_mime_type $comment_mime_type \ + -user_id $user_id \ + -initial } return $case_id @@ -145,6 +195,25 @@ # the active tokens in a petri net. } +ad_proc -public workflow::case::active_p { + -case_id:required +} { + Returns true if the case is active, otherwise false. +} { + # Implementation note: The case is active if there are any enabled actions, otherwise not + db_transaction { + set enabled_actions [workflow::case::get_enabled_actions -case_id $case_id] + } + + aa_log "Case $case_id has enabled actions: $enabled_actions" + foreach action_id $enabled_actions { + aa_log "-- Action: [workflow::action::get_element -action_id $action_id -element short_name]" + } + + + return [expr [llength $enabled_actions] > 0] +} + ad_proc -public workflow::case::get_element { {-case_id:required} {-element:required} @@ -689,9 +758,13 @@ @author Lars Pind (lars@collaboraid.biz) } { + aa_log "State-changed-handler for case_id $case_id" + db_transaction { - # Columns: action_id, timeout_seconds - db_multirow -local enabled_actions select_enabled_actions {} + # DB query to get actually enabled actions + set enabled_action_ids [db_list select_enabled_actions {}] + + aa_log "Enabled actions: $enabled_action_ids" # This array, keyed by action_id, will store the enabled_action_id for previously enabled actions array set action_enabled [list] @@ -701,7 +774,7 @@ # Loop over currently enabled actions and find out which ones are new array set newly_enabled_action [list] - template::multirow -local foreach enabled_actions { + foreach action_id $enabled_action_ids { if { [info exists action_enabled($action_id)] } { # Action was already enabled. Unset the array entry, so what remains will be # previously but no longer enabled actions @@ -719,21 +792,105 @@ } # Second, enable the newly enabled actions - template::multirow -local foreach enabled_actions { + foreach action_id $enabled_action_ids { if { [info exists newly_enabled_action($action_id)] } { workflow::case::action::enable \ -case_id $case_id \ -action_id $action_id \ - -automatic=[expr { $timeout_seconds == 0 }] \ -user_id $user_id } } + # Flush cache, now that things have changed + workflow::case::flush_cache -case_id $case_id + # Make sure roles are assigned, if possible workflow::case::assign_roles -all -case_id $case_id + + # Call parent child-state-changed handler + workflow::case::get -case_id $case_id -array case + if { $case(top_case_id) != $case_id } { + workflow::case::child_state_changed_handler \ + -parent_case_id $case(parent_case_id) \ + -parent_enabled_action_id $case(parent_enabled_action_id) \ + -child_case_id $case_id \ + -user_id $user_id + } } } +ad_proc -private workflow::case::child_state_changed_handler { + {-parent_case_id:required} + {-parent_enabled_action_id:required} + {-child_case_id:required} + {-user_id {}} +} { + Check if all child cases of this enabled action are complete, and if so + cause this action to execute +} { + aa_log "child_state_changed_handler: parent_case_id = $parent_case_id, parent_enabled_action_id = $parent_enabled_action_id, child_case_id = $child_case_id" + + db_transaction { + + #---------------------------------------------------------------------- + # 1. Check if this child-case is inactive + #---------------------------------------------------------------------- + if { [workflow::case::active_p -case_id $child_case_id] } { + # Child still running + aa_log "child_state_changed_handler: Child $child_case_id still running" + return + } + + #---------------------------------------------------------------------- + # 2. Check if all child-cases of this enabled_action_id are done + #---------------------------------------------------------------------- + set child_case_ids [workflow::case::get_child_cases -enabled_action_id $parent_enabled_action_id] + foreach one_child $child_case_ids { + + # No reason to check this child again + if { $one_child != $child_case_id } { + if { [workflow::case::active_p -case_id $one_child] } { + # Other child still running + aa_log "child_state_changed_handler: Other child, $one_child, still running" + return + } + } + } + + #---------------------------------------------------------------------- + # 3. If all are complete, execute the action + #---------------------------------------------------------------------- + + aa_log "child_state_changed_handler: All children done, executing parent action" + + # TODO: API to get action_id from enabled_action_id + set action_id [db_string select_action_id { + select action_id + from workflow_case_enabled_actions + where enabled_action_id = :parent_enabled_action_id + }] + + workflow::case::action::execute \ + -no_notification \ + -no_perm_check \ + -case_id $parent_case_id \ + -action_id $action_id \ + -user_id $user_id + } +} + + +ad_proc -private workflow::case::get_child_cases { + -enabled_action_id:required +} { +} { + return [db_list child_cases { + select case_id + from workflow_case_parent_action + where parent_enabled_action_id = :enabled_action_id + }] +} + ad_proc -public workflow::case::timed_actions_sweeper {} { Sweep for timed actions ready to fire. } { @@ -1324,6 +1481,7 @@ } ad_proc -public workflow::case::action::execute { + {-no_notification:boolean} {-no_perm_check:boolean} {-case_id:required} {-action_id:required} @@ -1385,6 +1543,8 @@ db_transaction { + aa_log "Executing action $action_id: [workflow::action::get_element -action_id $action_id -element short_name]" + # Update the case workflow state workflow::case::action::fsm::execute_state_change \ -case_id $case_id \ @@ -1405,6 +1565,8 @@ oacs_util::vars_to_ns_set \ -ns_set $extra_vars \ -var_list { entry_id case_id action_id comment comment_mime_type } + + # ns_set put $extra_vars parent_id $object_id set entry_id [package_instantiate_object \ -creation_user $user_id \ @@ -1419,12 +1581,14 @@ -entry_id $entry_id # Notifications - notify \ - -case_id $case_id \ - -action_id $action_id \ - -entry_id $entry_id \ - -comment $comment \ - -comment_mime_type $comment_mime_type + if { !$no_notification_p } { + workflow::case::action::notify \ + -case_id $case_id \ + -action_id $action_id \ + -entry_id $entry_id \ + -comment $comment \ + -comment_mime_type $comment_mime_type + } # Scan for enabled actions workflow::case::state_changed_handler \ @@ -1460,7 +1624,6 @@ {-case_id:required} {-action_id:required} {-user_id {}} - {-automatic:boolean} } { Update the workflow_case_enabled_actions table to say that the action is now enabled. Will automatically fire an automatic action. @@ -1469,18 +1632,68 @@ @author Lars Pind (lars@collaboraid.biz) } { + workflow::action::get -action_id $action_id -array action + + #aa_log [util::array_list_spec_pretty [array get action]] + db_transaction { set enabled_action_id [db_nextval workflow_case_enbl_act_seq] db_dml insert_enabled {} - if { $automatic_p } { + # Spawn child workflows + if { ![empty_string_p $action(child_workflow_id)] } { + aa_log "Has child workflow" + + # NOTE: Role is mapped at spawn time. + # Changes made after that are not synchronized from parent to child or vice versa. + + # Assignment will be a list of { role_short_name { party_id party_id ... } ... } + set assignment [list] + foreach { child_role_short_name spec } $action(child_role_map) { + lappend assignment $child_role_short_name + + # Allow simple list of short_name -> short_name + if { [llength $spec] == 1 } { + set parent_role_short_name $spec + set mapping_type "per_role" + } else { + foreach { parent_role_short_name mapping_type } $spec {} + } + + if { [string equal $mapping_type "per_user"] } { + # TODO: Handle per_user role mapping + error "child_role_map mapping_type of per_user not implemented." + } else { + set parent_role_id [workflow::role::get_id \ + -workflow_id $action(workflow_id) \ + -short_name $parent_role_short_name] + set top_case_id [workflow::case::get_element \ + -case_id $case_id \ + -element top_case_id] + lappend assignment [workflow::case::role::get_assignees \ + -case_id $case_id \ + -role_id $parent_role_id] + } + } + + workflow::case::new \ + -no_notification \ + -workflow_id $action(child_workflow_id) \ + -parent_enabled_action_id $enabled_action_id \ + -user_id $user_id \ + -top_case_id $top_case_id \ + -assignment $assignment + } + + # Automatic actions execute immediately + if { $action(timeout_seconds) == 0 } { workflow::case::action::execute \ -no_perm_check \ -case_id $case_id \ -action_id $action_id \ -user_id $user_id - } + } } } @@ -1538,13 +1751,14 @@ workflow::case::get \ -case_id $case_id \ -array case - + workflow::get \ -workflow_id $case(workflow_id) \ -array workflow set hr [string repeat "=" 70] + # TODO: Get activity log for top-case array set latest_action [lindex [workflow::case::get_activity_log_info -case_id $case_id] end] set latest_action_chunk "$latest_action(action_pretty_past_tense) [ad_decode $latest_action(log_title) "" "" "$latest_action(log_title) "]by $latest_action(user_first_names) $latest_action(user_last_name) ($latest_action(user_email))" @@ -1554,6 +1768,7 @@ } # Callback to get notification info + # TODO: Should this be the parent/top-workflow that does this? set contract_name [workflow::service_contract::notification_info] set impl_names [workflow::get_callbacks \ -workflow_id $case(workflow_id) \ Index: openacs-4/packages/workflow/tcl/case-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/case-procs.xql,v diff -u -r1.9 -r1.10 --- openacs-4/packages/workflow/tcl/case-procs.xql 18 Nov 2003 17:57:57 -0000 1.9 +++ openacs-4/packages/workflow/tcl/case-procs.xql 9 Jan 2004 15:47:53 -0000 1.10 @@ -14,13 +14,23 @@ insert into workflow_cases ( - case_id, workflow_id, object_id + case_id, workflow_id, object_id, top_case_id ) values ( - :case_id, :workflow_id, :object_id + :case_id, :workflow_id, :object_id, :top_case_id ) + + + insert into workflow_case_parent_action ( + case_id, parent_enabled_action_id + ) values ( + :case_id, :parent_enabled_action_id + ) + + + insert into workflow_case_fsm ( Index: openacs-4/packages/workflow/tcl/workflow-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/workflow-procs.tcl,v diff -u -r1.18 -r1.19 --- openacs-4/packages/workflow/tcl/workflow-procs.tcl 17 Dec 2003 19:14:02 -0000 1.18 +++ openacs-4/packages/workflow/tcl/workflow-procs.tcl 9 Jan 2004 15:47:53 -0000 1.19 @@ -445,6 +445,8 @@ Should be called when the workflow definition has changed while there are active cases. Will update the record of enabled actions in each of the case, so they reflect the new workflow. } { + workflow::flush_cache -workflow_id $workflow_id + set case_ids [db_list select_cases { select case_id from workflow_cases where workflow_id = :workflow_id }] foreach case_id $case_ids { Index: openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl,v diff -u -r1.14 -r1.15 --- openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 17 Dec 2003 16:13:54 -0000 1.14 +++ openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 9 Jan 2004 15:47:53 -0000 1.15 @@ -77,8 +77,6 @@ {-expect_current_state:required} {-expect_enabled_actions:required} {-expect_user_actions:required} - {-expect_user_roles:required} - } { Make assertions about what the current state should be and what actions are enabled etc. @@ -525,8 +523,7 @@ -case_id $case_id \ -expect_current_state open \ -expect_enabled_actions $expect_enabled_actions \ - -expect_user_actions $expect_enabled_actions \ - -expect_user_roles {} + -expect_user_actions $expect_enabled_actions # Resolve the bug workflow::case::action::execute \ @@ -543,8 +540,7 @@ -case_id $case_id \ -expect_current_state resolved \ -expect_enabled_actions $expect_enabled_actions \ - -expect_user_actions $expect_enabled_actions \ - -expect_user_roles {} + -expect_user_actions $expect_enabled_actions ##### @@ -635,8 +631,7 @@ -case_id $case_id \ -expect_current_state closed \ -expect_enabled_actions $expect_enabled_actions \ - -expect_user_actions $expect_enabled_actions \ - -expect_user_roles {} + -expect_user_actions $expect_enabled_actions } @@ -815,7 +810,7 @@ # Old process: [open] -> (open) -> [auto] -> (closed) # New process: [open] -> (open) -> [auto] -> (closed) -> [reopen] -> (open) - + set reopen_action_id [workflow::action::fsm::new \ -workflow_id $workflow_id \ -short_name "reopen" \ @@ -868,3 +863,247 @@ workflow::delete -workflow_id $workflow_id } } + + +aa_register_case recursive_workflow { + Testing a recursive workflow +} { + aa_run_with_teardown -rollback -test_code { + + #---------------------------------------------------------------------- + # Create inner workflow + #---------------------------------------------------------------------- + # [open] -> (open) -> [ask] -> (asked) -> [give] -> (done) + + set inner_workflow_id [workflow::fsm::new_from_spec -package_key "acs-automated-testing" -spec { + recursive_workflow_inner { + pretty_name "Recursive Workflow Inner Workflow" + states { + open { + pretty_name Open + } + asked { + pretty_name Asked + } + done { + pretty_name Done + } + } + roles { + asker { + pretty_name "Asker" + } + giver { + pretty_name "Giver" + } + } + actions { + open { + pretty_name "Open" + pretty_past_tense "Opened" + new_state "open" + initial_action_p t + } + ask { + pretty_name "Ask" + pretty_past_tense "Asked" + new_state "asked" + enabled_states { open } + } + give { + pretty_name "Give" + pretty_past_tense "Given" + new_state "done" + enabled_states { asked } + } + } + } + }] + + aa_log "Inner workflow created" + + #---------------------------------------------------------------------- + # Create outer workflow + #---------------------------------------------------------------------- + # [open] -> (open) -> [do] -> (done) + + set outer_workflow_id [workflow::fsm::new_from_spec -package_key "acs-automated-testing" -spec { + recursive_workflow_outer { + pretty_name "Recursive Workflow Outer Workflow" + states { + open { + pretty_name Open + } + done { + pretty_name Done + } + } + roles { + lawyer { + pretty_name "Lawyer" + } + client { + pretty_name "Client" + } + } + actions { + open { + pretty_name "Open" + pretty_past_tense "Opened" + new_state "open" + initial_action_p t + } + do { + pretty_name "Do" + pretty_past_tense "Done" + new_state "done" + child_workflow recursive_workflow_inner + child_role_map { + asker lawyer + giver client + } + enabled_states { open } + } + } + } + }] + + aa_log "Outer workflow created" + + #---------------------------------------------------------------------- + # Test that it found the right inner workflow ID from the short_name + #---------------------------------------------------------------------- + + set action_with_child_id [workflow::action::get_id -workflow_id $outer_workflow_id -short_name do] + workflow::action::get -action_id $action_with_child_id -array action_with_child + + aa_equals "Child_workflow has the right ID" $action_with_child(child_workflow_id) $inner_workflow_id + + #---------------------------------------------------------------------- + # Start a case of the outer workflow + #---------------------------------------------------------------------- + + set outer_case_id [workflow::case::new \ + -workflow_id $outer_workflow_id \ + -object_id [workflow::test::workflow_object_id] \ + -user_id [workflow::test::admin_owner_id]] + + aa_log "Outer case started" + + #---------------------------------------------------------------------- + # Do should be enabled immediately + #---------------------------------------------------------------------- + + set enabled_actions [workflow::case::get_enabled_actions -case_id $outer_case_id] + + if { [aa_equals "One enabled action" [llength $enabled_actions] 1] } { + + set enabled_action_short_name [workflow::action::get_element \ + -action_id [lindex $enabled_actions 0] \ + -element short_name] + aa_equals "And that is 'do'" $enabled_action_short_name "do" + } else { + aa_log "Parent case: [db_list_of_lists foo { select * from workflow_cases where case_id = :outer_case_id }]" + aa_log "Enabled actions outer: [db_list_of_lists foo { select * from workflow_case_enabled_actions where case_id = :outer_case_id }]" + aa_log "Child cases: [db_list_of_lists foo { select * from workflow_cases where top_case_id = :outer_case_id and case_id != top_case_id }]" + } + + #---------------------------------------------------------------------- + # Which means inner case should be created + #---------------------------------------------------------------------- + + set inner_case_ids [db_list foo { select case_id from workflow_cases where top_case_id = :outer_case_id and case_id != top_case_id }] + aa_equals "One child case" [llength $inner_case_ids] 1 + + set inner_case_id [lindex $inner_case_ids 0] + + aa_log "Child cases: $inner_case_ids" + + #---------------------------------------------------------------------- + # Which means 'ask' should be available + #---------------------------------------------------------------------- + + set inner_enabled_actions [workflow::case::get_enabled_actions -case_id $inner_case_id] + + if { [aa_equals "One enabled action" [llength $inner_enabled_actions] 1] } { + + set inner_enabled_action_id [lindex $inner_enabled_actions 0] + + set inner_enabled_action_short_name [workflow::action::get_element \ + -action_id $inner_enabled_action_id \ + -element short_name] + aa_equals "And that is 'ask'" $inner_enabled_action_short_name "ask" + } + + workflow::test::assert_case_state \ + -workflow_id $inner_workflow_id \ + -case_id $inner_case_id \ + -expect_current_state "open" \ + -expect_enabled_actions "ask" \ + -expect_user_actions "ask" + + + #---------------------------------------------------------------------- + # Execute 'ask' + #---------------------------------------------------------------------- + + workflow::case::action::execute \ + -case_id $inner_case_id \ + -action_id [workflow::action::get_id \ + -workflow_id $inner_workflow_id \ + -short_name "ask"] \ + -comment "Asking" \ + -comment_mime_type "text/plain" \ + -user_id [workflow::test::admin_owner_id] + + #---------------------------------------------------------------------- + # Enabled action: 'give' + #---------------------------------------------------------------------- + + workflow::test::assert_case_state \ + -workflow_id $inner_workflow_id \ + -case_id $inner_case_id \ + -expect_current_state "asked" \ + -expect_enabled_actions "give" \ + -expect_user_actions "give" + + #---------------------------------------------------------------------- + # Execute 'give' + #---------------------------------------------------------------------- + + workflow::case::action::execute \ + -case_id $inner_case_id \ + -action_id [workflow::action::get_id \ + -workflow_id $inner_workflow_id \ + -short_name "give"] \ + -comment "Giving" \ + -comment_mime_type "text/plain" \ + -user_id [workflow::test::admin_owner_id] + + #---------------------------------------------------------------------- + # No enabled actions in inner workflow + #---------------------------------------------------------------------- + + workflow::test::assert_case_state \ + -workflow_id $inner_workflow_id \ + -case_id $inner_case_id \ + -expect_current_state "done" \ + -expect_enabled_actions {} \ + -expect_user_actions {} + + #---------------------------------------------------------------------- + # No enabled actions in outer workflow + #---------------------------------------------------------------------- + + workflow::test::assert_case_state \ + -workflow_id $outer_workflow_id \ + -case_id $outer_case_id \ + -expect_current_state "done" \ + -expect_enabled_actions {} \ + -expect_user_actions {} + + + + } + +} Index: openacs-4/packages/workflow/www/doc/fall-2003-extensions.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/fall-2003-extensions.html,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/fall-2003-extensions.html 9 Dec 2003 09:59:45 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/fall-2003-extensions.html 9 Jan 2004 15:47:53 -0000 1.3 @@ -126,14 +126,6 @@ ('per_role','per_member','per_user')) ); -create table workflow_cases( - ... - parent_enabled_action_id integer - constraint workflow_cases_parent_fk - references workflow_case_enabled_actions(enabled_action_id) - ... -); - create table workflow_case_enabled_actions( enabled_action_id integer constraint wf_case_enbl_act_case_id_pk @@ -160,6 +152,19 @@ constraint wf_case_ena_act_case_act_un primary key (case_id, action_id) ); + +create table workflow_case_child_cases( + case_id integer + constraint wf_case_child_cases_case_fk + references workflow_cases + constraint wf_case_child_cases_case_pk + primary key, + enabled_action_id integer + constraint wf_case_child_cases_en_act_fk + references workflow_case_enabled_actions + constraint wf_case_child_cases_en_act_nn + not null +);

Enabled States Explained