Index: openacs-4/packages/workflow/workflow.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/workflow.info,v diff -u -r1.11 -r1.12 --- openacs-4/packages/workflow/workflow.info 13 Nov 2003 14:34:50 -0000 1.11 +++ openacs-4/packages/workflow/workflow.info 18 Nov 2003 17:57:56 -0000 1.12 @@ -8,28 +8,29 @@ t workflow - - Peter Marklund + Lars Pind + Peter Marklund A Tcl API for creating workflows that support the Bug Tracker, CMS publication, simple approval, and much more. - 2003-11-10 + 2003-11-18 Collaboraid - This package lets you define the process that your tickets, articles, documents, reports, claims, change requests, or any other object of interest, must go through to ensure consistent quality and to avoid that any cases falls through the cracks. -<p> + This package lets you define the process that your tickets, articles, documents, reports, claims, change requests, or any other object of interest, must go through to ensure consistent quality and to avoid that any cases falls through the cracks. +<p> For more information, see: <a href="http://www.collaboraid.biz/developer/workflow-spec">the workflow specification</a>. - + - + + - + Index: openacs-4/packages/workflow/sql/oracle/workflow-tables-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/oracle/workflow-tables-create.sql,v diff -u -r1.7 -r1.8 --- openacs-4/packages/workflow/sql/oracle/workflow-tables-create.sql 13 Nov 2003 14:22:03 -0000 1.7 +++ openacs-4/packages/workflow/sql/oracle/workflow-tables-create.sql 18 Nov 2003 17:57:56 -0000 1.8 @@ -158,7 +158,11 @@ on delete set null, always_enabled_p char(1) default 'f' constraint wf_acns_enabled_p_ck - check (always_enabled_p in ('t','f')) + check (always_enabled_p in ('t','f')), + -- When the action to automatically fire. + -- A value of 0 means immediately, null means never. + -- Other values mean x amount of time after having become enabled + timeout_seconds integer ); create sequence workflow_actions_seq; @@ -383,6 +387,38 @@ primary key (case_id, role_id, party_id) ); +create sequence workflow_case_enbl_act_seq; + +create table workflow_case_enabled_actions( + enabled_action_id integer + constraint wf_case_enbl_act_case_id_pk + primary key, + case_id integer + constraint wf_case_enbl_act_case_id_nn + not null + constraint wf_case_enbl_act_case_id_fk + references workflow_cases(case_id) + on delete cascade, + action_id integer + constraint wf_case_enbl_act_action_id_nn + not null + constraint wf_case_enbl_act_action_id_fk + references workflow_actions(action_id) + on delete cascade, + enabled_date date + default sysdate, + executed_date date, + enabled_state char(40) + constraint wf_case_enbl_act_state_ck + check (enabled_state in ('enabled','completed','canceled','refused')), + -- the timestamp when this action will fire + execution_time date +); + +create index wf_case_enbl_act_case_idx on workflow_case_enabled_actions(case_id); +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); + --------------------------------- -- Deputies --------------------------------- @@ -492,6 +528,10 @@ on delete cascade ); +create index workflow_case_log_action_id on workflow_case_log (action_id); +create index workflow_case_log_case_id on workflow_case_log (case_id); + + create table workflow_case_log_data ( entry_id integer constraint wf_case_log_data_eid_nn Index: openacs-4/packages/workflow/sql/oracle/upgrade/upgrade-1.2-2.0d1.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/oracle/upgrade/upgrade-1.2-2.0d1.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/sql/oracle/upgrade/upgrade-1.2-2.0d1.sql 18 Nov 2003 17:57:56 -0000 1.1 @@ -0,0 +1,48 @@ +-- +-- Adds timed actions, plus some missing delete cascade indices +-- +-- @cvs-id $Id: upgrade-1.2-2.0d1.sql,v 1.1 2003/11/18 17:57:56 lars Exp $ +-- + + +alter table workflow_actions add (timeout_seconds integer); + + +create sequence workflow_case_enbl_act_seq; + +create table workflow_case_enabled_actions( + enabled_action_id integer + constraint wf_case_enbl_act_case_id_pk + primary key, + case_id integer + constraint wf_case_enbl_act_case_id_nn + not null + constraint wf_case_enbl_act_case_id_fk + references workflow_cases(case_id) + on delete cascade, + action_id integer + constraint wf_case_enbl_act_action_id_nn + not null + constraint wf_case_enbl_act_action_id_fk + references workflow_actions(action_id) + on delete cascade, + enabled_date date + default sysdate, + executed_date date, + enabled_state char(40) + constraint wf_case_enbl_act_state_ck + check (enabled_state in ('enabled','completed','canceled','refused')), + -- the timestamp when this action will fire + execution_time date +); + +create index wf_case_enbl_act_case_idx on workflow_case_enabled_actions(case_id); +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); + + +-- Missing delete cascade index in Oracle + +create index workflow_case_log_action_id on workflow_case_log (action_id); +create index workflow_case_log_case_id on workflow_case_log (case_id); + 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.15 -r1.16 --- openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 13 Nov 2003 14:22:03 -0000 1.15 +++ openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 18 Nov 2003 17:57:56 -0000 1.16 @@ -167,7 +167,11 @@ constraint wf_acns_assigned_role_fk references workflow_roles(role_id) on delete set null, - always_enabled_p bool default 'f' + always_enabled_p bool default 'f', + -- When the action to automatically fire. + -- A value of 0 means immediately, null means never. + -- Other values mean x amount of time after having become enabled + timeout interval ); create sequence workflow_actions_seq; @@ -408,6 +412,39 @@ primary key (case_id, role_id, party_id) ); +create sequence workflow_case_enbl_act_seq; + +create table workflow_case_enabled_actions( + enabled_action_id integer + constraint wf_case_enbl_act_case_id_pk + primary key, + case_id integer + constraint wf_case_enbl_act_case_id_nn + not null + constraint wf_case_enbl_act_case_id_fk + references workflow_cases(case_id) + on delete cascade, + action_id integer + constraint wf_case_enbl_act_action_id_nn + not null + constraint wf_case_enbl_act_action_id_fk + references workflow_actions(action_id) + on delete cascade, + enabled_date timestamptz + default current_timestamp, + executed_date timestamptz, + enabled_state char(40) + constraint wf_case_enbl_act_state_ck + check (enabled_state in ('enabled','completed','canceled','refused')), + -- the timestamp when this action will fire + execution_time timestamptz +); + +create index wf_case_enbl_act_case_idx on workflow_case_enabled_actions(case_id); +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); + + --------------------------------- -- 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 --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/sql/postgresql/upgrade/upgrade-1.2-2.0d1.sql 18 Nov 2003 17:57:56 -0000 1.1 @@ -0,0 +1,42 @@ +-- +-- Adds timed actions +-- +-- @cvs-id $Id: upgrade-1.2-2.0d1.sql,v 1.1 2003/11/18 17:57:56 lars Exp $ +-- + + +alter table workflow_actions add timeout interval; + + +create sequence workflow_case_enbl_act_seq; + +create table workflow_case_enabled_actions( + enabled_action_id integer + constraint wf_case_enbl_act_case_id_pk + primary key, + case_id integer + constraint wf_case_enbl_act_case_id_nn + not null + constraint wf_case_enbl_act_case_id_fk + references workflow_cases(case_id) + on delete cascade, + action_id integer + constraint wf_case_enbl_act_action_id_nn + not null + constraint wf_case_enbl_act_action_id_fk + references workflow_actions(action_id) + on delete cascade, + enabled_date timestamptz + default current_timestamp, + executed_date timestamptz, + enabled_state char(40) + constraint wf_case_enbl_act_state_ck + check (enabled_state in ('enabled','completed','canceled','refused')), + -- the timestamp when this action will fire + execution_time timestamptz +); + +create index wf_case_enbl_act_case_idx on workflow_case_enabled_actions(case_id); +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); + 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.2 -r1.3 --- openacs-4/packages/workflow/tcl/action-procs-oracle.xql 6 Nov 2003 15:42:59 -0000 1.2 +++ openacs-4/packages/workflow/tcl/action-procs-oracle.xql 18 Nov 2003 17:57:56 -0000 1.3 @@ -2,7 +2,22 @@ oracle8.1.6 + + + insert into workflow_actions + (action_id, workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, + edit_fields, assigned_role, always_enabled_p, description, description_mime_type, timeout_seconds) + values (:action_id, :workflow_id, :sort_order, :short_name, :pretty_name, :pretty_past_tense, + :edit_fields, :assigned_role_id, :always_enabled_p, :description, :description_mime_type, :timeout_seconds) + + + + + timeout_seconds = :attr_timeout_seconds + + + select a.action_id, @@ -41,7 +56,7 @@ - + insert into workflow_action_allowed_roles select :action_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.2 -r1.3 --- openacs-4/packages/workflow/tcl/action-procs-postgresql.xql 6 Nov 2003 15:42:59 -0000 1.2 +++ openacs-4/packages/workflow/tcl/action-procs-postgresql.xql 18 Nov 2003 17:57:56 -0000 1.3 @@ -2,7 +2,23 @@ postgresql7.2 + + + insert into workflow_actions + (action_id, workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, + edit_fields, assigned_role, always_enabled_p, description, description_mime_type, timeout) + values (:action_id, :workflow_id, :sort_order, :short_name, :pretty_name, :pretty_past_tense, + :edit_fields, :assigned_role_id, :always_enabled_p, :description, :description_mime_type, + [ad_decode $timeout_seconds "" "null" "interval '$timeout_seconds seconds'"]) + + + + + timeout = [ad_decode $attr_timeout_seconds "" "null" "interval '$attr_timeout_seconds seconds'"] + + + select a.action_id, @@ -40,7 +56,7 @@ - + insert into workflow_action_allowed_roles select :action_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.10 -r1.11 --- openacs-4/packages/workflow/tcl/action-procs.tcl 6 Nov 2003 15:42:59 -0000 1.10 +++ openacs-4/packages/workflow/tcl/action-procs.tcl 18 Nov 2003 17:57:56 -0000 1.11 @@ -21,6 +21,7 @@ ad_proc -public workflow::action::new { {-workflow_id:required} + {-action_id {}} {-sort_order {}} {-short_name:required} {-pretty_name:required} @@ -34,42 +35,64 @@ {-initial_action_p f} {-description {}} {-description_mime_type {}} + {-timeout_seconds {}} + {-internal:boolean} } { This procedure is normally not invoked from application code. Instead a procedure for a certain workflow implementation, such as for example workflow::action::fsm::new (for Finite State Machine workflows), is used. @param workflow_id The id of the FSM workflow to add the action to + + @param action_id Optionally specify the ID of the new action. + @param sort_order The number which this action should be in the sort ordering sequence. Leave blank to add action at the end. If you provide a sort_order number which already exists, existing actions are pushed down one number. + @param short_name Short name of the action for use in source code. Should be on Tcl variable syntax. + @param pretty_name Human readable name of the action for use in UI. + @param pretty_past_tense Past tense of pretty name + @param edit_fields A space-separated list of the names of form fields which should be opened for editing when this action is carried out. + @param assigned_role The name of an assigned role. Users in this role are expected (obliged) to take the action. + @param allowed_roles A list of role names. Users in these roles are allowed to take the action. @param privileges Users with these privileges on the object treated by the workflow (i.e. a bug in the Bug Tracker) will be allowed to take this action. - @param callbacks List of names of service contract implementations of callbacks for the action in + + @param callbacks List of names of service contract implementations of callbacks for the action in impl_owner_name.impl_name format. + @param initial_action_p Use this switch to indicate that this is the initial action that will fire whenever a case of the workflow is created. The initial action is used to determine the initial state of the worklow as well as any procedures that should be executed when the case created. + @param timeout_seconds If zero, the action will automatically fire whenever it becomes enabled. + If greater than zero, the action will automatically fire x number of + seconds after the action is enabled. If empty, will never fire automatically. + + @param internal Set this flag if you're calling this proc from within the corresponding proc + for a particular workflow model. Will cause this proc to not flush the cache + or call workflow::definition_changed_handler, which the caller must then do. + @return The id of the created action @see workflow::action::fsm::new + @see workflow::definition_changed_handler @author Peter Marklund } { @@ -80,13 +103,14 @@ -workflow_id $workflow_id \ -table_name "workflow_actions"] } else { - set sort_order_taken_p [db_string select_sort_order_p {}] - if { $sort_order_taken_p } { - db_dml update_sort_order {} - } + workflow::action::update_sort_order \ + -workflow_id $workflow_id \ + -sort_order $sort_order } - set action_id [db_nextval "workflow_actions_seq"] + if { [empty_string_p $action_id] } { + set action_id [db_nextval "workflow_actions_seq"] + } if { [empty_string_p $assigned_role] } { set assigned_role_id [db_null] @@ -102,32 +126,180 @@ # Insert the action db_dml insert_action {} - # Record which roles are allowed to take action - foreach allowed_role $allowed_roles { - db_dml insert_allowed_role {} + # Set all the other attributes + array set update_cols [list] + set update_cols(allowed_roles) $allowed_roles + set update_cols(privileges) $privileges + set update_cols(callbacks) $callbacks + set update_cols(initial_action_p) $initial_action_p + + workflow::action::edit \ + -internal \ + -action_id $action_id \ + -workflow_id $workflow_id \ + -array update_cols + + if { !$internal_p } { + workflow::definition_changed_handler -workflow_id $workflow_id } + } - # Record which privileges enable the action - foreach privilege $privileges { - db_dml insert_privilege {} + if { !$internal_p } { + workflow::action::flush_cache -workflow_id $workflow_id + } + + return $action_id +} + +ad_proc -public workflow::action::edit { + {-action_id:required} + {-workflow_id {}} + {-array:required} + {-internal:boolean} +} { + Edit an action. + + @param action_id The action to edit. + + @param workflow_id Optionally specify the workflow_id. If not specified, we will execute a query to find it. + + @param array Name of an array in the caller's namespace with attributes to edit. + + @param internal Set this flag if you're calling this proc from within the corresponding proc + for a particular workflow model. Will cause this proc to not flush the cache + or call workflow::definition_changed_handler, which the caller must then do. + + @return action_id + + @see workflow::action::new +} { + upvar 1 $array row + foreach name [array names row] { + set missing_elm($name) 1 + } + + set set_clauses [list] + + if { [empty_string_p $workflow_id] } { + set workflow_id [workflow::action::get_element \ + -action_id $action_id \ + -element workflow_id] + } + + foreach attr { + short_name pretty_name pretty_past_tense edit_fields description description_mime_type + always_enabled_p + assigned_role + timeout_seconds + } { + if { [info exists row($attr)] } { + set varname attr_$attr + switch $attr { + always_enabled_p { + set $varname [db_boolean [template::util::is_true $row($attr)]] + } + assigned_role { + # Get role_id by short_name + set $varname [workflow::role::get_id \ + -workflow_id $workflow_id \ + -short_name $row($attr)] + } + default { + set $varname $row($attr) + } + } + switch $attr { + timeout_seconds { + lappend set_clauses [db_map update_timeout_seconds] + } + default { + lappend set_clauses "$attr = :$varname" + } + } + unset missing_elm($attr) } + } + + db_transaction { + if { [info exists row(sort_order)] } { + workflow::action::update_sort_order \ + -workflow_id $workflow_id \ + -sort_order $row(sort_order) + } + # Update action + if { [llength $set_clauses] > 0 } { + db_dml update_action " + update workflow_actions + set [join $set_clauses ", "] + where action_id = :action_id + " + } + + # Record which roles are allowed to take action + if { [info exists row(allowed_roles)] } { + db_dml delete_allowed_roles { + delete from workflow_action_allowed_roles + where action_id = :action_id + } + foreach allowed_role $row(allowed_roles) { + db_dml insert_allowed_role {} + } + unset missing_elm(allowed_roles) + } + + # Record which privileges enable the action + if { [info exists row(privileges)] } { + db_dml delete_privileges { + delete from workflow_action_privileges + where action_id = :action_id + } + foreach privilege $row(privileges) { + db_dml insert_privilege {} + } + unset missing_elm(privileges) + } + # Record if this is an initial action - if { [string equal $initial_action_p "t"] } { - db_dml insert_initial_action {} + if { [info exists row(initial_action_p)] } { + if { [template::util::is_true $row(initial_action_p)] } { + db_dml delete_initial_action { + delete from workflow_initial_action + where workflow_id = :workflow_id + } + db_dml insert_initial_action {} + } + unset missing_elm(initial_action_p) } # Callbacks - foreach callback_name $callbacks { - workflow::action::callback_insert \ + if { [info exists row(callbacks)] } { + db_dml delete_callbacks { + delete from workflow_action_callbacks + where action_id = :action_id + } + foreach callback_name $row(callbacks) { + workflow::action::callback_insert \ -action_id $action_id \ -name $callback_name + } + unset missing_elm(callbacks) } + # Check that there are no unknown attributes + if { [llength [array names missing_elm]] > 0 } { + error "Trying to set illegal action attributes: [join [array names row] ", "]" + } + + if { !$internal_p } { + workflow::definition_changed_handler -workflow_id $workflow_id + } } - # The cache is flushed in workflow::action::fsm::new - + if { !$internal_p } { + workflow::action::flush_cache -workflow_id $workflow_id + } + return $action_id } @@ -304,8 +476,20 @@ return $impl_names } +ad_proc -private workflow::action::update_sort_order { + {-workflow_id:required} + {-sort_order:required} +} { + Increase the sort_order of other actions, if the new sort_order is already taken. +} { + set sort_order_taken_p [db_string select_sort_order_p {}] + if { $sort_order_taken_p } { + db_dml update_sort_order {} + } +} + ###################################################################### # # workflow::action::fsm @@ -314,6 +498,7 @@ ad_proc -public workflow::action::fsm::new { {-workflow_id:required} + {-action_id {}} {-sort_order {}} {-short_name:required} {-pretty_name:required} @@ -330,6 +515,7 @@ {-initial_action_p f} {-description {}} {-description_mime_type {}} + {-timeout_seconds {}} } { Add an action to a certain FSM (Finite State Machine) workflow. This procedure invokes the generic workflow::action::new procedures @@ -346,22 +532,25 @@ db_transaction { # Generic workflow data: set action_id [workflow::action::new \ - -initial_action_p $initial_action_p \ - -workflow_id $workflow_id \ - -sort_order $sort_order \ - -short_name $short_name \ - -pretty_name $pretty_name \ - -pretty_past_tense $pretty_past_tense \ - -edit_fields $edit_fields \ - -allowed_roles $allowed_roles \ - -assigned_role $assigned_role \ - -privileges $privileges \ - -callbacks $callbacks \ - -always_enabled_p $always_enabled_p \ - -description $description \ - -description_mime_type $description_mime_type] + -internal \ + -initial_action_p $initial_action_p \ + -workflow_id $workflow_id \ + -action_id $action_id \ + -sort_order $sort_order \ + -short_name $short_name \ + -pretty_name $pretty_name \ + -pretty_past_tense $pretty_past_tense \ + -edit_fields $edit_fields \ + -allowed_roles $allowed_roles \ + -assigned_role $assigned_role \ + -privileges $privileges \ + -callbacks $callbacks \ + -always_enabled_p $always_enabled_p \ + -description $description \ + -description_mime_type $description_mime_type \ + -timeout_seconds $timeout_seconds] - # FSM specific data: + # FSM specific information below # Record whether the action changes state if { ![empty_string_p $new_state] } { @@ -373,29 +562,116 @@ } db_dml insert_fsm_action {} + array set update_cols [list] + set update_cols(enabled_states) $enabled_states + set update_cols(assigned_states) $assigned_states + + workflow::action::fsm::edit \ + -internal \ + -action_id $action_id \ + -workflow_id $workflow_id \ + -array update_cols + + workflow::definition_changed_handler -workflow_id $workflow_id + } + + workflow::action::flush_cache -workflow_id $workflow_id + + return $action_id +} + +ad_proc -public workflow::action::fsm::edit { + {-action_id:required} + {-workflow_id {}} + {-array:required} + {-internal:boolean} +} { + Edit an FSM action. + + @param action_id The action to edit. + + @param workflow_id Optionally specify the workflow_id. If not specified, we will execute a query to find it. + + @param array Name of an array in the caller's namespace with attributes to edit. + + @param internal Set this flag if you're calling this proc from within the corresponding proc + for a particular workflow model. Will cause this proc to not flush the cache + or call workflow::definition_changed_handler, which the caller must then do. + + @return action_id + + @see workflow::action::fsm::new +} { + upvar 1 $array org_row + + # We make a copy here, so the check for illegal attributes in workflow::action::edit works properly + array set row [array get org_row] + + if { [empty_string_p $workflow_id] } { + set workflow_id [workflow::action::get_element \ + -action_id $action_id \ + -element workflow_id] + } + + db_transaction { + + # Record whether the action changes state + if { [info exists row(new_state)] } { + if { ![empty_string_p $row(new_state)] } { + set new_state_id [workflow::state::fsm::get_id \ + -workflow_id $workflow_id \ + -short_name $new_state] + } else { + set new_state_id [db_null] + } + db_dml update_fsm_action {} + unset row(new_state) + } + # Record in which states the action is enabled but not assigned - foreach state_short_name $enabled_states { - set enabled_state_id [workflow::state::fsm::get_id \ - -workflow_id $workflow_id \ - -short_name $state_short_name] + if { [info exists row(enabled_states)] } { set assigned_p "f" - db_dml insert_enabled_state {} + db_dml delete_enabled_states {} + foreach state_short_name $row(enabled_states) { + set enabled_state_id [workflow::state::fsm::get_id \ + -workflow_id $workflow_id \ + -short_name $state_short_name] + db_dml insert_enabled_state {} + } + unset row(enabled_states) } # Record where the action is both enabled and assigned - foreach state_short_name $assigned_states { - set enabled_state_id [workflow::state::fsm::get_id \ - -workflow_id $workflow_id \ - -short_name $state_short_name] + if { [info exists row(assigned_states)] } { set assigned_p "t" - db_dml insert_enabled_state {} + db_dml delete_enabled_states {} + foreach state_short_name $row(assigned_states) { + set enabled_state_id [workflow::state::fsm::get_id \ + -workflow_id $workflow_id \ + -short_name $state_short_name] + db_dml insert_enabled_state {} + } + unset row(assigned_states) } - } - workflow::action::flush_cache -workflow_id $workflow_id - return $action_id + # This will error if there are attributes it doesn't know about, so we remove the attributes we know above + workflow::action::edit \ + -internal \ + -action_id $action_id \ + -workflow_id $workflow_id \ + -array row + + if { !$internal_p } { + workflow::definition_changed_handler -workflow_id $workflow_id + } + } + + if { !$internal_p } { + workflow::action::flush_cache -workflow_id $workflow_id + } } + ad_proc -public workflow::action::fsm::delete { {-action_id:required} } { Index: openacs-4/packages/workflow/tcl/action-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/action-procs.xql,v diff -u -r1.8 -r1.9 --- openacs-4/packages/workflow/tcl/action-procs.xql 6 Nov 2003 15:42:59 -0000 1.8 +++ openacs-4/packages/workflow/tcl/action-procs.xql 18 Nov 2003 17:57:57 -0000 1.9 @@ -1,7 +1,7 @@ - + select count(*) from workflow_actions @@ -10,7 +10,7 @@ - + update workflow_actions set sort_order = sort_order + 1 @@ -19,25 +19,15 @@ - + insert into workflow_action_privileges (action_id, privilege) values (:action_id, :privilege) - - - insert into workflow_actions - (action_id, workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, - edit_fields, assigned_role, always_enabled_p, description, description_mime_type) - values (:action_id, :workflow_id, :sort_order, :short_name, :pretty_name, :pretty_past_tense, - :edit_fields, :assigned_role_id, :always_enabled_p, :description, :description_mime_type) - - - - + insert into workflow_initial_action (workflow_id, action_id) @@ -130,8 +120,24 @@ - + + update workflow_fsm_actions + set new_state = :new_state_id + where action_id = :action_id + + + + + + delete from workflow_fsm_action_en_in_st + where action_id = :action_id + and assigned_p = :assigned_p + + + + + insert into workflow_fsm_action_en_in_st (action_id, state_id, assigned_p) values (:action_id, :enabled_state_id, :assigned_p) 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.4 -r1.5 --- openacs-4/packages/workflow/tcl/case-procs-oracle.xql 30 Sep 2003 12:10:12 -0000 1.4 +++ openacs-4/packages/workflow/tcl/case-procs-oracle.xql 18 Nov 2003 17:57:57 -0000 1.5 @@ -20,6 +20,16 @@ + + + select case_id, + action_id + from workflow_case_enabled_actions + where execution_time <= sysdate + and enabled_state = 'enabled' + + + select m.party_id, @@ -87,6 +97,29 @@ + + + select a.action_id, + a.timeout_seconds + from workflow_cases c, + workflow_actions a + where c.case_id = :case_id + and a.workflow_id = c.workflow_id + and not exists (select 1 + from workflow_initial_action wia + where wia.workflow_id = c.workflow_id + and wia.action_id = a.action_id) + and (a.always_enabled_p = 't' + or exists (select 1 + from workflow_case_fsm cfsm, + workflow_fsm_action_en_in_st waeis + where cfsm.case_id = c.case_id + and waeis.state_id = cfsm.current_state + and waeis.action_id = a.action_id)) + order by a.sort_order + + + select acs_object.name(:object_id) as name from dual @@ -101,4 +134,25 @@ + + + update workflow_case_enabled_actions + set enabled_state = 'completed', + executed_date = sysdate + where case_id = :case_id + and action_id = :action_id + and enabled_state = 'enabled' + + + + + + insert into workflow_case_enabled_actions + (enabled_action_id, case_id, action_id, enabled_state, execution_time) + select :enabled_action_id, :case_id, a.action_id, 'enabled', sysdate + a.timeout_seconds/(24*60*60) + from workflow_actions a + where a.action_id = :action_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.4 -r1.5 --- openacs-4/packages/workflow/tcl/case-procs-postgresql.xql 28 Aug 2003 09:41:59 -0000 1.4 +++ openacs-4/packages/workflow/tcl/case-procs-postgresql.xql 18 Nov 2003 17:57:57 -0000 1.5 @@ -61,6 +61,39 @@ + + + select a.action_id, + extract(seconds from a.timeout) as timeout_seconds + from workflow_cases c, + workflow_actions a + where c.case_id = :case_id + and a.workflow_id = c.workflow_id + and not exists (select 1 + from workflow_initial_action wia + where wia.workflow_id = c.workflow_id + and wia.action_id = a.action_id) + and (a.always_enabled_p = 't' + or exists (select 1 + from workflow_case_fsm cfsm, + workflow_fsm_action_en_in_st waeis + where cfsm.case_id = c.case_id + and waeis.state_id = cfsm.current_state + and waeis.action_id = a.action_id)) + order by a.sort_order + + + + + + select case_id, + action_id + from workflow_case_enabled_actions + where execution_time <= current_timestamp + and enabled_state = 'enabled' + + + select acs_object__name(:object_id) as name @@ -91,4 +124,25 @@ + + + update workflow_case_enabled_actions + set enabled_state = 'completed', + executed_date = current_timestamp + where case_id = :case_id + and action_id = :action_id + and enabled_state = 'enabled' + + + + + + insert into workflow_case_enabled_actions + (enabled_action_id, case_id, action_id, enabled_state, execution_time) + select :enabled_action_id, :case_id, a.action_id, 'enabled', current_timestamp + a.timeout + from workflow_actions a + where a.action_id = :action_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.15 -r1.16 --- openacs-4/packages/workflow/tcl/case-procs.tcl 13 Oct 2003 20:24:57 -0000 1.15 +++ openacs-4/packages/workflow/tcl/case-procs.tcl 18 Nov 2003 17:57:57 -0000 1.16 @@ -50,8 +50,8 @@ ad_proc -public workflow::case::new { {-workflow_id:required} {-object_id:required} - {-comment:required} - {-comment_mime_type:required} + {-comment {}} + {-comment_mime_type {}} {-user_id} } { Start a new case for this workflow and object. @@ -250,34 +250,28 @@ If any of these currently have zero assignees, run the default assignment process. - @param case_id the ID of the case. + @param case_id The ID of the case. + + @param all Set this to assign all roles for this case. + This parameter is deprecated, and always assumed. @author Lars Pind (lars@collaboraid.biz) } { - set role_id_list [list] + set role_ids [db_list select_unassigned_roles { + select r.role_id + from workflow_roles r + where not exists (select 1 + from workflow_case_role_user_map m + where m.role_id = r.role_id + and m.case_id = :case_id) + }] - if { $all_p } { - set workflow_id [workflow::case::get_element -case_id $case_id -element workflow_id] - set role_id_list [workflow::get_roles -workflow_id $workflow_id] - } else { - foreach action_id [get_enabled_actions -case_id $case_id] { - set role_id [workflow::action::get_assigned_role -action_id $action_id] - if { ![empty_string_p $role_id] && [lsearch $role_id_list $role_id] == -1 } { - lappend role_id_list $role_id - } - } + foreach role_id $role_ids { + workflow::case::role::set_default_assignees \ + -case_id $case_id \ + -role_id $role_id } - foreach role_id $role_id_list { - set num_assignees [db_string select_num_assignees {}] - - if { $num_assignees == 0 } { - workflow::case::role::set_default_assignees \ - -case_id $case_id \ - -role_id $role_id - } - } - workflow::case::role::flush_cache -case_id $case_id } @@ -669,8 +663,74 @@ workflow::case::role::flush_cache -case_id $case_id } +ad_proc -private workflow::case::state_changed_handler { + {-case_id:required} + {-user_id {}} +} { + Scans for newly enabled actions, as well as actions which were + enabled but are now no longer enabled. Does not flush the cache. + Should only be called indirectly through the workflow API. + @author Lars Pind (lars@collaboraid.biz) +} { + db_transaction { + # Columns: action_id, timeout_seconds + db_multirow -local enabled_actions select_enabled_actions {} + + # This array, keyed by action_id, will store the enabled_action_id for previously enabled actions + array set action_enabled [list] + db_foreach select_previously_enabled_actions {} { + set action_enabled($action_id) $enabled_action_id + } + + # Loop over currently enabled actions and find out which ones are new + array set newly_enabled_action [list] + template::multirow -local foreach enabled_actions { + 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 + unset action_enabled($action_id) + } else { + # Newly enabled action + set newly_enabled_action($action_id) 1 + } + } + + # First, unenable the previously but no longer enabled actions + foreach action_id [array names action_enabled] { + workflow::case::action::unenable \ + -enabled_action_id $action_enabled($action_id) + } + # Second, enable the newly enabled actions + template::multirow -local foreach enabled_actions { + 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 + } + } + + # Make sure roles are assigned, if possible + workflow::case::assign_roles -all -case_id $case_id + } +} + +ad_proc -public workflow::case::timed_actions_sweeper {} { + Sweep for timed actions ready to fire. +} { + db_multirow -local actions select_timed_out_actions {} + + template::multirow -local foreach actions { + workflow::case::action::execute \ + -no_perm_check \ + -case_id $case_id \ + -action_id $action_id + } +} + ##### # # workflow::case::role namespace @@ -1207,27 +1267,36 @@ } ad_proc -public workflow::case::action::execute { + {-no_perm_check:boolean} {-case_id:required} {-action_id:required} - {-comment:required} - {-comment_mime_type:required} + {-comment ""} + {-comment_mime_type "text/plain"} {-user_id} {-initial:boolean} {-entry_id {}} } { Execute the action @param case_id The ID of the case. + @param action_id The ID of the action + @param comment Comment for the case activity log + @param comment_mime_type MIME Type of the comment, according to OpenACS standard text formatting + @param user_id The user who's executing the action + @param initial Use this switch to signal that this is the initial action. This causes permissions/enabled checks to be bypasssed, and causes all roles to get assigned. + @param entry_id Optional item_id for double-click protection. If you call workflow::case::fsm::get with a non-empty action_id, it will generate a new entry_id for you, which you can pass in here. + @param no_perm_check Set this switch if you do not want any permissions chcecking, e.g. for automatic actions. + @return entry_id of the new log entry (will be a cr_item). @author Lars Pind (lars@collaboraid.biz) @@ -1237,33 +1306,43 @@ } if { !$initial_p } { - if { ![available_p -case_id $case_id -action_id $action_id -user_id $user_id] } { - error "This user is not allowed to perform this action at this time." + if { ![enabled_p -case_id $case_id -action_id $action_id] } { + error "This action is not enabled at this time." } + + if { !$no_perm_check_p } { + if { ![permission_p -case_id $case_id -action_id $action_id -user_id $user_id] } { + error "This user is not allowed to perform this action at this time." + } + } } - set new_state_id [workflow::action::fsm::get_new_state -action_id $action_id] + if { [empty_string_p $comment] } { + set comment { } + } + # We can't have empty comment_mime_type, default to text/plain + if { [empty_string_p $comment_mime_type] } { + set comment_mime_type "text/plain" + } + db_transaction { - # Update the workflow state - if { ![empty_string_p $new_state_id] } { - db_dml update_fsm_state {} - } + # Update the case workflow state + workflow::case::action::fsm::execute_state_change \ + -case_id $case_id \ + -action_id $action_id + # Update workflow_case_enabled_transactions + db_dml set_completed {} + # Double-click protection if { ![empty_string_p $entry_id] } { if { [db_string log_entry_exists_p {}] } { return $entry_id } } - # We can't have empty comment_mime_type - if { [empty_string_p $comment_mime_type] } { - # We need a default value here - set comment_mime_type "text/plain" - } - # Insert activity log entry set extra_vars [ns_set create] oacs_util::vars_to_ns_set \ @@ -1276,30 +1355,78 @@ -package_name "workflow_case_log_entry" \ "workflow_case_log_entry"] - # Assign new enabled roles, if currently unassigned - workflow::case::assign_roles -all=$initial_p -case_id $case_id - # Fire side-effects do_side_effects \ -case_id $case_id \ -action_id $action_id \ -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 + + # Scan for enabled actions + workflow::case::state_changed_handler \ + -case_id $case_id \ + -user_id $user_id } workflow::case::flush_cache -case_id $case_id - # Notifications - notify \ - -case_id $case_id \ - -action_id $action_id \ - -entry_id $entry_id \ - -comment $comment \ - -comment_mime_type $comment_mime_type - return $entry_id } +ad_proc -private workflow::case::action::unenable { + {-enabled_action_id:required} +} { + Update the workflow_case_enabled_actions table to say that the + previously enabled actions are no longer enabled. + Does not flush the cache. + Should only be called indirectly through the workflow API. + + @author Lars Pind (lars@collaboraid.biz) +} { + db_transaction { + db_dml set_canceled { + update workflow_case_enabled_actions + set enabled_state = 'canceled' + where enabled_action_id = :enabled_action_id + } + } +} + +ad_proc -private workflow::case::action::enable { + {-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. + Does not flush the cache. + Should only be called indirectly through the workflow API. + + @author Lars Pind (lars@collaboraid.biz) +} { + db_transaction { + set enabled_action_id [db_nextval workflow_case_enbl_act_seq] + + db_dml insert_enabled {} + + if { $automatic_p } { + workflow::case::action::execute \ + -no_perm_check \ + -case_id $case_id \ + -action_id $action_id \ + -user_id $user_id + } + } +} + ad_proc -public workflow::case::action::do_side_effects { {-case_id:required} {-action_id:required} @@ -1541,3 +1668,26 @@ } return $new_state_id } + + +ad_proc -public workflow::case::action::fsm::execute_state_change { + {-case_id:required} + {-action_id:required} +} { + Modify the state of the case as required when executing the given action. + + @param case_id The ID of the case. + @param action_id The ID of the action + + @author Lars Pind (lars@collaboraid.biz) +} { + # We wrap this in a transaction, which will be the same transaction as the parent one inside + # workflow::case::action::execute + + db_transaction { + set new_state_id [workflow::action::fsm::get_new_state -action_id $action_id] + if { ![empty_string_p $new_state_id] } { + db_dml update_fsm_state {} + } + } +} 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.8 -r1.9 --- openacs-4/packages/workflow/tcl/case-procs.xql 1 Sep 2003 13:43:51 -0000 1.8 +++ openacs-4/packages/workflow/tcl/case-procs.xql 18 Nov 2003 17:57:57 -0000 1.9 @@ -54,21 +54,11 @@ select a.action_id - from workflow_cases c, + from workflow_case_enabled_actions ena, workflow_actions a - where c.case_id = :case_id - and a.workflow_id = c.workflow_id - and not exists (select 1 - from workflow_initial_action wia - where wia.workflow_id = c.workflow_id - and wia.action_id = a.action_id) - and (a.always_enabled_p = 't' - or exists (select 1 - from workflow_case_fsm cfsm, - workflow_fsm_action_en_in_st waeis - where cfsm.case_id = c.case_id - and waeis.state_id = cfsm.current_state - and waeis.action_id = a.action_id)) + where ena.case_id = :case_id + and a.action_id = ena.action_id + and enabled_state = 'enabled' order by a.sort_order @@ -109,6 +99,20 @@ + + + + select a.action_id, + ena.enabled_action_id + from workflow_case_enabled_actions ena, + workflow_actions a + where ena.case_id = :case_id + and a.action_id = ena.action_id + and enabled_state = 'enabled' + order by a.sort_order + + + select impl.impl_name @@ -183,20 +187,14 @@ select 1 - from workflow_actions a - where a.action_id = :action_id - and (a.always_enabled_p = 't' or - exists (select 1 - from workflow_fsm_action_en_in_st waeis, - workflow_case_fsm c_fsm - where waeis.action_id = a.action_id - and c_fsm.case_id = :case_id - and waeis.state_id = c_fsm.current_state) - ) + from workflow_case_enabled_actions ean + where ean.action_id = :action_id + and ean.case_id = :case_id + and enabled_state = 'enabled' - + update workflow_case_fsm set current_state = :new_state_id @@ -211,7 +209,6 @@ where item_id = :entry_id - select distinct rum.user_id Index: openacs-4/packages/workflow/tcl/install-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/install-procs.tcl,v diff -u -r1.6 -r1.7 --- openacs-4/packages/workflow/tcl/install-procs.tcl 28 Aug 2003 09:41:59 -0000 1.6 +++ openacs-4/packages/workflow/tcl/install-procs.tcl 18 Nov 2003 17:57:57 -0000 1.7 @@ -46,7 +46,27 @@ } } +ad_proc -private workflow::install::after_upgrade { + {-from_version_name:required} + {-to_version_name:required} +} { + Workflow package after upgrade callback proc +} { + apm_upgrade_logic \ + -from_version_name $from_version_name \ + -to_version_name $to_version_name \ + -spec { + 1.2 2.0d1 { + set workflow_ids [db_list select_workflow_ids { select workflow_id from workflows }] + foreach workflow_id $workflow_ids { + workflow::definition_changed_handler \ + -workflow_id $workflow_id + } + } + } +} + ##### # # Create service contracts Index: openacs-4/packages/workflow/tcl/role-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/role-procs.tcl,v diff -u -r1.9 -r1.10 --- openacs-4/packages/workflow/tcl/role-procs.tcl 6 Nov 2003 15:42:59 -0000 1.9 +++ openacs-4/packages/workflow/tcl/role-procs.tcl 18 Nov 2003 17:57:57 -0000 1.10 @@ -434,7 +434,9 @@ lappend callback_impl_names,${role_id}(${row(contract_name)}) $row(impl_name) set callbacks_array,${role_id}($row(impl_id)) [array get row] } - unset row + if { [info exists row] } { + unset row + } foreach role_id $role_ids { set role,${role_id}(callback_impl_names) [array get callback_impl_names,$role_id] Index: openacs-4/packages/workflow/tcl/workflow-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/workflow-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/tcl/workflow-init.tcl 18 Nov 2003 17:57:57 -0000 1.1 @@ -0,0 +1,24 @@ +ad_library { + Initialization for workflow. + + @creation-date 18 November 2003 + @author Lars Pind (lars@collaboraid.biz) + @cvs-id $Id: workflow-init.tcl,v 1.1 2003/11/18 17:57:57 lars Exp $ +} + +#---------------------------------------------------------------------- +# Schedule the timed actions sweeper +#---------------------------------------------------------------------- + +set interval [parameter::get_from_package_key \ + -package_key "workflow" \ + -parameter "SweepTimedActionsFrequency" \ + -default 300] + +if { ![string is integer $interval] } { + ns_log Error "Workflow parameter SweepTimedActionsFrequency is not an integer. Value is '$interval'." + set interval 300 +} + +ad_schedule_proc -thread t $interval workflow::case::timed_actions_sweeper + 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.11 -r1.12 --- openacs-4/packages/workflow/tcl/workflow-procs.tcl 1 Nov 2003 08:45:39 -0000 1.11 +++ openacs-4/packages/workflow/tcl/workflow-procs.tcl 18 Nov 2003 17:57:57 -0000 1.12 @@ -209,10 +209,22 @@ return $action_data(action_ids) } +ad_proc -public workflow::definition_changed_handler { + {-workflow_id:required} +} { + 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. +} { + set case_ids [db_list select_cases { select case_id from workflow_cases where workflow_id = :workflow_id }] + foreach case_id $case_ids { + workflow::case::state_changed_handler \ + -case_id $case_id + } + +} - ##### # Private procs ##### Fisheye: Tag 1.4 refers to a dead (removed) revision in file `openacs-4/packages/workflow/tcl/test/workflow-test-init.tcl'. Fisheye: No comparison available. Pass `N' to diff? 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.11 -r1.12 --- openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 17 Oct 2003 08:54:45 -0000 1.11 +++ openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 18 Nov 2003 17:57:57 -0000 1.12 @@ -24,11 +24,13 @@ } ad_proc workflow::test::workflow_object_id {} { - + Return a dummy object_id for use for the workflow stuff. } { - return [db_string main_site_package_id {select object_id - from site_nodes - where parent_id is null}] + return [db_string main_site_package_id { + select object_id + from site_nodes + where parent_id is null + }] } ad_proc workflow::test::workflow_object_id_2 {} { @@ -432,7 +434,7 @@ teardown_chunk } { Execute code in test chunk and guarantee that code in - teardown_chunk will be executed even if error if thrown. + teardown_chunk will be executed even if error is thrown by the test_chunk. @author Peter Marklund } { @@ -647,3 +649,219 @@ aa_false "error during setup: $errMsg - $errorInfo" $error_p } } + + + +##### +# +# Register the test cases +# +##### + +aa_register_case bugtracker_workflow_create_normal { + Test creation and teardown of an FSM workflow case. +} { + workflow::test::run_bug_tracker_test -create_proc "workflow::test::workflow_setup" +} + +aa_register_case bugtracker_workflow_create_array_style { + Test creation and teardown of an FSM workflow case, with array style specification. + + @author Lars Pind + @creation-date 21 January 2003 +} { + workflow::test::run_bug_tracker_test -create_proc "workflow::test::workflow_setup_array_style" +} + +aa_register_case bugtracker_workflow_clone { + Test creation and teardown of cloning an FSM workflow case. + + @author Lars Pind + @creation-date 22 January 2003 +} { + set workflow_id_list [list] + set test_chunk { + set workflow_id_1 [workflow::test::workflow_setup] + lappend workflow_id_list $workflow_id_1 + set workflow_id_2 [workflow::fsm::clone -workflow_id $workflow_id_1 -object_id [workflow::test::workflow_object_id_2]] + lappend workflow_id_list $workflow_id_2 + + set spec_1 [workflow::fsm::generate_spec -workflow_id $workflow_id_1] + set spec_2 [workflow::fsm::generate_spec -workflow_id $workflow_id_2] + + aa_true "Generated spec from original and cloned workflow should be identical" \ + [string equal $spec_1 $spec_2] + } + + set error_p [catch $test_chunk errMsg] + + # Teardown + foreach workflow_id $workflow_id_list { + workflow::delete -workflow_id $workflow_id + } + + if { $error_p } { + global errorInfo + aa_false "error during setup: $errMsg - $errorInfo" $error_p + } +} + +aa_register_case workflow_spec_with_message_keys { + Test creating a workflow from a spec with message catalog + keys. +} { + set test_chunk { + + set workflow_id [workflow::fsm::new_from_spec \ + -spec [workflow::test::get_message_key_spec]] + + set generated_spec [workflow::fsm::generate_spec -workflow_id $workflow_id] + + aa_true "Checking that generated spec 2 is identical to the spec that we created from (except for ordering)" \ + [array_lists_equal_p $generated_spec [workflow::test::get_message_key_spec]] + } + + set teardown_chunk { + set workflow_id [workflow::get_id -package_key acs-automated-testing -short_name test_message_keys] + workflow::delete -workflow_id $workflow_id + } + + workflow::test::run_with_teardown $test_chunk $teardown_chunk +} + + +aa_register_case workflow_automatic_action { + Test workflow with automatic action. +} { + workflow::test::run_with_teardown { + # Define workflow + + set workflow_id [workflow::new \ + -short_name "test_automatic_ations" \ + -pretty_name "Testing Automatic Actions" \ + -package_key "acs-automated-testing"] + + # [open] -> (open) -> [auto] -> (closed) + + workflow::state::fsm::new \ + -workflow_id $workflow_id \ + -short_name "open" \ + -pretty_name "Open" + + workflow::state::fsm::new -workflow_id $workflow_id \ + -short_name "closed" \ + -pretty_name "Closed" + + workflow::action::fsm::new \ + -initial_action_p t \ + -workflow_id $workflow_id \ + -short_name [ad_generate_random_string] \ + -pretty_name "Open" \ + -new_state "open" + + set auto_action_id [workflow::action::fsm::new \ + -workflow_id $workflow_id \ + -short_name "auto" \ + -pretty_name "Auto" \ + -enabled_states "open" \ + -new_state "closed" \ + -timeout_seconds 0] + + # Start a case + + set case_id [workflow::case::new \ + -workflow_id $workflow_id \ + -object_id [workflow::test::workflow_object_id] \ + -user_id [workflow::test::admin_owner_id]] + + # Check that it's now in 'closed' state + set current_state [workflow::case::fsm::get_current_state -case_id $case_id] + set current_state_short [workflow::state::fsm::get_element -state_id $current_state -element short_name] + + aa_equals "Case is closed" $current_state_short "closed" + + # Change the action to be timed + + set update_cols(timeout_seconds) 1 + workflow::action::fsm::edit \ + -action_id $auto_action_id \ + -array update_cols + + set case_id [workflow::case::new \ + -workflow_id $workflow_id \ + -object_id [db_string objid { select max(object_id) from acs_objects } ] \ + -user_id [workflow::test::admin_owner_id]] + + # Check that it's now in 'open' state + set current_state [workflow::case::fsm::get_current_state -case_id $case_id] + set current_state_short [workflow::state::fsm::get_element -state_id $current_state -element short_name] + + aa_equals "Case is open" $current_state_short "open" + + # Run sweeper + ns_sleep 1 + workflow::case::timed_actions_sweeper + + # Check that it's now in 'closed' state + set current_state [workflow::case::fsm::get_current_state -case_id $case_id] + set current_state_short [workflow::state::fsm::get_element -state_id $current_state -element short_name] + + aa_equals "Case is closed" $current_state_short "closed" + + # Add another action + + # 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" \ + -pretty_name "Reopen" \ + -enabled_states "closed" \ + -new_state "open"] + + # The new action should now be anabled + aa_true "New 'reopen' action is enabled" [workflow::case::action::enabled_p \ + -case_id $case_id \ + -action_id $reopen_action_id] + + # Execute it + workflow::case::action::execute \ + -no_perm_check \ + -case_id $case_id \ + -action_id $reopen_action_id + + # The new action should now be anabled + aa_false "New 'reopen' action is not enabled" [workflow::case::action::enabled_p \ + -case_id $case_id \ + -action_id $reopen_action_id] + + # Case should be open + set current_state [workflow::case::fsm::get_current_state -case_id $case_id] + set current_state_short [workflow::state::fsm::get_element -state_id $current_state -element short_name] + aa_equals "Case is open" $current_state_short "open" + + # The new action should now be anabled again + aa_true "Timed 'close' action is enabled" [workflow::case::action::enabled_p \ + -case_id $case_id \ + -action_id $auto_action_id] + # Run sweeper + ns_sleep 1 + workflow::case::timed_actions_sweeper + + # Check that it's now in 'closed' state + set current_state [workflow::case::fsm::get_current_state -case_id $case_id] + set current_state_short [workflow::state::fsm::get_element -state_id $current_state -element short_name] + + aa_equals "Case is closed" $current_state_short "closed" + + # The new action should now be anabled again + aa_true "New 'reopen' action is enabled" [workflow::case::action::enabled_p \ + -case_id $case_id \ + -action_id $reopen_action_id] + + } { + set workflow_id [workflow::get_id -package_key "acs-automated-testing" -short_name "test_automatic_ations"] + workflow::delete -workflow_id $workflow_id + } +}