Index: openacs-4/packages/workflow/workflow.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/workflow.info,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/workflow.info 21 Jan 2003 18:05:20 -0000 1.3 +++ openacs-4/packages/workflow/workflow.info 3 Feb 2003 12:22:24 -0000 1.4 @@ -2,24 +2,27 @@ - Workflow Service - Workflow Services + Workflow + Workflows f t + workflow - + oracle postgresql Peter Marklund Lars Pind - A Tcl API for creating workflows that support the Bug Tracker, CMS publication, and simple approval. - 2003-01-10 + A Tcl API for creating workflows that support the Bug Tracker, CMS publication, simple approval, and much more. + 2003-01-31 Collaboraid - Currently this package uses a Finite State Machine (FSM) implementation of workflows but it is designed to allow for addition of other workflow implementations (i.e. Petri Nets or dependency graphs). For more information, see: <a href="http://www.collaboraid.biz/developer/workflow-spec">the workflow specification</a>. + 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>. - + @@ -34,8 +37,8 @@ - - + + @@ -48,6 +51,9 @@ + + + Index: openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql 21 Jan 2003 18:05:48 -0000 1.3 +++ openacs-4/packages/workflow/sql/postgresql/workflow-procedural-create.sql 3 Feb 2003 12:22:47 -0000 1.4 @@ -23,7 +23,7 @@ -- Function for creating a workflow -create function workflow__new ( +create or replace function workflow__new ( varchar, -- short_name varchar, -- pretty_name varchar, -- package_key @@ -65,3 +65,89 @@ return v_workflow_id; end; ' language 'plpgsql'; + + + + +-- Function for getting the pretty state of a case +create or replace function workflow_case__get_pretty_state ( + varchar, -- workflow_short_name + integer -- object_id +) +returns varchar as ' +declare + p_workflow_short_name alias for $1; + p_object_id alias for $2; + + v_state_pretty varchar; +begin + select s.pretty_name + into v_state_pretty + from workflows w, + workflow_cases c, + workflow_case_fsm cfsm, + workflow_fsm_states s + where w.short_name = p_workflow_short_name + and c.object_id = p_object_id + and c.workflow_id = w.workflow_id + and cfsm.case_id = c.case_id + and s.state_id = cfsm.current_state; + + return v_state_pretty; +end; +' language 'plpgsql'; + +create function workflow_activity_log__new (integer, --case_id + integer, --action_id + varchar -- comment_format + ) returns integer as ' +declare + new__case_id alias for $1; + new__action_id alias for $2; + new__comment_format alias for $3; + + + v_item_id cr_items.item_id%TYPE; + v_revision_id cr_revisions.revision_id%TYPE; +begin + + v_item_id := content_item__new ( + new__name, + new__parent_id, + new__item_id, + new__locale, + new__creation_date, + new__creation_user, + new__context_id, + new__creation_ip, + ''content_item'', + new__content_type, + null, + null, + null, + null, + null + ); + + v_revision_id := content_revision__new ( + new__title, + new__description, + new__publish_date, + new__mime_type, + new__nls_language, + null, + v_item_id, + new__revision_id, + new__creation_date, + new__creation_user, + new__creation_ip + + ); + + insert into workflow_case_log + (entry_id, case_id, action_id, comment_format) + values (v_revision_id, new__case_id, new__action_id, new__comment_format); + + PERFORM content_item__set_live_revision (v_revision_id); + +end; ' language 'plpgsql'; Index: openacs-4/packages/workflow/sql/postgresql/workflow-procedural-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/workflow-procedural-drop.sql,v diff -u -N -r1.2 -r1.3 --- openacs-4/packages/workflow/sql/postgresql/workflow-procedural-drop.sql 14 Jan 2003 15:09:07 -0000 1.2 +++ openacs-4/packages/workflow/sql/postgresql/workflow-procedural-drop.sql 3 Feb 2003 12:22:47 -0000 1.3 @@ -15,6 +15,7 @@ drop function workflow__delete (integer); drop function workflow__new (varchar, -- short_name varchar, -- pretty_name + varchar, -- package_key integer, -- object_id varchar, -- object_type integer, -- creation_user 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 -N -r1.5 -r1.6 --- openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 21 Jan 2003 18:05:48 -0000 1.5 +++ openacs-4/packages/workflow/sql/postgresql/workflow-tables-create.sql 3 Feb 2003 12:22:47 -0000 1.6 @@ -44,160 +44,130 @@ -- this workflows table. create table workflows ( workflow_id integer - constraint workflows_pk + constraint wfs_pk primary key - constraint workflows_workflow_id_fk + constraint wfs_workflow_id_fk references acs_objects(object_id) on delete cascade, short_name varchar(100) - constraint workflows_short_name_nn + constraint wfs_short_name_nn not null, pretty_name varchar(200) - constraint workflows_pretty_name_nn + constraint wfs_pretty_name_nn not null, object_id integer - constraint workflows_object_id_fk + constraint wfs_object_id_fk references acs_objects(object_id) on delete cascade, package_key varchar(100) - constraint workflows_apm_package_types_fk + constraint wfs_package_key_nn + not null + constraint wfs_apm_package_types_fk references apm_package_types(package_key), -- object_id points to either a package type, package instance, or single workflow case -- For Bug Tracker, every package instance will get its own workflow instance that is a copy -- of the workflow instance for the Bug Tracker package type object_type varchar(1000) - constraint workflows_object_type_nn + constraint wfs_object_type_nn not null - constraint workflows_object_type_fk + constraint wfs_object_type_fk references acs_object_types(object_type) on delete cascade, - constraint workflows_oid_sn_un - unique (object_id, short_name) + constraint wfs_oid_sn_un + unique (package_key, object_id, short_name) ); -- For callbacks on workflow create table workflow_callbacks ( workflow_id integer - constraint workflow_callbacks_wid_nn + constraint wf_cbks_wid_nn not null - constraint workflow_callbacks_wid_fk + constraint wf_cbks_wid_fk references workflows(workflow_id) on delete cascade, acs_sc_impl_id integer - constraint workflow_callbacks_sci_nn + constraint wf_cbks_sci_nn not null - constraint workflow_callbacks_sci_fk + constraint wf_cbks_sci_fk references acs_sc_impls(impl_id) on delete cascade, sort_order integer - constraint workflow_callbacks_so_nn + constraint wf_cbks_so_nn not null, - constraint workflow_callbacks_pk + constraint wf_cbks_pk primary key (workflow_id, acs_sc_impl_id) ); create table workflow_roles ( role_id integer - constraint workflow_roles_pk + constraint wf_roles_pk primary key, workflow_id integer - constraint workflow_roles_workflow_id_fk + constraint wf_roles_workflow_id_fk references workflows(workflow_id) on delete cascade, short_name varchar(100) - constraint workflow_roles_short_name_nn + constraint wf_roles_short_name_nn not null, pretty_name varchar(200) - constraint workflow_roles_pretty_name_nn + constraint wf_roles_pretty_name_nn + not null, + sort_order integer + constraint wf_roles_so_nn not null ); create sequence workflow_roles_seq; --- Static role-party map -create table workflow_role_default_parties ( - role_id integer - constraint workflow_role_default_parties_rid_nn - not null - constraint workflow_role_default_parties_rid_fk - references workflow_roles(role_id) - on delete cascade, - party_id integer - constraint workflow_role_default_parties_pid_nn - not null - constraint workflow_role_default_parties_pid_fk - references parties(party_id) - on delete cascade, - constraint workflow_role_default_parties_pk - primary key (role_id, party_id) -); - --- Static map between roles and parties allowed to be in those roles -create table workflow_role_allowed_parties ( - role_id integer - constraint workflow_role_allowed_parties_rid_nn - not null - constraint workflow_role_allowed_parties_rid_fk - references workflow_roles(role_id) - on delete cascade, - party_id integer - constraint workflow_role_allowed_parties_pid_nn - not null - constraint workflow_role_allowed_parties_pid_fk - references parties(party_id) - on delete cascade, - constraint workflow_role_allowed_parties_pk - primary key (role_id, party_id) -); - -- Callbacks for roles create table workflow_role_callbacks ( role_id integer - constraint workflow_role_callbacks_role_id_nn + constraint wf_role_cbks_role_id_nn not null - constraint workflow_role_callbacks_role_id_fk + constraint wf_role_cbks_role_id_fk references workflow_roles(role_id) on delete cascade, acs_sc_impl_id integer - constraint workflow_role_callbacks_contract_id_nn + constraint wf_role_cbks_contract_id_nn not null - constraint workflow_role_callbacks_contract_id_fk + constraint wf_role_cbks_contract_id_fk references acs_sc_impls(impl_id) on delete cascade, -- this should be an implementation of any of the three assignment -- service contracts: DefaultAssignee, AssigneePickList, or -- AssigneeSubQuery sort_order integer - constraint workflow_role_callbacks_sort_order_nn + constraint wf_role_cbks_sort_order_nn not null, - constraint workflow_role_callbacks_pk + constraint wf_role_cbks_pk primary key (role_id, acs_sc_impl_id), - constraint workflow_role_asgn_rol_sort_un + constraint wf_role_asgn_rol_sort_un unique (role_id, sort_order) ); create table workflow_actions ( action_id integer - constraint workflow_actions_pk + constraint wf_acns_pk primary key, workflow_id integer - constraint workflow_actions_workflow_id_nn + constraint wf_acns_workflow_id_nn not null - constraint workflow_actions_workflow_id_fk + constraint wf_acns_workflow_id_fk references workflows(workflow_id) on delete cascade, sort_order integer - constraint workflow_actions_sort_order_nn + constraint wf_acns_sort_order_nn not null, short_name varchar(100) - constraint workflow_actions_short_name_nn + constraint wf_acns_short_name_nn not null, pretty_name varchar(200) - constraint workflow_actions_pretty_name_nn + constraint wf_acns_pretty_name_nn not null, pretty_past_tense varchar(200), + edit_fields varchar(4000), assigned_role integer - constraint workflow_actions_assigned_role_fk + constraint wf_acns_assigned_role_fk references workflow_roles(role_id) on delete set null, always_enabled_p bool default 'f' @@ -208,70 +178,71 @@ -- Determines which roles are allowed to take certain actions create table workflow_action_allowed_roles ( action_id integer - constraint workflow_action_allowed_roles_action_id_nn + constraint wf_acn_alwd_roles_acn_id_nn not null - constraint workflow_action_allowed_roles_action_id_fk + constraint wf_acn_alwd_roles_acn_id_fk references workflow_actions(action_id) on delete cascade, role_id integer - constraint workflow_action_allowed_roles_role_id_nn + constraint wf_acn_alwd_roles_role_id_nn not null - constraint workflow_action_allowed_roles_role_id_fk + constraint wf_acn_alwd_roles_role_id_fk references workflow_roles(role_id) on delete cascade, - constraint workflow_action_allowed_roles_pk + constraint wf_acn_alwd_roles_pk primary key (action_id, role_id) ); -- Determines which privileges (on the object treated by a workflow case) will allow -- users to take certain actions create table workflow_action_privileges ( action_id integer - constraint workflow_action_privileges_action_id_nn + constraint wf_acn_priv_acn_id_nn not null - constraint workflow_action_privileges_action_id_fk + constraint wf_acn_priv_acn_id_fk references workflow_actions(action_id) on delete cascade, privilege varchar(100) - constraint workflow_action_privileges_privilege_nn + constraint wf_acn_priv_privilege_nn not null - constraint workflow_action_privileges_privilege_fk + constraint wf_acn_priv_privilege_fk references acs_privileges(privilege) on delete cascade, - constraint workflow_action_privileges_pk + constraint wf_acn_priv_pk primary key (action_id, privilege) ); -- For callbacks on actions create table workflow_action_callbacks ( action_id integer - constraint workflow_action_callbacks_action_id_nn + constraint wf_acn_cbks_acn_id_nn not null - constraint workflow_action_callbacks_action_id_fk + constraint wf_acn_cbks_acn_id_fk references workflow_actions(action_id) on delete cascade, acs_sc_impl_id integer - constraint workflow_action_callbacks_sci_nn + constraint wf_acn_cbks_sci_nn not null - constraint workflow_action_callbacks_sci_fk + constraint wf_acn_cbks_sci_fk references acs_sc_impls(impl_id) on delete cascade, sort_order integer - constraint workflow_action_callbacks_sort_order_nn + constraint wf_acn_cbks_sort_order_nn not null, - constraint workflow_action_callbacks_pk + constraint wf_acn_cbks_pk primary key (action_id, acs_sc_impl_id) ); +-- For the initial action, which fires when a new case is started create table workflow_initial_action ( workflow_id integer - constraint workflow_initial_action_pk + constraint wf_initial_acn_pk primary key - constraint workflow_initial_action_wf_fk + constraint wf_initial_acn_wf_fk references workflows(workflow_id) on delete cascade, action_id integer - constraint workflow_initial_action_act_fk + constraint wf_initial_acn_act_fk references workflow_actions(action_id) on delete cascade ); @@ -283,59 +254,106 @@ create table workflow_fsm_states ( state_id integer - constraint workflow_fsm_states_pk + constraint wf_fsm_states_pk primary key, workflow_id integer - constraint workflow_fsm_states_workflow_id_nn + constraint wf_fsm_states_workflow_id_nn not null - constraint workflow_fsm_states_workflow_id_fk + constraint wf_fsm_states_workflow_id_fk references workflows(workflow_id) on delete cascade, sort_order integer - constraint workflow_fsm_states_sort_order_nn + constraint wf_fsm_states_sort_order_nn not null, -- The state with the lowest sort order is the initial state short_name varchar(100) - constraint workflow_fsm_states_short_name_nn + constraint wf_fsm_states_short_name_nn not null, pretty_name varchar(200) - constraint workflow_fsm_states_pretty_name_nn - not null + constraint wf_fsm_states_pretty_name_nn + not null, + hide_fields varchar(4000) ); create sequence workflow_fsm_states_seq; create table workflow_fsm_actions ( action_id integer - constraint workflow_fsm_actions_aid_fk + constraint wf_fsm_acns_aid_fk references workflow_actions(action_id) on delete cascade - constraint workflow_fsm_actions_pk + constraint wf_fsm_acns_pk primary key, new_state integer - constraint workflow_fsm_actions_new_state_fk + constraint wf_fsm_acns_new_st_fk references workflow_fsm_states(state_id) on delete cascade -- can be null ); -- If an action is enabled in all states it won't have any entries in this table -- it is enabled in all states -create table workflow_fsm_action_enabled_in_states ( +create table workflow_fsm_action_en_in_st ( action_id integer - constraint workflow_fsm_action_enabled_in_states_action_id_nn + constraint wf_fsm_acn_enb_in_st_acn_id_nn not null - constraint workflow_fsm_action_enabled_in_states_action_id_fk + constraint wf_fsm_acn_enb_in_st_acn_id_fk references workflow_fsm_actions(action_id) on delete cascade, state_id integer - constraint workflow_fsm_action_enabled_in_states_state_id_nn + constraint wf_fsm_acn_enb_in_st_st_id_nn not null - constraint workflow_fsm_action_enabled_in_states_state_id_fk + constraint wf_fsm_acn_enb_in_st_st_id_fk references workflow_fsm_states on delete cascade ); + + +-------------------------------------------------------- +-- Workflow level, context-dependent (assignments, etc.) +-------------------------------------------------------- + + +-- Static role-party map +create table workflow_role_default_parties ( + role_id integer + constraint wf_role_default_parties_rid_nn + not null + constraint wf_role_default_parties_rid_fk + references workflow_roles(role_id) + on delete cascade, + party_id integer + constraint wf_role_default_parties_pid_nn + not null + constraint wf_role_default_parties_pid_fk + references parties(party_id) + on delete cascade, + constraint wf_role_default_parties_pk + primary key (role_id, party_id) +); + +-- Static map between roles and parties allowed to be in those roles +create table workflow_role_allowed_parties ( + role_id integer + constraint wf_role_alwd_parties_rid_nn + not null + constraint wf_role_alwd_parties_rid_fk + references workflow_roles(role_id) + on delete cascade, + party_id integer + constraint wf_role_alwd_parties_pid_nn + not null + constraint wf_role_alwd_parties_pid_fk + references parties(party_id) + on delete cascade, + constraint wf_role_alwd_parties_pk + primary key (role_id, party_id) +); + + + + --------------------------------- -- Case level, Generic Model --------------------------------- @@ -344,86 +362,45 @@ create table workflow_cases ( case_id integer - constraint workflow_cases_pk + constraint wf_cases_pk primary key, workflow_id integer - constraint workflow_cases_workflow_id_nn + constraint wf_cases_workflow_id_nn not null - constraint workflow_cases_workflow_id_fk + constraint wf_cases_workflow_id_fk references workflows(workflow_id) on delete cascade, object_id integer - constraint workflow_cases_object_id_nn + constraint wf_cases_object_id_nn not null - constraint workflow_cases_object_id_fk + constraint wf_cases_object_id_fk references acs_objects(object_id) on delete cascade - constraint workflow_cases_object_id_un + constraint wf_cases_object_id_un unique -- the object which this case is about, e.g. the acs-object for a bug-tracker bug ); -create sequence workflow_case_log_seq; - -create table workflow_case_log ( - entry_id integer - constraint workflow_case_log_pk - primary key, - case_id integer - constraint workflow_case_log_case_id_fk - references workflow_cases(case_id) - on delete cascade, - action_id integer - constraint workflow_case_log_action_id_fk - references workflow_actions(action_id) - on delete cascade, - user_id integer - constraint workflow_case_log_user_id_fk - references users(user_id) - on delete cascade, - action_date timestamp - constraint workflow_case_log_action_date_nn - not null - default now(), - comment text, - comment_format varchar(30) default 'plain' not null - constraint bt_bug_actions_comment_format_ck - check (comment_format in ('html', 'plain', 'pre')) -); - -create table workflow_case_log_data ( - entry_id integer - constraint workflow_case_log_data_eid_nn - not null - constraint workflow_case_log_data_eid_fk - references workflow_case_log(entry_id) - on delete cascade, - key varchar(50), - value varchar(4000), - constraint workflow_case_log_data_pk - primary key (entry_id, key) -); - create table workflow_case_role_party_map ( case_id integer - constraint workflow_case_role_party_map_case_id_nn + constraint wf_case_role_pty_map_case_id_nn not null - constraint workflow_case_role_party_map_case_id_fk + constraint wf_case_role_pty_map_case_id_fk references workflow_cases(case_id) on delete cascade, role_id integer - constraint workflow_case_role_party_map_case_id_nn + constraint wf_case_role_pty_map_case_id_nn not null - constraint workflow_case_role_party_map_case_id_fk + constraint wf_case_role_pty_map_case_id_fk references workflow_roles(role_id) on delete cascade, party_id integer - constraint workflow_case_role_party_map_party_id_nn + constraint wf_case_role_pty_map_pty_id_nn not null - constraint workflow_case_role_party_map_party_id_fk + constraint wf_case_role_pty_map_pty_id_fk references parties(party_id) on delete cascade, - constraint workflow_case_role_party_map_pk + constraint wf_case_role_pty_map_pk primary key (case_id, role_id, party_id) ); @@ -433,13 +410,66 @@ create table workflow_case_fsm ( case_id integer - constraint workflow_case_fsm_case_id_nn + constraint wf_case_fsm_case_id_nn not null - constraint workflow_case_fsm_case_id_fk + constraint wf_case_fsm_case_id_fk references workflow_cases(case_id) on delete cascade, current_state integer - constraint workflow_case_fsm_state_id_fk + constraint wf_case_fsm_st_id_fk references workflow_fsm_states(state_id) on delete cascade ); + + +--------------------------------- +-- Case level, Activity Log +--------------------------------- + +begin; + select content_type__create_type ( + 'workflow_activity_log', -- content_type + 'content_revision', -- supertype + 'Workflow Activity Log', -- pretty_name + 'Workflow Activity Log', -- pretty_plural + 'workflow_case_log', -- table_name + 'entry_id', -- id_column + ); +end; + + +create sequence workflow_case_log_seq; + +create table workflow_case_log ( + entry_id integer + constraint wf_case_log_eid_fk + references cr_revisions on delete cascade + constraint wf_case_log_pk + primary key, + case_id integer + constraint wf_case_log_case_id_fk + references workflow_cases(case_id) + on delete cascade, + action_id integer + constraint wf_case_log_acn_id_fk + references workflow_actions(action_id) + on delete cascade, + comment_format varchar(50) + default 'text/plain' + constraint wf_clog_comment_format_nn + not null +); + +create table workflow_case_log_data ( + entry_id integer + constraint wf_case_log_data_eid_nn + not null + constraint wf_case_log_data_eid_fk + references workflow_case_log(entry_id) + on delete cascade, + key varchar(50), + value varchar(4000), + constraint wf_case_log_data_pk + primary key (entry_id, key) +); + Index: openacs-4/packages/workflow/sql/postgresql/workflow-tables-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/sql/postgresql/workflow-tables-drop.sql,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/sql/postgresql/workflow-tables-drop.sql 21 Jan 2003 18:05:48 -0000 1.3 +++ openacs-4/packages/workflow/sql/postgresql/workflow-tables-drop.sql 3 Feb 2003 12:22:47 -0000 1.4 @@ -44,7 +44,7 @@ drop table workflow_case_log_data; drop table workflow_case_log; drop table workflow_cases; -drop table workflow_fsm_action_enabled_in_states; +drop table workflow_fsm_action_en_in_st; drop table workflow_fsm_actions; drop table workflow_initial_action; drop table workflow_fsm_states; 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 -N -r1.4 -r1.5 --- openacs-4/packages/workflow/tcl/action-procs.tcl 21 Jan 2003 18:06:00 -0000 1.4 +++ openacs-4/packages/workflow/tcl/action-procs.tcl 3 Feb 2003 12:23:01 -0000 1.5 @@ -25,22 +25,28 @@ {-short_name:required} {-pretty_name:required} {-pretty_past_tense {}} + {-edit_fields {}} {-assigned_role {}} {-allowed_roles {}} {-privileges {}} {-callbacks {}} {-always_enabled_p f} - {-initial_action:boolean} + {-initial_action_p f} } { This procedure is normally not invoked from application code. Instead a procedure for a certain workflow implementation, such as for example workflow::fsm::action::new (for Finite State Machine workflows), is used. @param workflow_id The id of the FSM workflow to add the action to + @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. @@ -53,7 +59,7 @@ action. @param callbacks List of names of service contract implementations of callbacks for the action in impl_owner_name.impl_name format. - @param initial_action Use this switch to indicate that this is the initial + @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 @@ -71,6 +77,11 @@ set sort_order [workflow::default_sort_order \ -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 {} + } } set action_id [db_nextval "workflow_actions_seq"] @@ -81,6 +92,9 @@ set assigned_role_id [workflow::role::get_id \ -workflow_id $workflow_id \ -short_name $assigned_role] + if { [empty_string_p $assigned_role_id] } { + error "Cannot find role '$assigned_role' to be the assigned role for action '$short_name'" + } } # Insert the action @@ -97,7 +111,7 @@ } # Record if this is an initial action - if { $initial_action_p } { + if { [string equal $initial_action_p "t"] } { db_dml insert_initial_action {} } @@ -135,12 +149,20 @@ ad_proc -public workflow::action::get_privileges { {-action_id:required} + {-no_admin:boolean} } { 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 [db_list select_privileges {}] + set privileges [db_list select_privileges {}] + + # Admins always have privilege + if { !$no_admin_p } { + lappend privileges "admin" + } + + return $privileges } ad_proc -public workflow::action::get_id { @@ -163,6 +185,7 @@ Return information about an action with a given id. @author Peter Marklund + @author Lars Pind (lars@collaboraid.biz) @return An array list with workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, assigned_role, and always_enabled_p column @@ -172,8 +195,29 @@ upvar $array row db_1row action_info {} -column_array row + + set row(callbacks) [db_list action_callbacks {}] + set row(allowed_roles) [db_list action_allowed_roles {}] + set row(privileges) [get_privileges -action_id $action_id -no_admin] + } +ad_proc -public workflow::action::get_element { + {-action_id:required} + {-element:required} +} { + Return a single element from the information about a action. + + @param action_id The ID of the action + @param element The element you want + @return The element you asked for + + @author Lars Pind (lars@collaboraid.biz) +} { + get -action_id $action_id -array row + return $row($element) +} + ad_proc -public workflow::action::callback_insert { {-action_id:required} {-name:required} @@ -188,11 +232,6 @@ @author Lars Pind (lars@collaboraid.biz) } { - # TODO: - # Insert for real when the service contracts have been defined - - ns_log Error "LARS: workflow::action::callback_insert -- would have inserted the callback $name to action $action_id" - return db_transaction { @@ -222,28 +261,25 @@ ad_proc -public workflow::action::fsm::new { {-workflow_id:required} + {-sort_order {}} {-short_name:required} {-pretty_name:required} {-pretty_past_tense {}} + {-edit_fields {}} {-allowed_roles {}} {-assigned_role {}} {-privileges {}} {-enabled_states {}} {-new_state {}} {-callbacks {}} {-always_enabled_p f} - {-initial_action:boolean} + {-initial_action_p f} } { 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. - @param enabled_states The short names of states in which the - action is enabled. - @param new_state The name of the state that a workflow case moves to - when the action is taken. Optional. - @see workflow::action::new @author Peter Marklund @@ -252,11 +288,13 @@ db_transaction { # Generic workflow data: set action_id [workflow::action::new \ - -initial_action=$initial_action_p \ + -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 \ @@ -295,7 +333,50 @@ return [db_string select_new_state {} -default {}] } +ad_proc -public workflow::action::fsm::get { + {-action_id:required} + {-array:required} +} { + Return information about an action with a given id, including + FSM-related info such as 'enabled_states', and 'new_state'. + @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 + + # Get new_state + db_0or1row action_fsm_info {} -column_array additional_row + array set row [array get additional_row] + + # Get enabled_states + set row(enabled_states) [db_list action_enabled_short_name {}] + +} + +ad_proc -public workflow::action::fsm::get_element { + {-action_id:required} + {-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'. + + @author Peter Marklund + @author Lars Pind (lars@collaboraid.biz) +} { + workflow::action::fsm::get -action_id $action_id -array row + return $row($element) +} + + + +##### +# Private procs +##### + ad_proc -private workflow::action::fsm::parse_spec { {-workflow_id:required} {-short_name:required} @@ -312,13 +393,15 @@ # Initialize array with default values array set action { pretty_past_tense {} + edit_fields {} allowed_roles {} assigned_role {} privileges {} always_enabled_p f enabled_states {} new_state {} - initial_action_p 0 + initial_action_p f + callbacks {} } # Get the info from the spec @@ -330,13 +413,15 @@ -short_name $short_name \ -pretty_name $action(pretty_name) \ -pretty_past_tense $action(pretty_past_tense) \ + -edit_fields $action(edit_fields) \ -allowed_roles $action(allowed_roles) \ -assigned_role $action(assigned_role) \ -privileges $action(privileges) \ -always_enabled_p $action(always_enabled_p) \ -enabled_states $action(enabled_states) \ -new_state $action(new_state) \ - -initial_action=$action(initial_action_p) \ + -callbacks $action(callbacks) \ + -initial_action_p $action(initial_action_p) ] } @@ -352,15 +437,76 @@ @author Lars Pind (lars@collaboraid.biz) } { - # actions(short_name) { ... action-spec ... } - array set actions $spec - - foreach short_name [array names actions] { + foreach { short_name subspec } $spec { workflow::action::fsm::parse_spec \ -workflow_id $workflow_id \ -short_name $short_name \ - -spec $actions($short_name) + -spec $subspec } } +ad_proc -private workflow::action::fsm::generate_spec { + {-action_id:required} +} { + Generate the spec for an individual action definition. + @param action_id The id of the action to generate spec for. + @return spec The actions spec + + @author Lars Pind (lars@collaboraid.biz) +} { + get -action_id $action_id -array row + + set row(assigned_role) $row(assigned_role_short_name) + + # 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_short_name + + # Get rid of a few defaults + array set defaults { initial_action_p f always_enabled_p f } + + foreach name [array names defaults] { + if { [string equal $row($name) $defaults($name)] } { + array unset row $name + } + } + + # Get rid of empty strings + foreach name [array names row] { + if { [empty_string_p $row($name)] } { + array unset row $name + } + } + + set spec {} + foreach name [lsort [array names row]] { + lappend spec $name $row($name) + } + + return $spec +} + +ad_proc -private workflow::action::fsm::generate_actions_spec { + {-workflow_id:required} +} { + Generate the spec for the block containing the definition of all + actions for the workflow. + + @param workflow_id The id of the workflow to delete. + @return The actions spec + + @author Lars Pind (lars@collaboraid.biz) +} { + # actions(short_name) { ... action-spec ... } + set actions [list] + + foreach action_id [workflow::get_actions -workflow_id $workflow_id] { + lappend actions [get_element -action_id $action_id -element short_name] [generate_spec -action_id $action_id] + } + + return $actions +} 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 -N -r1.4 -r1.5 --- openacs-4/packages/workflow/tcl/action-procs.xql 21 Jan 2003 18:06:00 -0000 1.4 +++ openacs-4/packages/workflow/tcl/action-procs.xql 3 Feb 2003 12:23:01 -0000 1.5 @@ -1,6 +1,24 @@ + + + select count(*) + from workflow_actions + where workflow_id = :workflow_id + and sort_order = :sort_order + + + + + + update workflow_actions + set sort_order = sort_order + 1 + where workflow_id = :workflow_id + and sort_order >= :sort_order + + + insert into workflow_action_allowed_roles @@ -24,9 +42,9 @@ insert into workflow_actions (action_id, workflow_id, sort_order, short_name, pretty_name, pretty_past_tense, - assigned_role, always_enabled_p) + edit_fields, assigned_role, always_enabled_p) values (:action_id, :workflow_id, :sort_order, :short_name, :pretty_name, :pretty_past_tense, - :assigned_role_id, :always_enabled_p) + :edit_fields, :assigned_role_id, :always_enabled_p) @@ -73,21 +91,50 @@ - select workflow_id, - sort_order, - short_name, - pretty_name, - pretty_past_tense, - assigned_role, - always_enabled_p - from workflow_actions - where action_id = :action_id + select a.action_id, + a.workflow_id, + a.sort_order, + a.short_name, + a.pretty_name, + a.pretty_past_tense, + a.edit_fields, + a.assigned_role, + (select short_name from workflow_roles where role_id = a.assigned_role) as assigned_role_short_name, + a.always_enabled_p, + (select case when count(*) = 1 then 't' else 'f' end + from workflow_initial_action + where workflow_id = a.workflow_id + and action_id = a.action_id + ) as initial_action_p + from workflow_actions a + where a.action_id = :action_id + + + select impl.impl_owner_name || '.' || impl.impl_name + from acs_sc_impls impl, + workflow_action_callbacks c + where c.action_id = :action_id + and impl.impl_id = c.acs_sc_impl_id + order by c.sort_order + + + + + + select r.short_name + from workflow_roles r, + workflow_action_allowed_roles aar + where aar.action_id = :action_id + and r.role_id = aar.role_id + + + - select coalesce(max(sort_order)) + 1 + select coalesce(max(sort_order),0) + 1 from workflow_action_callbacks where action_id = :action_id @@ -113,7 +160,7 @@ - insert into workflow_fsm_action_enabled_in_states + insert into workflow_fsm_action_en_in_st (action_id, state_id) values (:action_id, :enabled_state_id) @@ -127,4 +174,25 @@ + + + select a.new_state as new_state_id, + s.short_name as new_state + from workflow_fsm_actions a, + workflow_fsm_states s + where a.action_id = :action_id + and s.state_id = a.new_state + + + + + + select s.short_name + from workflow_fsm_action_en_in_st waeis, + workflow_fsm_states s + where waeis.action_id = :action_id + and s.state_id = waeis.state_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 -N -r1.3 -r1.4 --- openacs-4/packages/workflow/tcl/case-procs.tcl 21 Jan 2003 18:06:00 -0000 1.3 +++ openacs-4/packages/workflow/tcl/case-procs.tcl 3 Feb 2003 12:23:01 -0000 1.4 @@ -68,9 +68,14 @@ } db_transaction { + + ns_log Notice "LARS - case::new1: object_id = $object_id, [db_string foobar { select count(*) from acs_objects where object_id = :object_id }]" + # Insert the case set case_id [insert -workflow_id $workflow_id -object_id $object_id] + ns_log Notice "LARS - case::new2: object_id = $object_id, [db_string foobar { select count(*) from acs_objects where object_id = :object_id }]" + # Execute the initial action workflow::case::action::execute \ -case_id $case_id \ @@ -79,6 +84,9 @@ -comment_format $comment_format \ -user_id $user_id \ -no_check + + ns_log Notice "LARS - case::new3: object_id = $object_id, [db_string foobar { select count(*) from acs_objects where object_id = :object_id }]" + } return $case_id @@ -105,19 +113,47 @@ } } -ad_proc -public workflow::case::get_object_id { +ad_proc -public workflow::case::get { {-case_id:required} + {-array:required} + {-action_id {}} } { - Gets the object_id from the case. + Get information about a case - @param case_id The case_id. - @return The object_id of the case. + @param caes_id The case ID + @param array The name of an array in which information will be returned. @author Lars Pind (lars@collaboraid.biz) } { - return [db_string select_object_id {}] + # Select the info into the upvar'ed Tcl Array + upvar $array row + + workflow::case::fsm::get -case_id $case_id -array row -action_id $action_id + + # LARS TODO: + # Should we redesign the API so that it's polymorphic, wrt. to workflow type (FSM/Petri Net) + # That way, you'd call workflow::case::get and get a state_pretty pseudocolumn, which would be + # the pretty-name of the state in an FSM, but it would be some kind of human-readable summary of + # the active tokens in a petri net. } +ad_proc -public workflow::case::get_element { + {-case_id:required} + {-element:required} + {-action_id {}} +} { + Return a single element from the information about a case. + + @param case_id The ID of the case + @param element The element you want + @return The element you asked for + + @author Lars Pind (lars@collaboraid.biz) +} { + get -case_id $case_id -action_id $action_id -array row + return $row($element) +} + ad_proc -public workflow::case::get_user_roles { {-case_id:required} -user_id @@ -142,17 +178,12 @@ Get the currently enabled actions, based on the state of the case @param case_id The ID of the case. - @return A list of id:s of the actions which are currently + @return A list of id's of the actions which are currently enabled @author Lars Pind (lars@collaboraid.biz) } { - set action_list [list] - db_foreach select_enabled_actions {} { - lappend action_list $action_id - } - - return $action_list + return [db_list select_enabled_actions {}] } ad_proc -public workflow::case::get_available_actions { @@ -196,7 +227,7 @@ foreach action_id [get_enabled_actions -case_id $case_id] { set role_id [workflow::action::get_assigned_role -action_id $action_id] - if { [lsearch $role_id_list $role_id] == -1 } { + if { ![empty_string_p $role_id] && [lsearch $role_id_list $role_id] == -1 } { lappend role_id_list $role_id } } @@ -205,7 +236,9 @@ 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::set_default_assignees \ + -case_id $case_id \ + -role_id $role_id } } } @@ -231,21 +264,31 @@ @author Lars Pind (lars@collaboraid.biz) } { - set contract_name [workflow::service_contract::role_default_assignee] + set contract_name [workflow::service_contract::role_default_assignees] - set object_id [workflow::case::get_object_id -case_id $case_id] + db_transaction { - db_foreach select_callbacks {} { - - # Run the service contract - set party_id_list [acs_sc_call $contract_name "GetAssignees" [list $case_id $object_id $role_id] $impl_name] - - if { [llength $party_id_list] != 0 } { - foreach party_id $party_id_list { - assignee_insert -case_id $case_id -role_id $role_id -party_id $party_id + set object_id [workflow::case::get_element -case_id $case_id -element object_id] + + ns_log Notice "LARS - case::role::set_default_assignees: object_id = $object_id, [db_string foobar { select count(*) from acs_objects where object_id = :object_id }]" + + set impl_names [db_list select_callbacks {}] + + foreach impl_name $impl_names { + # Call the service contract implementation + set party_id_list [acs_sc::invoke \ + -contract $contract_name \ + -operation "GetAssignees" \ + -impl $impl_name \ + -call_args [list $case_id $object_id $role_id]] + + if { [llength $party_id_list] != 0 } { + foreach party_id $party_id_list { + assignee_insert -case_id $case_id -role_id $role_id -party_id $party_id + } + # We stop when the first callback returned something + break } - # We stop when the first callback returned something - break } } } @@ -274,8 +317,25 @@ } } +ad_proc -public workflow::case::get_activity_html { + -case_id:required +} { + Get the activity log for a case as an HTML chunk +} { + #LARS TODO: Template this + set log_html {} + db_foreach select_log {} { + append log_html "$action_date_pretty $action_pretty_past_tense by $user_first_names $user_last_name +
[ad_html_text_convert -from $comment_format -to "text/html" -- $comment]
" + } + + return $log_html +} + + + ##### # # workflow::case::fsm @@ -295,10 +355,36 @@ return [db_string select_current_state {}] } +ad_proc -public workflow::case::fsm::get { + {-case_id:required} + {-array:required} + {-action_id {}} +} { + Get information about an FSM case + @param caes_id The case ID + @param array The name of an array in which information will be returned. + @param action_id If you supply an action here, you'll get + the information as it'll look after executing the given action. + @author Lars Pind (lars@collaboraid.biz) +} { + # Select the info into the upvar'ed Tcl Array + upvar $array row + if { [empty_string_p $action_id] } { + db_1row select_case_info {} -column_array row + set row(entry_id) {} + } else { + db_1row select_case_info_after_action {} -column_array row + set row(entry_id) [db_nextval "workflow_case_log_seq"] + } +} + + + + ##### # # workflow::case::action @@ -324,7 +410,7 @@ set user_id [ad_conn user_id] } - set object_id [workflow::case::get_object_id -case_id $case_id] + set object_id [workflow::case::get_element -case_id $case_id -element object_id] set user_role_ids [workflow::case::get_user_roles -case_id $case_id -user_id $user_id] set permission_p 0 @@ -355,7 +441,7 @@ } } } - + return $permission_p } @@ -388,6 +474,11 @@ @author Lars Pind (lars@collaboraid.biz) } { + # Always permit the no-op + if { [empty_string_p $action_id] } { + return 1 + } + if { ![exists_and_not_null user_id] } { set user_id [ad_conn user_id] } @@ -410,6 +501,7 @@ {-comment_format:required} {-user_id} {-no_check:boolean} + {-entry_id {}} } { Execute the action @@ -422,6 +514,9 @@ @param no_check Use this switch to bypass a check of whether the action is enabled and the user is allowed to perform it. This switch should normally not be used. + @param entry_id Optional entry_id for double-click protection. + This can be gotten from workflow::case::fsm::get. + @return entry_id of the new log entry. @author Lars Pind (lars@collaboraid.biz) } { @@ -444,10 +539,27 @@ db_dml update_fsm_state {} } + # Maybe get entry_id, if one wasn't supplied + if { [empty_string_p $entry_id] } { + set entry_id [db_nextval "workflow_case_log_seq"] + } + + # Check if the row already exists + set exists_p [db_string log_entry_exists_p {}] + + if { $exists_p } { + return $entry_id + } + + # We can't have empty comment_format + if { [empty_string_p $comment_format] } { + # We need a default value here + set comment_format "text/plain" + } + # Insert activity log entry - set entry_id [db_nextval "workflow_case_log_seq"] db_dml insert_log_entry {} - + # Assign new enabled roles, if currently unassigned workflow::case::assign_roles -case_id $case_id @@ -456,7 +568,40 @@ # Notifications # ... TODO ... + + + # LARS: TODO + # Taken from bug-tracker + if 0 { + # Setup any assignee for alerts on the bug + if { [info exists row(assignee)] && ![empty_string_p $row(assignee)] } { + bug_tracker::add_instant_alert \ + -bug_id $bug_id \ + -user_id $row(assignee) + } + } + + + # LARS: TODO + # Taken from bug-tracker + if 0 { + set resolution {} + if { [exists_and_not_null row(resolution)] } { + set resolution $row(resolution) + } + + # Send out notifications + bug_tracker::bug_notify \ + -bug_id $bug_id \ + -action $action \ + -comment $description \ + -comment_format $desc_format \ + -resolution $resolution } + + } + + return $entry_id } Index: openacs-4/packages/workflow/tcl/case-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/case-procs.xql,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/tcl/case-procs.xql 21 Jan 2003 18:06:00 -0000 1.3 +++ openacs-4/packages/workflow/tcl/case-procs.xql 3 Feb 2003 12:23:01 -0000 1.4 @@ -32,14 +32,6 @@ - - - select object_id - from workflow_cases c - where c.case_id = :case_id - - - select distinct rpm.role_id @@ -65,10 +57,11 @@ and (a.always_enabled_p = 't' or exists (select 1 from workflow_case_fsm cfsm, - workflow_fsm_action_enabled_in_states waeis + 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 @@ -81,6 +74,32 @@ + + + select l.entry_id, + l.case_id, + l.action_id, + a.short_name as action_short_name, + a.pretty_name as action_pretty_name, + a.pretty_past_tense as action_pretty_past_tense, + l.user_id, + u.first_names as user_first_names, + u.last_name as user_last_name, + u.email as user_email, + l.action_date, + to_char(l.action_date, 'fmMM/DDfm/YYYY') as action_date_pretty, + l.comment, + l.comment_format + from workflow_case_log l, + workflow_actions a, + cc_users u + where l.case_id = :case_id + and a.action_id = l.action_id + and u.user_id = l.user_id + + + + select impl.impl_name @@ -124,14 +143,52 @@ + + + select c.case_id, + c.workflow_id, + c.object_id, + s.state_id, + s.short_name as state_short_name, + s.pretty_name as pretty_state, + s.hide_fields as state_hide_fields + from workflow_cases c, + workflow_case_fsm cfsm, + workflow_fsm_states s + where c.case_id = :case_id + and cfsm.case_id = c.case_id + and s.state_id = cfsm.current_state + + + + + + select c.case_id, + c.workflow_id, + c.object_id, + s.state_id, + s.short_name as state_short_name, + s.pretty_name as pretty_state, + s.hide_fields as state_hide_fields + from workflow_cases c, + workflow_case_fsm cfsm, + workflow_fsm_states s, + workflow_fsm_actions a + where c.case_id = :case_id + and cfsm.case_id = c.case_id + and a.action_id = :action_id + and ((a.new_state is null and s.state_id = cfsm.current_state) or (s.state_id = a.new_state)) + + + 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_enabled_in_states waeis, + 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 @@ -157,4 +214,17 @@ + + + select count(*) + from workflow_case_log + where entry_id = :entry_id + and case_id = :case_id + and action_id = :action_id + and user_id = :user_id + and comment = :comment + and comment_format = :comment_format + + + Index: openacs-4/packages/workflow/tcl/implementation-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/implementation-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/tcl/implementation-procs.tcl 3 Feb 2003 12:23:01 -0000 1.1 @@ -0,0 +1,92 @@ +ad_library { + Implementations of various service contracts. + + @creation-date 13 January 2003 + @author Lars Pind (lars@collaboraid.biz) + @cvs-id $Id: implementation-procs.tcl,v 1.1 2003/02/03 12:23:01 lars Exp $ +} + +namespace eval workflow::impl {} + +namespace eval workflow::impl::role_default_assignees {} +namespace eval workflow::impl::role_default_assignees::creation_user {} +namespace eval workflow::impl::role_default_assignees::static_assignees {} + +namespace eval workflow::impl::role_assignee_pick_list {} +namespace eval workflow::impl::role_assignee_pick_list::current_assignees {} + +##### +# +# Generic service contract implementation procs +# +##### + +ad_proc -public workflow::impl::acs_object {} { + Returns the static string 'acs_object'. This can be used by implementations that are valid for any object type. +} { + return "acs_object" +} + + +##### +# +# Role - Default Assignee - Creation User +# +##### + +ad_proc -public workflow::impl::role_default_assignees::creation_user::pretty_name {} { + return "Assign to the user who created this object" +} + +ad_proc -public workflow::impl::role_default_assignees::creation_user::get_assignees { + case_id + object_id + role_id +} { + Return the creation_user of the object +} { + return [db_string select_creation_user {}] +} + + + +##### +# +# Role - Default Assignee - Static Assignees +# +##### + +ad_proc -public workflow::impl::role_default_assignees::static_assignees::pretty_name {} { + return "Use static assignment" +} + +ad_proc -public workflow::impl::role_default_assignees::static_assignees::get_assignees { + case_id + object_id + role_id +} { + Return the static assignees for this role +} { + error "IMPL: [db_list select_static_assignees {}]" + return [db_list select_static_assignees {}] +} + +##### +# +# Pick list - Default assignees +# +##### + +ad_proc -public workflow::impl::role_assignee_pick_list::current_assignees::pretty_name {} { + return "Current asignees" +} + +ad_proc -public workflow::impl::role_assignee_pick_list::current_assignees::get_pick_list { + case_id + object_id + role_id +} { + Return the list of current assignees for this case and role +} { + return [db_list select_current_assignees {}] +} \ No newline at end of file Index: openacs-4/packages/workflow/tcl/implementation-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/implementation-procs.xql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/tcl/implementation-procs.xql 3 Feb 2003 12:23:01 -0000 1.1 @@ -0,0 +1,30 @@ + + + + + + select creation_user + from acs_objects + where object_id = :object_id + + + + + + select party_id + from workflow_role_default_parties + where role_id = :role_id + + + + + + + select party_id + from workflow_case_role_party_map + where role_id = :role_id + and case_id = :case_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 -N -r1.2 -r1.3 --- openacs-4/packages/workflow/tcl/install-procs.tcl 20 Jan 2003 15:45:00 -0000 1.2 +++ openacs-4/packages/workflow/tcl/install-procs.tcl 3 Feb 2003 12:23:01 -0000 1.3 @@ -10,199 +10,337 @@ namespace eval workflow::install {} -# Array-list with list of lists style -set operations { - GetObjectType { - {operation_desc "Get the object type for which this operation is valid."} - {inputspec {}} - {outputspec {object_type:string}} - {nargs 0} - {iscachable_p "t"} + +##### +# +# Install procs +# +##### + +ad_proc -private workflow::install::package_install {} { + Workflow package install proc +} { + + db_transaction { + + create_service_contracts + + register_implementations + } - GetPrettyName { - {operation_desc "Get the pretty name. May contain #...#, so should be localized."} - {inputspec {}} - {outputspec {pretty_name:string}} - {nargs 0} - {iscachable_p "t"} - } - GetAssignees { - {operation_desc "Get the assignees as a Tcl list of party_ids, of the default assignees for this case, object, role"} - {inputspec {case_id:integer,object_id:integer,role_id:integer}} - {outputspec {party_ids:[integer]}} - {nargs 3} - {iscachable_p "f"} - } } -namespace eval acs_sc::contract {} -namespace eval acs_sc::contract::define {} +ad_proc -private workflow::install::package_uninstall {} { + Workflow package uninstall proc +} { -proc acs_sc::contract::new { contract_name {operations {}} } { - puts "New Contract: $contract_name" - if { ![string equal $operations ""] } { - puts "Operations:" - namespace eval ::acs_sc::contract::define variable contract_name $contract_name - namespace eval ::acs_sc::contract::define $operations + db_transaction { + + delete_service_contracts + + unregister_implementations + } } -proc acs_sc::contract::define::operation { operation_name } { - variable contract_name - puts "Operation: ${contract_name}.${operation_name}" -} - + ##### # -# Style one: Lists of lists of lists with potentially unclear semantics +# Create service contracts # ##### ad_proc -private workflow::install::create_service_contracts {} { + Create the service contracts needed by workflow +} { - # Array-list style - -acs_sc::contract::new \ - -contract_name [workflow::service_contract::role_default_assignee] \ - -contract_desc "Service contract to get the default assignees for a role from parameters case_id, object_id and role_id" \ - -operations { + db_transaction { - GetObjectType { - operation_desc "Get the object type for which this operation is valid." - inputspec {} - outputspec {object_type:string} - nargs 0 - iscachable_p "t" - } + workflow::install::create_default_assignees_service_contract - GetPrettyName { - operation_desc "Get the pretty name. May contain #...#, so should be localized." - inputspec {} - outputspec {pretty_name:string} - nargs 0 - iscachable_p "t" + workflow::install::create_assignee_pick_list_service_contract + + workflow::install::create_assignee_subquery_service_contract + + workflow::install::create_action_side_effect_service_contract + + workflow::install::create_activity_log_format_title_service_contract + } +} - GetAssignees { - operation_desc "Get the assignees as a Tcl list of party_ids, of the default assignees for this case, object, role" - inputspec {case_id:integer,object_id:integer,role_id:integer} - outputspec {party_ids:[integer]} - nargs 3 - iscachable_p "f" + +ad_proc -private workflow::install::delete_service_contracts {} { + + db_transaction { + + acs_sc::contract::delete -name [workflow::service_contract::role_default_assignees] + + acs_sc::contract::delete -name [workflow::service_contract::role_assignee_pick_list] + + acs_sc::contract::delete -name [workflow::service_contract::role_assignee_subquery] + + acs_sc::contract::delete -name [workflow::service_contract::action_side_effect] + + acs_sc::contract::delete -name [workflow::service_contract::activity_log_format_title] + } } + +ad_proc -private workflow::install::create_default_assignees_service_contract {} { + set default_assignees_spec { + description "Get default assignees for a role in a workflow case" + operations { + GetObjectType { + description "Get the object type for which this implementation is valid." + output { object_type:string } + iscachable_p "t" + } + GetPrettyName { + description "Get the pretty name of this implementation. Will be localized, so i may contain #...#." + output { pretty_name:string } + iscachable_p "t" + } + GetAssignees { + description "Get the assignees as a Tcl list of party_ids, of the default assignees for this case, object, role" + input { + case_id:integer + object_id:integer + role_id:integer + } + output { + party_ids:integer,multiple + } + } + } + } + + acs_sc::contract::new_from_spec \ + -spec [concat [list name [workflow::service_contract::role_default_assignees]] $default_assignees_spec] } -##### -# -# Style two: Using procs in procs with global variables -# -##### +ad_proc -private workflow::install::create_assignee_pick_list_service_contract {} { -ad_proc -private workflow::install::create_service_contracts {} { + set assignee_pick_list_spec { + description "Get the most likely assignees for a role in a workflow case" + operations { + GetObjectType { + description "Get the object type for which this implementation is valid." + output { object_type:string } + iscachable_p "t" + } + GetPrettyName { + description "Get the pretty name of this implementation. Will be localized, so i may contain #...#." + output { pretty_name:string } + iscachable_p "t" + } + GetPickList { + description "Get the most likely assignees for this case, object and role, as a Tcl list of party_ids" + input { + case_id:integer + object_id:integer + role_id:integer + } + output { + party_ids:integer,multiple + } + } + } + } -acs_sc::contract::new \ - -contract_name [workflow::service_contract::role_default_assignee] \ - -contract_desc "Service contract to get the default assignees for a role from parameters case_id, object_id and role_id" \ - -operations { + acs_sc::contract::new_from_spec \ + -spec [concat [list name [workflow::service_contract::role_assignee_pick_list]] $assignee_pick_list_spec] +} - operation \ - -operation_name GetObjectType \ - -operation_desc "Get the object type for which this operation is valid." \ - -inputspec {} \ - -outputspec {object_type:string} \ - -nargs 0 \ - -iscachable_p "t" +ad_proc -private workflow::install::create_assignee_subquery_service_contract {} { + + set assignee_subquery_spec { + description "Get the name of a subquery to use when searching for users" + operations { + GetObjectType { + description "Get the object type for which this implementation is valid." + output { object_type:string } + iscachable_p "t" + } + GetPrettyName { + description "Get the pretty name of this implementation. Will be localized, so i may contain #...#." + output { pretty_name:string } + iscachable_p "t" + } + GetSubQueryName { + description "Get the Query Dispatcher query name of the query which will return the list of parties who can be assigned to the role, and optionally bind variables to be filled in. Names of bind variables cannot start with an underscore (_)." + input { + case_id:integer + object_id:integer + role_id:integer + } + output { + subquery_name:string + bind:string,multiple + } + } + } + } - operation \ - -operation_name GetPrettyName \ - -operation_desc "Get the pretty name. May contain #...#, so should be localized." \ - -inputspec {} \ - -outputspec {pretty_name:string} \ - -nargs 0 \ - -iscachable_p "t" + acs_sc::contract::new_from_spec \ + -spec [concat [list name [workflow::service_contract::role_assignee_subquery]] $assignee_subquery_spec] +} - operation \ - -operation_name GetAssignees \ - -operation_desc "Get the assignees as a Tcl list of party_ids, of the default assignees for this case, object, role" \ - -inputspec {case_id:integer,object_id:integer,role_id:integer} \ - -outputspec {party_ids:[integer]} \ - -nargs 3 \ - -iscachable_p "f" +ad_proc -private workflow::install::create_action_side_effect_service_contract {} { + + set side_effect_spec { + description "Get the name of the side effect to create action" + operations { + GetObjectType { + description "Get the object type for which this implementation is valid." + output { object_type:string } + iscachable_p "t" + } + GetPrettyName { + description "Get the pretty name of this implementation. Will be localized, so it may contain #...#." + output { object_type:string } + iscachable_p "t" + } + DoSideEffect { + description "Do the side effect" + input { + case_id:integer + object_id:integer + action_id:integer + entry_id:integer + } + } + } + } + + acs_sc::contract::new_from_spec \ + -spec [concat [list name [workflow::service_contract::action_side_effect]] $side_effect_spec] + } +ad_proc -private workflow::install::create_activity_log_format_title_service_contract {} { + + set format_title_spec { + description "Create the title format for activity log" + operations { + GetObjectType { + description "Get the object type for which this implementation is valid." + output { + object_type:string + } + iscachable_p "t" + } + GetPrettyName { + description "Get the pretty name of this implementation. Will be localized, so it may contain #...#." + output { object_type:string } + iscachable_p "t" + } + GetTitle { + description "Get the title name of this implementation." + input { + entry_id:integer + } + output { + title:string + } + iscachable_p "t" + } + } + } + + acs_sc::contract::new_from_spec \ + -spec [concat [list name [workflow::service_contract::activity_log_format_title]] $format_title_spec] } +##### # -# Corresponding Service Contract Definition API +# Register implementations # +##### -ad_proc -public acs_sc::contract::new { - {-contract_name:required} - {-contract_dec {}} - {-oprations} -} { - insert -contract_name $contract_name -contract_desc $contract_desc +ad_proc -private workflow::install::register_implementations {} { + Register service contract implementations +} { - if { [exists_and_not_null operations] } { + db_transaction { - namespace eval ::acs_sc::contract::define variable contract_name $contract_name + workflow::install::register_default_assignees_creation_user_impl - namespace eval ::acs_sc::contract::define $operations + workflow::install::register_default_assignees_static_assignee_impl + + workflow::install::register_pick_list_current_assignee_impl } } -ad_proc -public acs_sc::contract::define::operation { - {-operation_name:required} - {-operation_desc {}} - {-inputspec {}} - {-outputspec {}} - {-nargs 0} - {-iscachable_p "f"} +ad_proc -private workflow::install::unregister_implementations {} { + Unregister service contract implementations } { - variable contract_name - set inputtype "${contract_name}.${operatoin_name}.InputType" + db_transaction { - acs_sc::msg_type::insert \ - -msg_type_name $inputtype \ - -msg_type_spec $inputspec - - set outputtype "${contract_name}.${operation_name}.OutputType" + acs_sc::impl::delete \ + -contract_name [workflow::service_contract::role_default_assignees] \ + -impl_name "Role_DefaultAssignees_CreationUser" - acs_sc::msg_type::insert \ - -msg_type_name $outputtype \ - -msg_type_spec $outputspec - - acs_sc::operation::insert \ - -contract_name $contract_name \ - -operation_desc $operation_desc \ - -inputtype $inputtype \ - -outputtype $outputtype \ - -nargs $nargs \ - -iscachable_p $iscachable_p + acs_sc::impl::delete \ + -contract_name [workflow::service_contract::role_default_assignees] \ + -impl_name "Role_DefaultAssignees_StaticAssignees" + + acs_sc::impl::delete \ + -contract_name [workflow::service_contract::role_assignee_pick_list] \ + -impl_name "Role_PickList_CurrentAssignees" + } } +ad_proc -private workflow::install::register_default_assignees_creation_user_impl {} { + set spec { + name "Role_DefaultAssignees_CreationUser" + aliases { + GetObjectType workflow::impl::acs_object + GetPrettyName workflow::impl::role_default_assignees::creation_user::pretty_name + GetAssignees workflow::impl::role_default_assignees::creation_user::get_assignees + } + } + + lappend spec contract_name [workflow::service_contract::role_default_assignees] + lappend spec owner [workflow::package_key] + + acs_sc::impl::new_from_spec -spec $spec +} +ad_proc -private workflow::install::register_default_assignees_static_assignee_impl {} { -############################################################# + set spec { + name "Role_DefaultAssignees_StaticAssignees" + aliases { + GetObjectType workflow::impl::acs_object + GetPrettyName workflow::impl::role_default_assignees::static_assignees::pretty_name + GetAssignees workflow::impl::role_default_assignees::static_assignees::get_assignees + } + } + + lappend spec contract_name [workflow::service_contract::role_default_assignees] + lappend spec owner [workflow::package_key] + + acs_sc::impl::new_from_spec -spec $spec +} +ad_proc -private workflow::install::register_pick_list_current_assignee_impl {} { -proc create_service_contracts {} { - acs_sc::contract::new DefaultAssignees { - operation GetObjectType - operation GetPrettyName - operation GetAssignees + set spec { + name "Role_PickList_CurrentAssignees" + aliases { + GetObjectType workflow::impl::acs_object + GetPrettyName workflow::impl::role_assignee_pick_list::pretty_name + GetPickList workflow::impl::role_assignee_pick_list::get_pick_list + } } - acs_sc::contract::new SideEffect { - operation GetObjectType - operation GetPrettyName - operation DoSideEffect - } -} -#create_service_contracts + lappend spec contract_name [workflow::service_contract::role_assignee_pick_list] + lappend spec owner [workflow::package_key] + acs_sc::impl::new_from_spec -spec $spec +} \ No newline at end of file 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 -N -r1.4 -r1.5 --- openacs-4/packages/workflow/tcl/role-procs.tcl 21 Jan 2003 18:06:00 -0000 1.4 +++ openacs-4/packages/workflow/tcl/role-procs.tcl 3 Feb 2003 12:23:01 -0000 1.5 @@ -22,20 +22,37 @@ {-workflow_id:required} {-short_name:required} {-pretty_name:required} + {-sort_order {}} } { Inserts the DB row for a new role. You shouldn't normally be usin this procedure, use workflow::role::new instead. @param workflow_id The ID of the workflow the new role belongs to @param short_name The short_name of the new role @param pretty_name The pretty name of the new role + @param sort_order The number which this role should be in the sort ordering sequence. + Leave blank to add role at the end. If you provide a sort_order number + which already exists, existing roles are pushed down one number. @return The ID of the new role @author Lars Pind (lars@collaboraid.biz) @see workflow::role::new } { db_transaction { + + if { [empty_string_p $sort_order] } { + set sort_order [workflow::default_sort_order \ + -workflow_id $workflow_id \ + -table_name "workflow_roles"] + } else { + set sort_order_taken_p [db_string select_sort_order_p {}] + if { $sort_order_taken_p } { + db_dml update_sort_order {} + } + } + set role_id [db_nextval "workflow_roles_seq"] + db_dml do_insert {} } return $role_id @@ -45,6 +62,7 @@ {-workflow_id:required} {-short_name:required} {-pretty_name:required} + {-sort_order {}} {-callbacks {}} } { Creates a new role for a workflow. @@ -63,7 +81,8 @@ set role_id [insert \ -workflow_id $workflow_id \ -short_name $short_name \ - -pretty_name $pretty_name\ + -pretty_name $pretty_name \ + -sort_order $sort_order \ ] # Set up the assignment rules @@ -85,18 +104,53 @@ @param workflow_id The ID of the workflow @param short_name The short_name of the role @return role_id of the desired role, or the empty string if it can't be found. + + @author Lars Pind (lars@collaboraid.biz) } { return [db_string select_role_id {} -default {}] } +ad_proc -public workflow::role::get { + {-role_id:required} + {-array:required} +} { + Return information about a role in an array. + + @param role_id The ID of the workflow + @param array Name of the array you want the info returned in + + @author Lars Pind (lars@collaboraid.biz) +} { + upvar $array row + + db_1row role_info {} -column_array row + + set row(callbacks) [db_list role_callbacks {}] +} + +ad_proc -public workflow::role::get_element { + {-role_id:required} + {-element:required} +} { + Return a single element from the information about a role. + + @param role_id The ID of the workflow + @return element The element you asked for + + @author Lars Pind (lars@collaboraid.biz) +} { + get -role_id $role_id -array row + return $row($element) +} + ad_proc -private workflow::role::parse_spec { {-workflow_id:required} {-short_name:required} {-spec:required} } { Parse the spec for an individual role definition. - @param workflow_id The id of the workflow to delete. + @param workflow_id The id of the workflow the role should be added to. @param short_name The short_name of the role @param spec The roles spec @@ -129,17 +183,68 @@ @author Lars Pind (lars@collaboraid.biz) } { - # roles(short_name) { ... role-spec ... } - array set roles $spec - - foreach short_name [array names roles] { + foreach { short_name spec } $spec { workflow::role::parse_spec \ -workflow_id $workflow_id \ -short_name $short_name \ - -spec $roles($short_name) + -spec $spec } } +ad_proc -private workflow::role::generate_spec { + {-role_id:required} +} { + Generate the spec for an individual role definition. + + @param role_id The id of the role to generate spec for. + @return spec The roles spec + + @author Lars Pind (lars@collaboraid.biz) +} { + get -role_id $role_id -array row + + # Get rid of elements that shouldn't go into the spec + array unset row short_name + array unset row role_id + array unset row workflow_id + array unset row sort_order + + # Get rid of empty strings + foreach name [array names row] { + if { [empty_string_p $row($name)] } { + array unset row $name + } + } + + set spec {} + foreach name [lsort [array names row]] { + lappend spec $name $row($name) + } + + return $spec +} + +ad_proc -private workflow::role::generate_roles_spec { + {-workflow_id:required} +} { + Generate the spec for the block containing the definition of all + roles for the workflow. + + @param workflow_id The id of the workflow to delete. + @return The roles spec + + @author Lars Pind (lars@collaboraid.biz) +} { + # roles(short_name) { ... role-spec ... } + set roles [list] + + foreach role_id [workflow::get_roles -workflow_id $workflow_id] { + lappend roles [get_element -role_id $role_id -element short_name] [generate_spec -role_id $role_id] + } + + return $roles +} + ad_proc -private workflow::role::callback_insert { {-role_id:required} {-name:required} @@ -154,12 +259,6 @@ @author Lars Pind (lars@collaboraid.biz) } { - # TODO: - # Insert for real when the service contracts have been defined - - ns_log Error "LARS: workflow::role::callback_insert -- would have inserted the callback $name to role $role_id" - return - db_transaction { # Get the impl_id @@ -171,7 +270,8 @@ } # Insert the rule - db_dml insert_rule {} + db_dml insert_callback {} } + return $acs_sc_impl_id } Index: openacs-4/packages/workflow/tcl/role-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/role-procs.xql,v diff -u -N -r1.4 -r1.5 --- openacs-4/packages/workflow/tcl/role-procs.xql 21 Jan 2003 18:06:00 -0000 1.4 +++ openacs-4/packages/workflow/tcl/role-procs.xql 3 Feb 2003 12:23:01 -0000 1.5 @@ -1,33 +1,74 @@ + + + select count(*) + from workflow_roles + where workflow_id = :workflow_id + and sort_order = :sort_order + + + + + + update workflow_roles + set sort_order = sort_order + 1 + where workflow_id = :workflow_id + and sort_order >= :sort_order + + + insert into workflow_roles - (role_id, workflow_id, short_name, pretty_name) + (role_id, workflow_id, short_name, pretty_name, sort_order) values - (:role_id, :workflow_id, :short_name, :pretty_name) + (:role_id, :workflow_id, :short_name, :pretty_name, :sort_order) select role_id - from workflow_roles - where workflow_id = :workflow_id - and short_name = :short_name + from workflow_roles + where workflow_id = :workflow_id + and short_name = :short_name + + + select role_id, + workflow_id, + short_name, + pretty_name, + sort_order + from workflow_roles + where role_id = :role_id + + + + + + select impl.impl_owner_name || '.' || impl.impl_name + from acs_sc_impls impl, + workflow_role_callbacks c + where c.role_id = :role_id + and impl.impl_id = c.acs_sc_impl_id + order by c.sort_order + + + - select coalesce(max(sort_order)) + 1 + select coalesce(max(sort_order),0) + 1 from workflow_role_callbacks where role_id = :role_id - + insert into workflow_role_callbacks (role_id, acs_sc_impl_id, sort_order) values (:role_id, :acs_sc_impl_id, :sort_order) Index: openacs-4/packages/workflow/tcl/state-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/state-procs.tcl,v diff -u -N -r1.1 -r1.2 --- openacs-4/packages/workflow/tcl/state-procs.tcl 21 Jan 2003 18:06:00 -0000 1.1 +++ openacs-4/packages/workflow/tcl/state-procs.tcl 3 Feb 2003 12:23:01 -0000 1.2 @@ -20,13 +20,19 @@ {-workflow_id:required} {-short_name:required} {-pretty_name:required} + {-hide_fields {}} {-sort_order {}} } { Creates a new state for a certain FSM (Finite State Machine) workflow. @param workflow_id The id of the FSM workflow to add the state to @param short_name @param pretty_name + @param hide_fields A space-separated list of the names of form fields which should be + hidden when in this state, because they're irrelevant in a certain state. + @param sort_order The number which this state should be in the sort ordering sequence. + Leave blank to add state at the end. If you provide a sort_order number + which already exists, existing states are pushed down one number. @return ID of new state. @author Peter Marklund @@ -37,6 +43,11 @@ if { [empty_string_p $sort_order] } { set sort_order [workflow::default_sort_order -workflow_id $workflow_id -table_name "workflow_fsm_states"] + } else { + set sort_order_taken_p [db_string select_sort_order_p {}] + if { $sort_order_taken_p } { + db_dml update_sort_order {} + } } db_dml do_insert {} @@ -59,6 +70,21 @@ db_1row state_info {} -column_array row } +ad_proc -public workflow::state::fsm::get_element { + {-state_id:required} + {-element:required} +} { + Return a single element from the information about a state. + + @param state_id The ID of the workflow + @return The element you asked for + + @author Lars Pind (lars@collaboraid.biz) +} { + get -state_id $state_id -array row + return $row($element) +} + ad_proc -public workflow::state::fsm::get_id { {-workflow_id:required} {-short_name:required} @@ -73,6 +99,9 @@ return [db_string select_id {}] } +##### +# Private procs +##### ad_proc -private workflow::state::fsm::parse_spec { {-workflow_id:required} @@ -88,7 +117,9 @@ @author Lars Pind (lars@collaboraid.biz) } { # Initialize array with default values - array set state {} + array set state { + hide_fields {} + } # Get the info from the spec array set state $spec @@ -97,7 +128,8 @@ set state_id [workflow::state::fsm::new \ -workflow_id $workflow_id \ -short_name $short_name \ - -pretty_name $state(pretty_name) + -pretty_name $state(pretty_name) \ + -hide_fields $state(hide_fields) \ ] } @@ -113,13 +145,64 @@ @author Lars Pind (lars@collaboraid.biz) } { - # states(short_name) { ... state-spec ... } - array set states $spec - - foreach short_name [array names states] { + foreach { short_name spec } $spec { workflow::state::fsm::parse_spec \ -workflow_id $workflow_id \ -short_name $short_name \ - -spec $states($short_name) + -spec $spec } } + +ad_proc -private workflow::state::fsm::generate_spec { + {-state_id:required} +} { + Generate the spec for an individual state definition. + + @param state_id The id of the state to generate spec for. + @return spec The states spec + + @author Lars Pind (lars@collaboraid.biz) +} { + get -state_id $state_id -array row + + # Get rid of elements that shouldn't go into the spec + array unset row short_name + array unset row state_id + array unset row workflow_id + array unset row sort_order + + # Get rid of empty strings + foreach name [array names row] { + if { [empty_string_p $row($name)] } { + array unset row $name + } + } + + set spec {} + foreach name [lsort [array names row]] { + lappend spec $name $row($name) + } + + return $spec +} + +ad_proc -private workflow::state::fsm::generate_states_spec { + {-workflow_id:required} +} { + Generate the spec for the block containing the definition of all + states for the workflow. + + @param workflow_id The id of the workflow to delete. + @return The states spec + + @author Lars Pind (lars@collaboraid.biz) +} { + # states(short_name) { ... state-spec ... } + set states [list] + + foreach state_id [workflow::fsm::get_states -workflow_id $workflow_id] { + lappend states [get_element -state_id $state_id -element short_name] [generate_spec -state_id $state_id] + } + + return $states +} Index: openacs-4/packages/workflow/tcl/state-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/state-procs.xql,v diff -u -N -r1.1 -r1.2 --- openacs-4/packages/workflow/tcl/state-procs.xql 21 Jan 2003 18:06:00 -0000 1.1 +++ openacs-4/packages/workflow/tcl/state-procs.xql 3 Feb 2003 12:23:01 -0000 1.2 @@ -1,31 +1,51 @@ + + + select count(*) + from workflow_fsm_states + where workflow_id = :workflow_id + and sort_order = :sort_order + + + + + + update workflow_fsm_states + set sort_order = sort_order + 1 + where workflow_id = :workflow_id + and sort_order >= :sort_order + + + insert into workflow_fsm_states - (state_id, workflow_id, sort_order, short_name, pretty_name) - values (:state_id, :workflow_id, :sort_order, :short_name, :pretty_name) + (state_id, workflow_id, sort_order, short_name, pretty_name, hide_fields) + values (:state_id, :workflow_id, :sort_order, :short_name, :pretty_name, :hide_fields) - select workflow_id, + select state_id, + workflow_id, sort_order, short_name, - pretty_name - from workflow_fsm_states - where state_id = :state_id + pretty_name, + hide_fields + from workflow_fsm_states + where state_id = :state_id select state_id - from workflow_fsm_states - where short_name = :short_name - and workflow_id = :workflow_id + from workflow_fsm_states + where short_name = :short_name + and workflow_id = :workflow_id 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 -N -r1.4 -r1.5 --- openacs-4/packages/workflow/tcl/workflow-procs.tcl 21 Jan 2003 18:06:00 -0000 1.4 +++ openacs-4/packages/workflow/tcl/workflow-procs.tcl 3 Feb 2003 12:23:01 -0000 1.5 @@ -24,7 +24,7 @@ ad_proc -public workflow::new { {-short_name:required} {-pretty_name:required} - {-package_key {}} + {-package_key:required} {-object_id {}} {-object_type "acs_object"} {-callbacks {}} @@ -62,10 +62,6 @@ db_transaction { - if { [empty_string_p $package_key] } { - set package_key [db_null] - } - if { [empty_string_p $object_id] } { set object_id [db_null] } @@ -89,27 +85,6 @@ return $workflow_id } -ad_proc -private workflow::fsm::parse_spec { - {-workflow_id:required} - {-spec:required} -} { - Parse the -workflow argument to workflow::new and create roles, - states, actions, etc., as appropriate - - @param workflow_id The id of the workflow to delete. - - @author Lars Pind (lars@collaboraid.biz) - @see workflow::new -} { - array set workflow { roles {} states {} actions {} } - array set workflow $spec - - workflow::roles::parse_spec $workflow(roles) - workflow::roles::parse_spec $workflow(roles) -} - - - ad_proc -public workflow::delete { {-workflow_id:required} } { @@ -161,6 +136,42 @@ } } +ad_proc -public workflow::get { + {-workflow_id:required} + {-array:required} +} { + Return information about a workflow. + + @author Lars Pind (lars@collaboraid.biz) + + @param workflow_id ID of workflow + @param array name of array in which the info will be returned + @return An array list with info +} { + # Select the info into the upvar'ed Tcl Array + upvar $array row + + db_1row workflow_info {} -column_array row + + set row(callbacks) [db_list workflow_callbacks {}] +} + + +ad_proc -public workflow::get_element { + {-workflow_id:required} + {-element:required} +} { + Return a single element from the information about a workflow. + + @param workflow_id The ID of the workflow + @return The element you asked for + + @author Lars Pind (lars@collaboraid.biz) +} { + get -workflow_id $workflow_id -array row + return $row($element) +} + ad_proc -public workflow::get_initial_action { {-workflow_id:required} } { @@ -174,6 +185,40 @@ return [db_string select_initial_action {}] } +ad_proc -public workflow::get_roles { + {-workflow_id:required} +} { + Get the role_id's of all the roles in the workflow. + + @param workflow_id The ID of the workflow + @return list of role_id's. + + @author Lars Pind (lars@collaboraid.biz) +} { + return [db_list select_role_ids {}] +} + +ad_proc -public workflow::get_actions { + {-workflow_id:required} +} { + 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 [db_list select_action_ids {}] +} + + + + + +##### +# Private procs +##### + ad_proc -private workflow::default_sort_order { {-workflow_id:required} {-table_name:required} @@ -203,12 +248,6 @@ @author Lars Pind (lars@collaboraid.biz) } { - # TODO: - # Insert for real when the service contracts have been defined - - ns_log Error "LARS: workflow::callback_insert -- would have inserted the callback $name to workflow $workflow_id" - return - db_transaction { # Get the impl_id @@ -233,16 +272,44 @@ # ##### -ad_proc -public workflow::fsm::new { - {-short_name:required} - {-pretty_name:required} - {-object_id:required} - {-object_type "acs_object"} - {-callbacks {}} - {-spec} +ad_proc -public workflow::fsm::new_from_spec { + {-package_key {}} + {-object_id {}} + {-spec:required} } { - Creates a new FSM workflow, with an optional spec argument. + Create a new workflow from spec + @param workflow_id The id of the workflow to delete. + @param spec The roles spec + @return A list of IDs of the workflows created + + @author Lars Pind (lars@collaboraid.biz) + @see workflow::new +} { + if { [llength $spec] != 2 } { + error "You can only create one workflow at a time" + } + + db_transaction { + foreach { short_name spec } $spec { + set workflow_id [workflow::fsm::parse_spec \ + -package_key $package_key \ + -object_id $object_id \ + -short_name $short_name \ + -spec $spec] + } + } + + return $workflow_id +} + +ad_proc -public workflow::fsm::clone { + {-workflow_id:required} + {-package_key {}} + {-object_id {}} +} { + Clones an existing FSM workflow + @param short_name For referring to the workflow from Tcl code. Use Tcl variable syntax. @param pretty_name A human readable name for the workflow for use in the UI. @param object_id The id of an ACS Object indicating the scope the workflow. @@ -256,42 +323,120 @@ @author Lars Pind (lars@collaboraid.biz) @see workflow::new } { + set workflow_id [new_from_spec \ + -package_key $package_key \ + -object_id $object_id \ + -spec [generate_spec -workflow_id $workflow_id] \ + ] - db_transaction { + return $workflow_id +} - # Create the workflow - set workflow_id [workflow::new \ - -short_name $short_name \ - -pretty_name $pretty_name \ - -object_id $object_id \ - -object_type $object_type \ - -callbacks $callbacks] - - # May need to parse the simple workflow notation - if { [exists_and_not_null spec] } { - parse_spec -workflow_id $workflow_id -spec $spec - } +ad_proc -public workflow::fsm::generate_spec { + {-workflow_id:required} +} { + Generate a spec for a workflow in array list style. + + @param workflow_id The id of the workflow to delete. + @return The spec for the workflow. + + @author Lars Pind (lars@collaboraid.biz) + @see workflow::new +} { + workflow::get -workflow_id $workflow_id -array row + + set short_name $row(short_name) + + array unset row object_id + array unset row workflow_id + array unset row short_name + + set spec [list] + + foreach name [lsort [array names row]] { + lappend spec $name $row($name) } - return $workflow_id + lappend spec roles [workflow::role::generate_roles_spec -workflow_id $workflow_id] + lappend spec states [workflow::state::fsm::generate_states_spec -workflow_id $workflow_id] + lappend spec actions [workflow::action::fsm::generate_actions_spec -workflow_id $workflow_id] + + return [list $short_name $spec] } -ad_proc -private workflow::fsm::parse_spec { +ad_proc -public workflow::fsm::get_states { {-workflow_id:required} +} { + Get the state_id's of all the states in the workflow. + + @param workflow_id The ID of the workflow + @return list of state_id's. + + @author Lars Pind (lars@collaboraid.biz) +} { + return [db_list select_state_ids {}] +} + +ad_proc -public workflow::fsm::get_initial_state { + {-workflow_id:required} +} { + Get the id of the state that a workflow case is in once it's + started (after the initial action is fired). + + @author Peter Marklund +} { + set initial_action_id [workflow::get_initial_action -workflow_id $workflow_id] + + set initial_state [workflow::action::fsm::get_element -action_id $initial_action_id \ + -element new_state_id] + + return $initial_state +} + +##### +# Private procs +##### + +ad_proc -private workflow::fsm::parse_spec { + {-short_name:required} + {-package_key {}} + {-object_id {}} {-spec:required} } { - Parse the -workflow argument to workflow::new and create roles, - states, actions, etc., as appropriate + Create workflow, roles, states, actions, etc., as appropriate @param workflow_id The id of the workflow to delete. @param spec The roles spec @author Lars Pind (lars@collaboraid.biz) @see workflow::new } { - array set workflow { roles {} states {} actions {} } + # Default values + array set workflow { + roles {} + states {} + actions {} + callbacks {} + object_type {acs_object} + } + array set workflow $spec + + # Override stuff in the spec with stuff provided as an argument here + foreach var { package_key object_id } { + if { ![empty_string_p [set $var]] } { + set workflow($var) [set $var] + } + } + set workflow_id [workflow::new \ + -short_name $short_name \ + -pretty_name $workflow(pretty_name) \ + -package_key $workflow(package_key) \ + -object_id $object_id \ + -object_type $workflow(object_type) \ + -callbacks $workflow(callbacks)] + workflow::role::parse_roles_spec \ -workflow_id $workflow_id \ -spec $workflow(roles) @@ -303,6 +448,8 @@ workflow::action::fsm::parse_actions_spec \ -workflow_id $workflow_id \ -spec $workflow(actions) + + return $workflow_id } @@ -311,30 +458,32 @@ + + ##### # # workflow::service_contract # ##### -ad_proc -public workflow::service_contract::role_default_assignee {} { - return "Role_DefaultAssignees" +ad_proc -public workflow::service_contract::role_default_assignees {} { + return "[workflow::package_key].Role_DefaultAssignees" } ad_proc -public workflow::service_contract::role_assignee_pick_list {} { - return "Role_AssigneePickList" + return "[workflow::package_key].Role_AssigneePickList" } ad_proc -public workflow::service_contract::role_assignee_subquery {} { - return "Role_AssigneeSubQuery" + return "[workflow::package_key].Role_AssigneeSubQuery" } ad_proc -public workflow::service_contract::action_side_effect {} { - return "Action_SideEffect" + return "[workflow::package_key].Action_SideEffect" } ad_proc -public workflow::service_contract::activity_log_format_title {} { - return "ActivityLog_FormatTitle" + return "[workflow::package_key].ActivityLog_FormatTitle" } ad_proc -public workflow::service_contract::get_impl_id { Index: openacs-4/packages/workflow/tcl/workflow-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/workflow-procs.xql,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/tcl/workflow-procs.xql 21 Jan 2003 18:06:00 -0000 1.3 +++ openacs-4/packages/workflow/tcl/workflow-procs.xql 3 Feb 2003 12:23:01 -0000 1.4 @@ -10,12 +10,37 @@ + + + select workflow_id, + short_name, + pretty_name, + object_id, + package_key, + object_type + from workflows + where workflow_id = :workflow_id + + + + + + select impl.impl_owner_name || '.' || impl.impl_name + from acs_sc_impls impl, + workflow_callbacks c + where c.workflow_id = :workflow_id + and impl.impl_id = c.acs_sc_impl_id + order by c.sort_order + + + select workflow_id from workflows where package_key = :package_key and short_name = :short_name + and object_id is null @@ -27,6 +52,24 @@ + + + select role_id + from workflow_roles + where workflow_id = :workflow_id + order by sort_order + + + + + + select action_id + from workflow_actions + where workflow_id = :workflow_id + order by sort_order + + + select max(sort_order) @@ -37,7 +80,7 @@ - select coalesce(max(sort_order)) + 1 + select coalesce(max(sort_order),0) + 1 from workflow_callbacks where workflow_id = :workflow_id @@ -50,6 +93,15 @@ + + + select state_id + from workflow_fsm_states + where workflow_id = :workflow_id + order by sort_order + + + select impl_id 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 -N -r1.3 -r1.4 --- openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 21 Jan 2003 18:06:13 -0000 1.3 +++ openacs-4/packages/workflow/tcl/test/workflow-test-procs.tcl 3 Feb 2003 12:23:13 -0000 1.4 @@ -25,6 +25,13 @@ where parent_id is null}] } +ad_proc workflow::test::workflow_object_id_2 {} { + +} { + return [db_string some_object_id {select min(object_id) from acs_objects where object_type = 'apm_parameter'}] + +} + ad_proc workflow::test::workflow_id {} { Get the id of the Bug Tracker bug workflow } { @@ -97,7 +104,12 @@ Create a test workflow for the Bug Tracker Bug use case. } { + set spec { + pretty_name "Bug Test" + package_key "acs-automated-testing" + object_type "acs_object" + callbacks { bug-tracker.FormatLogTitle } roles { submitter { pretty_name "Submitter" @@ -129,7 +141,7 @@ pretty_name "Open" pretty_past_tense "Opened" new_state "open" - initial_action_p 1 + initial_action_p t } comment { pretty_name "Comment" @@ -172,17 +184,12 @@ } } } + set spec [list [workflow::test::workflow_name] $spec] - set main_site_package_id [workflow::test::workflow_object_id] - # Cannot use bt_bug as we cannot assume Bug Tracker to be installed - set workflow_id [workflow::fsm::new \ - -short_name [workflow::test::workflow_name] \ - -pretty_name "Bug Test" \ - -object_id $main_site_package_id \ - -object_type "acs_object" \ - -callbacks { bug-tracker.FormatLogTitle } \ + set workflow_id [workflow::fsm::new_from_spec \ + -object_id [workflow::test::workflow_object_id] \ -spec $spec] return $workflow_id @@ -198,14 +205,13 @@ # ##### - set main_site_package_id [workflow::test::workflow_object_id] - # Cannot use bt_bug as we cannot assume Bug Tracker to be installed set workflow_id [workflow::new \ -short_name [workflow::test::workflow_name] \ -pretty_name "Bug Test" \ - -object_id $main_site_package_id \ + -package_key "acs-automated-testing" \ + -object_id [workflow::test::workflow_object_id] \ -object_type "acs_object" \ -callbacks { bug-tracker.FormatLogTitle }] @@ -253,7 +259,7 @@ ##### workflow::action::fsm::new \ - -initial_action \ + -initial_action_p t \ -workflow_id $workflow_id \ -short_name "open" \ -pretty_name "Open" \ @@ -317,9 +323,7 @@ } { # We don't care about error here catch { - set workflow_id [workflow::get_id \ - -object_id [workflow::test::workflow_object_id] \ - -short_name [workflow::test::workflow_name]] + set workflow_id [workflow_id] workflow::delete -workflow_id $workflow_id } @@ -351,7 +355,7 @@ # Cannot get this to work as it seems the catch will return true # if any catch did so in the executed code. # set error_p [catch workflow::test::workflow_setup error] - set workflow_id [workflow::test::$create_proc] + set workflow_id [$create_proc] # Create the workflow case in open state set object_id [workflow::test::workflow_object_id] @@ -362,13 +366,14 @@ -object_id $object_id \ -workflow_short_name [workflow::test::workflow_name]] - set retrieved_object_id \ - [workflow::case::get_object_id -case_id $case_id] + #set retrieved_object_id \ + \# [workflow::case::get_object_id $case_id + aa_true "case_id of a created workflow case should be retrievable" \ [string equal $case_id $retrieved_case_id] - aa_true "object_id of a created workflow case should be retrievable" \ - [string equal $object_id $retrieved_object_id] + #aa_true "object_id of a created workflow case should be retrievable" \ + \# [string equal $object_id $retrieved_object_id] set expect_enabled_actions [list comment edit resolve] workflow::test::assert_case_state \ @@ -418,13 +423,14 @@ } set error_p [catch $test_chunk errMsg] + + if { $error_p } { + global errorInfo + aa_false "error during setup: $errMsg - $errorInfo" $error_p + } # Teardown - workflow::test::workflow_teardown - - # Report any errors from the setup proc - global errorInfo - aa_false "error during setup: $errMsg - $errorInfo" $error_p + # workflow::test::workflow_teardown } @@ -442,15 +448,47 @@ @author Peter Marklund @creation-date 16 January 2003 } { - workflow::test::run_bug_tracker_test -create_proc "workflow_setup" + 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 Peter Marklund - @creation-date 16 January 2003 + @author Lars Pind + @creation-date 21 January 2003 } { - workflow::test::run_bug_tracker_test -create_proc "workflow_setup_array_style" + 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 test_chunk { + set workflow_id_1 [workflow::test::workflow_setup] + + set workflow_id_2 [workflow::fsm::clone -workflow_id $workflow_id_1 -object_id [workflow::test::workflow_object_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] + + if { $error_p } { + global errorInfo + aa_false "error during setup: $errMsg - $errorInfo" $error_p + } + + catch { + workflow::delete -workflow_id $workflow_id_1 + workflow::delete -workflow_id $workflow_id_2 + } + +} +