action-procs.tcl
Does not contain a contract.
- Location:
- /packages/workflow/tcl/action-procs.tcl
Related Files
- packages/workflow/tcl/action-procs.xql
- packages/workflow/tcl/action-procs.tcl
- packages/workflow/tcl/action-procs-postgresql.xql
- packages/workflow/tcl/action-procs-oracle.xql
[ hide source ] | [ make this the default ]
File Contents
ad_library { Procedures in the workflow::action namespace. @creation-date 9 January 2003 @author Lars Pind (lars@collaboraid.biz) @author Peter Marklund (peter@collaboraid.biz) @cvs-id $Id: action-procs.tcl,v 1.44 2022/09/28 09:12:55 gustafn Exp $ } namespace eval workflow::action {} namespace eval workflow:::action::fsm {} ##### # # workflow::action namespace # ##### d_proc -public workflow::action::new { {-workflow_id:required} {-action_id {}} {-sort_order {}} {-short_name {}} {-pretty_name:required} {-pretty_past_tense {}} {-edit_fields {}} {-assigned_role {}} {-allowed_roles {}} {-privileges {}} {-callbacks {}} {-always_enabled_p f} -initial_action_p {-trigger_type user} {-parent_action {}} {-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 short_name of an assigned role. Users in this role are expected (obliged) to take the action. @param allowed_roles A list of role short_names or IDs. 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 impl_owner_name.impl_name format. @param trigger_type user, auto, message, time, init, workflow, parallel, dynamic. @param parent_action Short_name of the action's parent action. @param initial_action_p Deprecated. 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::edit @see workflow::action::fsm::edit @see workflow::definition_changed_handler @author Peter Marklund } { # Wrapper for workflow::action::edit array set row [list] foreach col { initial_action_p sort_order short_name pretty_name pretty_past_tense edit_fields allowed_roles assigned_role privileges callbacks always_enabled_p description description_mime_type timeout_seconds trigger_type parent_action } { if { [info exists $col] } { set row($col) [set $col] } } set action_id [workflow::action::edit \ -operation "insert" \ -action_id $action_id \ -workflow_id $workflow_id \ -array row] return $action_id } d_proc -public workflow::action::edit { {-operation "update"} {-action_id {}} {-workflow_id {}} {-array {}} {-internal:boolean} {-no_complain:boolean} {-handlers { roles "workflow::role" actions "workflow::action" }} } { Edit an action. Attributes of the array: <ul> <li>short_name <li>pretty_name <li>pretty_past_tense <li>edit_fields <li>description <li>description_mime_type <li>sort_order <li>always_enabled_p <li>assigned_role <li>timeout_seconds <li>trigger_type <li>parent_action <li>parent_action_id <li>privileges <li>allowed_roles <li>callbacks <li>child_actions </ul> Deprecated but still supported: <ul> <li>initial_action_p </ul> @param operation insert, update, delete @param action_id For update/delete: The action to update or delete. For insert: Optionally specify a pre-generated action_id for the action. @param workflow_id For update/delete: Optionally specify the workflow_id. If not specified, we will execute a query to find it. For insert: The workflow_id of the new action. @param array For insert/update: Name of an array in the caller's namespace with attributes to insert/update. @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. @param no_complain Silently ignore extra attributes that we don't know how to handle. @return action_id @author Lars Pind (lars@collaboraid.biz) @see workflow::action::new } { switch $operation { update - delete { if { $action_id eq "" } { error "You must specify the action_id of the action to $operation." } } insert {} default { error "Illegal operation '$operation'" } } switch $operation { insert - update { upvar 1 $array row if { ![array exists row] } { error "Array $array does not exist or is not an array" } foreach name [array names row] { set missing_elm($name) 1 } } } switch $operation { insert { if { $workflow_id eq "" } { error "You must supply workflow_id" } # Default sort_order if { (![info exists row(sort_order)] || $row(sort_order) eq "") } { set row(sort_order) [workflow::default_sort_order \ -workflow_id $workflow_id \ -table_name "workflow_actions"] } # Default short_name on insert if { ![info exists row(short_name)] } { set row(short_name) {} } } update - delete { if { $workflow_id eq "" } { set workflow_id [workflow::action::get_element \ -action_id $action_id \ -element workflow_id] } } } # Parse column values switch $operation { insert - update { # Special-case: array entry parent_action (takes short_name) and parent_action_id (takes action_id) -- # DB column is parent_action_id (takes action_id_id) if { [info exists row(parent_action)] } { if { [info exists row(parent_action_id)] } { error "You cannot supply both parent_action ($row(parent_action)) (takes short_name) and parent_action_id ($row(parent_action_id)) (takes action_id)" } if { $row(parent_action) ne "" } { set row(parent_action_id) [workflow::action::get_id \ -workflow_id $workflow_id \ -short_name $row(parent_action)] } unset row(parent_action) unset missing_elm(parent_action) } # Record if this is an initial action (deprecated) if { [info exists row(initial_action_p)] } { if { [info exists row(trigger_type)] && $row(trigger_type) ne "user" } { error "You can't specify both initial_action_p (which is deprecated) and trigger_type (which has replaced it) at the same time. Stick to trigger_type." } if { [string is true -strict $row(initial_action_p)] } { set row(trigger_type) "init" } unset row(initial_action_p) unset missing_elm(initial_action_p) } set update_clauses [list] set insert_names [list] set insert_values [list] # Handle columns in the workflow_actions table foreach attr { short_name pretty_name pretty_past_tense edit_fields description description_mime_type sort_order always_enabled_p assigned_role timeout_seconds trigger_type parent_action_id } { if { [info exists row($attr)] } { set varname attr_$attr # Convert the Tcl value to something we can use in the query switch $attr { short_name { if { (![info exists row(pretty_name)] || $row(pretty_name) eq "") } { if { $row(short_name) eq "" } { error "You cannot edit with an empty short_name without also setting pretty_name" } else { set row(pretty_name) {} } } set $varname [workflow::action::generate_short_name \ -workflow_id $workflow_id \ -pretty_name $row(pretty_name) \ -short_name $row(short_name) \ -action_id $action_id] } always_enabled_p { set $varname [db_boolean [string is true -strict $row($attr)]] } assigned_role { if { $row($attr) eq "" } { set $varname "" } else { # 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) } } # Add the column to the insert/update statement switch $attr { timeout_seconds { lappend update_clauses "[db_map update_timeout_seconds_name] = [db_map update_timeout_seconds_value]" lappend insert_names [db_map update_timeout_seconds_name] lappend insert_values [db_map update_timeout_seconds_value] } default { lappend update_clauses "$attr = :$varname" lappend insert_names $attr lappend insert_values :$varname } } if { [info exists missing_elm($attr)] } { unset missing_elm($attr) } } } } } db_transaction { # Sort_order switch $operation { insert - update { if { [info exists row(sort_order)] } { workflow::action::update_sort_order \ -workflow_id $workflow_id \ -sort_order $row(sort_order) } } } # Do the insert/update/delete switch $operation { insert { if { $action_id eq "" } { set action_id [db_nextval "workflow_actions_seq"] } lappend insert_names action_id lappend insert_values :action_id lappend insert_names workflow_id lappend insert_values :workflow_id db_dml insert_action " insert into workflow_actions ([join $insert_names ", "]) values ([join $insert_values ", "]) " } update { if { [llength $update_clauses] > 0 } { db_dml update_action " update workflow_actions set [join $update_clauses ", "] where action_id = :action_id " } } delete { db_dml delete_action { delete from workflow_actions where action_id = :action_id } } } # Auxiliary rows switch $operation { insert - update { # 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) } # Callbacks 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) } # Child actions foreach { type namespace } $handlers { # type is 'roles', 'actions', 'states', etc. if { [info exists row(child_${type})] } { # First, delete existing objects foreach existing_action_id [${namespace}::get_ids \ -workflow_id $workflow_id \ -parent_action_id $action_id] { # LARS: Ugly as hell with the string range to cut from 'actions' to 'action_id' ${namespace}::edit -[string range $type 0 end-1]_id $existing_action_id } foreach { child_short_name child_spec } $row(child_${type}) { array unset child array set child $child_spec set child(short_name) $child_short_name set child(parent_action_id) $action_id # string trim everything foreach key [array names child] { set child($key) [string trim $child($key)] } ${namespace}::edit \ -internal \ -handlers $handlers \ -operation "insert" \ -workflow_id $workflow_id \ -array child } unset missing_elm(child_${type}) } } # Check that there are no unknown attributes if { [array size missing_elm] > 0 && !$no_complain_p } { error "Trying to set illegal action attributes: [join [array names missing_elm] ", "]" } } } if { !$internal_p } { workflow::definition_changed_handler -workflow_id $workflow_id } } return $action_id } d_proc -public workflow::action::delete { {-action_id:required} } { Delete action with given id. @author Peter Marklund } { workflow::action::edit -operation "delete" -action_id $action_id } d_proc -public workflow::action::get_assigned_role { {-action_id:required} } { Return the assigned role of the given action @param action_id The action_id of the action. @return role_id of the assigned role. } { return [get_from_request_cache $action_id "assigned_role_id"] } d_proc -public workflow::action::get_allowed_roles { {-action_id:required} } { Return the allowed roles of the given action @param action_id The action_id of the action. @return List of role_id of the allowed roles } { return [get_from_request_cache $action_id "allowed_role_ids"] } d_proc -public workflow::action::get_privileges { {-action_id:required} } { Return the assigned role of the given action @param action_id The action_id of the action. @return List of privileges that give permission to do this action } { return [get_from_request_cache $action_id "privileges"] } d_proc -public workflow::action::get_id { {-workflow_id:required} {-short_name:required} } { Return the action_id of the action with the given short_name in the given workflow. @param workflow_id The ID of the workflow @param short_name The short_name of the action @return action_id of the desired action, or the empty string if it can't be found. } { workflow::action::refresh_request_cache $workflow_id global __workflow_action_data,${workflow_id} foreach action_id [set __workflow_action_data,${workflow_id}(action_ids)] { array set one_action [set __workflow_action_data,${workflow_id}($action_id)] if {$one_action(short_name) eq $short_name} { return $action_id } } error "workflow::action::get_id: Action with short_name $short_name not found for workflow $workflow_id" } d_proc -public workflow::action::get_workflow_id { {-action_id:required} } { Lookup the workflow_id of a certain action_id. @author Peter Marklund } { return [util_memoize \ [list workflow::action::get_workflow_id_not_cached -action_id $action_id]] } d_proc -private workflow::action::get_workflow_id_not_cached { {-action_id:required} } { This is a proc that should only be used internally by the workflow API, applications should use workflow::action::get_workflow_id instead. @author Peter Marklund } { return [db_string select_workflow_id {}] } d_proc -public workflow::action::get { {-action_id:required} {-array:required} } { Return information about an action with a given id. @author Peter Marklund @author Lars Pind (lars@collaboraid.biz) @return The array will contain the following entries: workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, assigned_role (short_name), assigned_role_id, always_enabled_p, trigger_type, parent_action, parent_action_id, description, description_mime_type values for an action. @see workflow::action::get_all_info @see workflow::action::get_all_info_not_cached } { # Select the info into the upvar'ed Tcl Array upvar $array row array set row [get_from_request_cache $action_id] } d_proc -public workflow::action::get_element { {-action_id {}} {-one_id {}} {-element:required} } { Return a single element from the information about a action. @param action_id The ID of the action @param one_id Same as action_id, just used for consistency across roles/actions/states. @param element The element you want @return The element you asked for @author Lars Pind (lars@collaboraid.biz) } { if { $action_id eq "" } { if { $one_id eq "" } { error "You must supply either action_id or one_id" } set action_id $one_id } else { if { $one_id ne "" } { error "You can only supply either action_id or one_id" } } get -action_id $action_id -array row return $row($element) } d_proc -public workflow::action::callback_insert { {-action_id:required} {-name:required} {-sort_order {}} } { Add a side-effect to an action. @param action_id The ID of the action. @param name Name of service contract implementation, in the form (impl_owner_name).(impl_name), for example, bug-tracker.CaptureResolutionCode @param sort_order The sort_order for the rule. Leave blank to add to the end of the list @author Lars Pind (lars@collaboraid.biz) } { db_transaction { # Get the impl_id set acs_sc_impl_id [workflow::service_contract::get_impl_id -name $name] # Get the sort order if { (![info exists sort_order] || $sort_order eq "") } { set sort_order [db_string select_sort_order {}] } # Insert the callback db_dml insert_callback {} } set workflow_id [workflow::action::get_workflow_id -action_id $action_id] workflow::action::flush_cache -workflow_id $workflow_id return $acs_sc_impl_id } d_proc -private workflow::action::get_callbacks { {-action_id:required} {-contract_name:required} } { Return a list of implementation names for the callbacks of a given workflow action. @see workflow::case::role::get_callbacks @author Peter Marklund } { array set callbacks [get_from_request_cache $action_id callbacks_array] set callback_ids [get_from_request_cache $action_id callback_ids] # Loop over the callbacks and return the impl_names of those with a matching # contract name set impl_names [list] foreach callback_id $callback_ids { array set one_callback $callbacks($callback_id) if {$one_callback(contract_name) eq $contract_name} { lappend impl_names $one_callback(impl_name) } } return $impl_names } d_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 {} } } d_proc -public workflow::action::get_existing_short_names { {-workflow_id:required} {-ignore_action_id {}} } { Returns a list of existing action short_names in this workflow. Useful when you're trying to ensure a short_name is unique, or construct a new short_name that is guaranteed to be unique. @param ignore_action_id If specified, the short_name for the given action will not be included in the result set. } { set result [list] foreach action_id [workflow::get_actions -all -workflow_id $workflow_id] { if { $ignore_action_id eq "" || $ignore_action_id ne $action_id } { lappend result [workflow::action::get_element -action_id $action_id -element short_name] } } return $result } d_proc -public workflow::action::generate_short_name { {-workflow_id:required} {-pretty_name:required} {-short_name {}} {-action_id {}} } { Generate a unique short_name from pretty_name. @param action_id If you pass in this, we will allow that action's short_name to be reused. } { set existing_short_names [workflow::action::get_existing_short_names \ -workflow_id $workflow_id \ -ignore_action_id $action_id] if { $short_name eq "" } { if { $pretty_name eq "" } { error "Cannot have empty pretty_name when short_name is empty" } set short_name [util_text_to_url \ -replacement "_" \ -existing_urls $existing_short_names \ -text $pretty_name] } else { # Make lowercase, remove illegal characters set short_name [string tolower $short_name] regsub -all {[- ]} $short_name {_} short_name regsub -all {[^a-zA-Z_0-9]} $short_name {} short_name if {$short_name in $existing_short_names} { error "Action with short_name '$short_name' already exists in this workflow." } } return $short_name } d_proc -public workflow::action::get_ids { {-all:boolean} {-workflow_id:required} {-parent_action_id {}} } { Get the action_id's of all the actions in the workflow. @param workflow_id The ID of the workflow @return list of action_id's. @author Lars Pind (lars@collaboraid.biz) } { # Use cached data about actions array set action_data [workflow::action::get_all_info -workflow_id $workflow_id] if { $all_p } { return $action_data(action_ids) } set action_ids [list] foreach action_id $action_data(action_ids) { if { [workflow::action::get_element \ -action_id $action_id \ -element parent_action_id] == $parent_action_id } { lappend action_ids $action_id } } return $action_ids } d_proc -public workflow::action::get_options { {-all:boolean} {-workflow_id:required} {-parent_action_id {}} } { Get an options list of actions for use with form builder. } { set result [list] foreach action_id [workflow::get_actions \ -all=$all_p \ -workflow_id $workflow_id \ -parent_action_id $parent_action_id] { workflow::action::get -action_id $action_id -array action lappend result [list $action(pretty_name) $action_id] } return $result } d_proc -public workflow::action::pretty_name_unique_p { -workflow_id:required -pretty_name:required {-parent_action_id {}} {-action_id {}} } { Check if suggested pretty_name is unique. @return 1 if unique, 0 if not unique. } { set exists_p [db_string name_exists { select count(*) from workflow_actions where workflow_id = :workflow_id and pretty_name = :pretty_name and (:parent_action_id is null or parent_action_id = :parent_action_id) and (:action_id is null or action_id != :action_id) }] return [expr {!$exists_p}] } ###################################################################### # # workflow::action::fsm # ###################################################################### d_proc -public workflow::action::fsm::new { {-workflow_id:required} {-action_id {}} {-sort_order {}} {-short_name {}} {-pretty_name:required} {-pretty_past_tense {}} {-edit_fields {}} {-allowed_roles {}} {-assigned_role {}} {-privileges {}} {-enabled_states {}} {-assigned_states {}} {-enabled_state_ids {}} {-assigned_state_ids {}} {-new_state {}} {-new_state_id {}} {-callbacks {}} -initial_action_p {-always_enabled_p f} {-trigger_type user} {-parent_action {}} {-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 and does additional inserts for FSM specific information. See the parameter documentation for the proc workflow::action::new. @return the new action_id. @see workflow::action::fsm::edit @author Peter Marklund } { # Wrapper for workflow::action::edit array set row [list] foreach col { initial_action_p sort_order short_name pretty_name pretty_past_tense edit_fields allowed_roles assigned_role privileges callbacks always_enabled_p description description_mime_type timeout_seconds trigger_type parent_action } { if { [info exists $col] } { set row($col) [set $col] } } foreach elm { new_state new_state_id enabled_states assigned_states enabled_state_ids assigned_state_ids } { if { ([info exists $elm] && $$elm ne "") } { set row($elm) [set $elm] } } set action_id [workflow::action::fsm::edit \ -operation "insert" \ -action_id $action_id \ -workflow_id $workflow_id \ -array row] return $action_id } d_proc -public workflow::action::fsm::edit { {-operation "update"} {-action_id {}} {-workflow_id {}} {-array {}} {-internal:boolean} {-handlers { roles "workflow::role" actions "workflow::action::fsm" states "workflow::state::fsm" }} } { Edit an action. Attributes: <ul> <li>new_state_id <li>enabled_states <li>enabled_state_ids <li>enabled_actions <li>enabled_action_ids <li>child_states </ul> @param operation insert, update, delete @param action_id For update/delete: The action to update or delete. For insert: Optionally specify a pre-generated action_id for the action. @param workflow_id For update/delete: Optionally specify the workflow_id. If not specified, we will execute a query to find it. For insert: The workflow_id of the new action. @param array For insert/update: Name of an array in the caller's namespace with attributes to insert/update. @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::edit } { switch $operation { update - delete { if { $action_id eq "" } { error "You must specify the action_id of the action to $operation." } } insert {} default { error "Illegal operation '$operation'" } } switch $operation { insert - update { upvar 1 $array org_row if { ![array exists org_row] } { error "Array $array does not exist or is not an array" } array set row [array get org_row] } } switch $operation { insert { if { $workflow_id eq "" } { error "You must supply workflow_id" } } update - delete { if { $workflow_id eq "" } { set workflow_id [workflow::action::get_element \ -action_id $action_id \ -element workflow_id] } } } # Parse column values switch $operation { insert - update { # Special-case: array entry new_state (short_name) and new_state_id (state_id) -- DB column is new_state (state_id) if { [info exists row(new_state)] } { if { [info exists row(new_state_id)] } { error "You cannot supply both new_state (takes short_name) and new_state_id (takes state_id)" } if { $row(new_state) ne "" } { set row(new_state_id) [workflow::state::fsm::get_id \ -workflow_id $workflow_id \ -short_name $row(new_state)] } unset row(new_state) } set update_clauses [list] set insert_names [list] set insert_values [list] # Handle columns in the workflow_fsm_actions table foreach attr { new_state_id } { if { [info exists row($attr)] } { set varname attr_$attr # Convert the Tcl value to something we can use in the query switch $attr { new_state_id { set varname attr_new_state set $varname $row($attr) unset row($attr) set attr new_state } default { set $varname $row($attr) } } # Add the column to the insert/update statement switch $attr { default { lappend update_clauses "$attr = :$varname" lappend insert_names $attr lappend insert_values :$varname } } if { [info exists row($attr)] } { unset row($attr) } } } if { [info exists row(enabled_states)] } { if { [info exists row(enabled_state_ids)] } { error "You cannot supply both enabled_states and enabled_state_ids" } set row(enabled_state_ids) [list] foreach state_short_name $row(enabled_states) { lappend row(enabled_state_ids) [workflow::state::fsm::get_id \ -workflow_id $workflow_id \ -short_name $state_short_name] } unset row(enabled_states) } if { [info exists row(assigned_states)] } { if { [info exists row(assigned_state_ids)] } { error "You cannot supply both assigned_states and assigned_state_ids" } set row(assigned_state_ids) [list] foreach state_short_name $row(assigned_states) { lappend row(assigned_state_ids) [workflow::state::fsm::get_id \ -workflow_id $workflow_id \ -short_name $state_short_name] } unset row(assigned_states) } # Handle auxiliary rows array set aux [list] foreach attr { enabled_state_ids assigned_state_ids } { if { [info exists row($attr)] } { set aux($attr) $row($attr) unset row($attr) } } } } db_transaction { # Base row set action_id [workflow::action::edit \ -internal \ -handlers $handlers \ -operation $operation \ -action_id $action_id \ -workflow_id $workflow_id \ -array row] # Verify insert/update switch $operation { insert - update { set row_exists_p [db_string row_exists_p { select count(*) from workflow_fsm_actions where action_id = :action_id }] if { $row_exists_p } { set operation "update" } else { set operation "insert" } } } # FSM action row switch $operation { insert { lappend insert_names action_id lappend insert_values :action_id db_dml insert_action " insert into workflow_fsm_actions ([join $insert_names ", "]) values ([join $insert_values ", "]) " } update { if { [llength $update_clauses] > 0 } { db_dml update_action " update workflow_fsm_actions set [join $update_clauses ", "] where action_id = :action_id " } } delete { # Handled through cascading delete } } # Auxiliary rows switch $operation { insert - update { # Record in which states the action is enabled but not assigned if { [info exists aux(enabled_state_ids)] } { set assigned_p "f" db_dml delete_enabled_states {} foreach enabled_state_id $aux(enabled_state_ids) { db_dml insert_enabled_state {} } unset aux(enabled_state_ids) } # Record where the action is both enabled and assigned if { [info exists aux(assigned_state_ids)] } { set assigned_p "t" db_dml delete_enabled_states {} foreach enabled_state_id $aux(assigned_state_ids) { db_dml insert_enabled_state {} } unset aux(assigned_state_ids) } } } if { !$internal_p } { workflow::definition_changed_handler -workflow_id $workflow_id } } return $action_id } d_proc -public workflow::action::fsm::delete { {-action_id:required} } { Delete FSM action with given id. @author Peter Marklund } { workflow::action::fsm::edit -operation delete -action_id $action_id } d_proc -public workflow::action::fsm::get_new_state { {-action_id:required} } { Return the ID of the new state for an action @param action_id The action_id of the action. @return The ID of the new state after executing this action, or the empty string if the action doesn't change the state. } { return [workflow::action::get_from_request_cache $action_id "new_state_id"] } d_proc -public workflow::action::fsm::get { {-action_id:required} {-array:required} } { Return information about an action with a given id, including FSM-related info: enabled_states, enabled_state_ids, assigned_states, assigned_state_ids, new_state, new_state_id. @author Peter Marklund @author Lars Pind (lars@collaboraid.biz) } { # Select the info into the upvar'ed Tcl Array upvar $array row workflow::action::get -action_id $action_id -array row } d_proc -public workflow::action::fsm::get_element { {-action_id {}} {-one_id {}} {-element:required} } { Return element from information about an action with a given id, including FSM-related info such as 'enabled_in_states', and 'new_state'. Return a single element from the information about a action. @param action_id The ID of the action @param one_id Same as action_id, just used for consistency across roles/actions/states. @param element The element you want @return The element you asked for @author Peter Marklund @author Lars Pind (lars@collaboraid.biz) } { if { $action_id eq "" } { if { $one_id eq "" } { error "You must supply either action_id or one_id" } set action_id $one_id } else { if { $one_id ne "" } { error "You can only supply either action_id or one_id" } } workflow::action::fsm::get -action_id $action_id -array row return $row($element) } d_proc -public workflow::action::fsm::set_enabled_in_state { -action_id:required -state_id:required -enabled:boolean -assigned:boolean {-workflow_id {}} } { Edit the enabled state of an action @param workflow_id Optionally provide the workflow_id. If not, this will be gotten from a query. @author Lars Pind (lars@collaboraid.biz) } { if { $workflow_id eq "" } { set workflow_id [workflow::action::get_element \ -action_id $action_id \ -element workflow_id] } set currently_assigned_p [db_string enabled_p { select assigned_p from workflow_fsm_action_en_in_st where action_id = :action_id and state_id = :state_id } -default {}] set currently_enabled_p [expr {$currently_assigned_p ne ""}] set currently_assigned_p [string is true -strict $currently_assigned_p] set db_assigned_p [db_boolean $assigned_p] if { $currently_enabled_p != $enabled_p} { if { $enabled_p } { db_dml enabled { insert into workflow_fsm_action_en_in_st (action_id, state_id, assigned_p) values (:action_id, :state_id, :db_assigned_p) } } else { db_dml disable { delete from workflow_fsm_action_en_in_st where action_id = :action_id and state_id = :state_id } } } elseif { $currently_assigned_p != $assigned_p } { db_dml update_assigned_p { update workflow_fsm_action_en_in_st set assigned_p = :db_assigned_p where action_id = :action_id and state_id = :state_id } } workflow::definition_changed_handler -workflow_id $workflow_id } ##### # Private procs ##### d_proc -private workflow::action::fsm::parse_spec { {-workflow_id:required} {-short_name:required} {-spec:required} {-parent_action_id {}} } { Parse the spec for an individual action definition. @param workflow_id The id of the workflow to delete. @param short_name The short_name of the action @param spec The action spec @author Lars Pind (lars@collaboraid.biz) } { # Initialize array with default values array set action { pretty_past_tense {} edit_fields {} allowed_roles {} assigned_role {} privileges {} always_enabled_p f enabled_states {} assigned_states {} new_state {} trigger_type user callbacks {} } # Get the info from the spec foreach { key value } $spec { set action($key) [string trim $value] } set action(short_name) $short_name set action(parent_action_id) $parent_action_id # Create the action set action_id [workflow::action::fsm::edit \ -operation "insert" \ -workflow_id $workflow_id \ -array action] } d_proc -private workflow::action::fsm::generate_spec { {-action_id {}} {-one_id {}} {-handlers {}} } { Generate the spec for an individual action definition. @param action_id The id of the action to generate spec for. @param one_id Same as action_id, just used for consistency across roles/actions/states. @return spec The actions spec @author Lars Pind (lars@collaboraid.biz) } { if { $action_id eq "" } { if { $one_id eq "" } { error "You must supply either action_id or one_id" } set action_id $one_id } else { if { $one_id ne "" } { error "You can only supply either action_id or one_id" } } get -action_id $action_id -array row # Get rid of elements that shouldn't go into the spec array unset row short_name array unset row action_id array unset row workflow_id array unset row sort_order array unset row assigned_role_id array unset row new_state_id array unset row callbacks_array array unset row callback_ids array unset row allowed_roles_array array unset row allowed_role_ids array unset row enabled_state_ids array unset row assigned_state_ids array unset row parent_action array unset row parent_action_id foreach { type namespace } $handlers { # type is 'roles', 'actions', 'states', etc. # LARS: Ugly as hell with the string range to cut from 'actions' to 'action_ids' if { [info exists row(child_[string range $type 0 end-1]_ids)] } { set row(child_${type}) [list] foreach child_id $row(child_[string range $type 0 end-1]_ids) { set child_short_name [${namespace}::get_element \ -one_id $child_id \ -element short_name] set child_spec [${namespace}::generate_spec -one_id $child_id -handlers $handlers] lappend row(child_${type}) $child_short_name $child_spec } unset row(child_[string range $type 0 end-1]_ids) } } if { (![info exists row(description)] || $row(description) eq "") } { array unset row description_mime_type } # Get rid of a few defaults array set defaults { trigger_type user always_enabled_p f } set spec [list] foreach name [lsort [array names row]] { if { $row($name) ne "" && !([info exists defaults($name)] && $defaults($name) eq $row($name)) } { lappend spec $name $row($name) } } return $spec } d_proc -private workflow::action::flush_cache { {-workflow_id:required} } { Flush all caches related to actions for the given workflow_id. Used internally by the workflow API only. @author Peter Marklund } { # Flush the request cache global __workflow_action_data,${workflow_id} if { [info exists __workflow_action_data,${workflow_id}] } { foreach action_id [set __workflow_action_data,${workflow_id}(action_ids)] { global __workflow_one_action,$action_id if { [info exists __workflow_one_action,$action_id] } { unset __workflow_one_action,$action_id } } unset __workflow_action_data,${workflow_id} } # Flush the thread global cache util_memoize_flush [list workflow::action::get_all_info_not_cached -workflow_id $workflow_id] } ad_proc -private workflow::action::refresh_request_cache { workflow_id } { Initializes the cached array with information about actions for a certain workflow so that it can be reused within one request. @author Peter Marklund } { global __workflow_action_data,${workflow_id} if { ![info exists __workflow_action_data,${workflow_id}] } { array set __workflow_action_data,${workflow_id} [workflow::action::get_all_info -workflow_id $workflow_id] } } d_proc -private workflow::action::get_from_request_cache { action_id {element ""} } { This provides some abstraction for the Workflow API cache and also some optimization - we only convert lists to arrays once per request. Should be used internally by the workflow API only. @author Peter Marklund } { # Get the cache with all actions set workflow_id [workflow::action::get_workflow_id -action_id $action_id] refresh_request_cache $workflow_id global __workflow_action_data,${workflow_id} array set workflow_data [workflow::action::get_all_info -workflow_id $workflow_id] # A single action set action_var_name __workflow_one_action,${action_id} global $action_var_name if { ![info exists $action_var_name] } { array set $action_var_name [set __workflow_action_data,${workflow_id}($action_id)] } if { $element eq "" } { return [array get $action_var_name] } else { return [set "${action_var_name}($element)"] } } d_proc -private workflow::action::get_all_info { {-workflow_id:required} } { This proc is for internal use in the workflow API only. Returns all information related to actions for a certain workflow instance. Uses util_memoize to cache values. @see workflow::action::get_all_info_not_cached @author Peter Marklund } { return [util_memoize [list workflow::action::get_all_info_not_cached \ -workflow_id $workflow_id] [workflow::cache_timeout]] } d_proc -private workflow::action::get_all_info_not_cached { {-workflow_id:required} } { This proc is for internal use in the workflow API only and should not be invoked directly from application code. Returns all information related to actions for a certain workflow instance. Goes to the database on every invocation and should be used together with util_memoize. @author Peter Marklund } { # We avoid nested db queries in this proc to enhance performance # This is where we will ultimately deliver the results array set action_data {} # This will be a list of all action_id's set action_ids [list] # Get basic action info 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)] \ $workflow_id set action_id $action_row(action_id) array set action_array_${action_id} { callbacks_array {} callbacks {} callback_ids {} allowed_roles {} allowed_role_ids {} allowed_roles_array {} privileges {} assigned_states {} assigned_state_ids {} enabled_states {} enabled_state_ids {} child_states {} child_state_ids {} } array set action_array_${action_id} [array get action_row] if { $action_row(parent_action_id) ne "" } { lappend action_array_$action_row(parent_action_id)(child_action_ids) $action_id lappend action_array_$action_row(parent_action_id)(child_actions) $action_row(short_name) } lappend action_ids $action_id } foreach action_id $action_ids { if { ![info exists action_array_${action_id}(child_action_ids)] } { set action_array_${action_id}(child_action_ids) [list] set action_array_${action_id}(child_actios) [list] } } # Get child states foreach state_id [workflow::fsm::get_states -all -workflow_id $workflow_id] { workflow::state::fsm::get -state_id $state_id -array state_array if { $state_array(parent_action_id) ne "" } { lappend action_array_$state_array(parent_action_id)(child_state_ids) $state_id lappend action_array_$state_array(parent_action_id)(child_states) $state_array(short_name) } } # Build a separate array for all action callbacks of the workflow # Columns: impl_id, impl_name, impl_owner_name, contract_name, action_id db_foreach action_callbacks {} -column_array callback_row { set action_id $callback_row(action_id) lappend action_array_${action_id}(callbacks) \ "$callback_row(impl_owner_name).$callback_row(impl_name)" lappend action_array_${action_id}(callback_ids) $callback_row(impl_id) lappend action_array_${action_id}(callbacks_array) $callback_row(impl_id) [array get callback_row] } # Build an array for all allowed roles for all actions db_foreach action_allowed_roles {} -column_array allowed_role_row { set action_id $allowed_role_row(action_id) lappend action_array_${action_id}(allowed_roles) $allowed_role_row(short_name) lappend action_array_${action_id}(allowed_role_ids) $allowed_role_row(role_id) # The 'allowed_roles_array' entry is an array-list, keyed by role_id, with the value being # an array-list of the information returned by this call lappend action_array_${action_id}(allowed_roles_array) \ [list $allowed_role_row(role_id) [array get allowed_role_row]] } # Build an array of privileges for all actions db_foreach select_privileges {} { lappend action_array_${action_id}(privileges) $privilege } # Build arrays of enabled and assigned state short names for all actions db_foreach action_enabled_in_states {} { if {$assigned_p == "t"} { lappend action_array_${action_id}(assigned_states) $short_name lappend action_array_${action_id}(assigned_state_ids) $state_id } else { lappend action_array_${action_id}(enabled_states) $short_name lappend action_array_${action_id}(enabled_state_ids) $state_id } } # Move everything from the action_array_${action_id} arrays into the cacheo foreach action_id $action_ids { set action_data($action_id) [array get action_array_${action_id}] } set action_data(action_ids) $action_ids return [array get action_data] } d_proc -public workflow::action::fsm::get_ids { {-all:boolean} {-workflow_id:required} {-parent_action_id {}} } { Get the action_id's of all the actions in the workflow. @param workflow_id The ID of the workflow @return list of action_id's. @author Lars Pind (lars@collaboraid.biz) } { return [workflow::action::get_ids -all=$all_p -workflow_id $workflow_id -parent_action_id $parent_action_id] } # Local variables: # mode: tcl # tcl-indent-level: 4 # indent-tabs-mode: nil # End: