Index: openacs-4/packages/workflow/www/doc/developer-guide.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/developer-guide.html,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/developer-guide.html 28 Aug 2003 09:41:59 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/developer-guide.html 20 Nov 2003 12:52:49 -0000 1.3 @@ -40,7 +40,7 @@

Package Developer's Guide to Workflow

-Workflow Documentation : Package Developer's Guide +Workflow Documentation : Package Developer's Guide
Index: openacs-4/packages/workflow/www/doc/fall-2003-extensions.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/fall-2003-extensions.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/www/doc/fall-2003-extensions.html 20 Nov 2003 12:52:49 -0000 1.1 @@ -0,0 +1,735 @@ + + + + Fall 2003 Workflow Extensions Requirements and Design + + + + +

Fall 2003 Workflow Extensions

+ +Workflow Documentation : Fall 2003 Workflow Extensions + +
+ +

+ By Lars Pind +

+ +

+ This requirements and design document is primarily motivated by: +

+ + + +

Timers

+ +

Requirements

+ +

+ Use cases: +

+ + + +

+ The timer will always be of the form "This action will automatically + execute x amount of time after it becomes enabled". If it is later + un-enabled (disabled) because another action (e.g. a vote action in + the second use casae above) was executed, then the timer will be + reset. If the action later becomes enabled, the timer will start + anew. +

+ +

Design

+ +

+ We currently do not have any information on which actions are + enabled, and when they're enabled. We will probably need a table, + perhaps one just for timed actions, in which a row is created when a + timed action is enabled, and the row is deleted again when the state + changes. +

+ +

Extending workflow_actions

+ +
+create table workflow_actions(
+    ...
+    -- The number of seconds after having become enabled the action
+    -- will automatically execute
+    timeout                 interval
+    ...
+);
+
+ +

+ DESIGN NOTE: The 'interval' datatype is not supported in + Oracle. +

+ +

The Enabled Actions Table

+ +
+create table workflow_case_enabled_actions(
+    case_id                 integer
+                            constraint wf_case_enbl_act_case_id_nn
+                            not null
+                            constraint wf_case_enbl_act_case_id_fk
+                            references workflow_cases(case_id)
+                            on delete cascade,
+    action_id               integer
+                            constraint wf_case_enbl_act_action_id_nn
+                            not null
+                            constraint wf_case_enbl_act_action_id_fk
+                            references workflow_actions(action_id)
+                            on delete cascade,
+    -- the timestamp when this action will fires
+    execution_time          timestamptz
+                            constraint wf_case_enbl_act_timeout_nn
+                            not null,
+    constraint workflow_case_enabled_actions_pk
+    primary key (case_id, action_id)
+);
+
+ +

The Logic

+ +

+ After executing an action, workflow::case::action::execute will: +

+ +
    +
  1. + Delete all actions from worklfow_case_enabled_actions which are no longer enabled. +
  2. +
  3. + If the timeout is zero, execute immediately. +
  4. +
  5. + Insert a row for all enabled actions with timeouts which are not + already in workflow_case_enabled_actions, with + fire_timestamp = current_timestamp + workflow_actions.timeout_seconds . +
  6. +
+ +

+ NOTE: We need to keep running, so if another automatic action + becomes enabled after this action fires, they'll fire as well. +

+ +

The Sweeper

+ +

+ The sweeper will find rows in + workflow_case_enabled_actions with fire_timetsamp + < current_timestamp, ordered by fire_timstamp, and execute + them. +

+ +

+ It should do a query to find the action to fire first, then release + the db-handle and execute it. Then do a fresh query to find the + next, etc. That way we will handle the situation correctly where the + first action firing causes the second action to no longer be + enabled. +

+ +

The Optimization

+ +

+ Every time the sweeper runs, at least one DB query will be made, + even if there are no timed actions to be executed. +

+ +

+ Possible optimizations: +

+ + + +

Hierarchical Workflows

+ +

Requirements

+ +

+ Use cases: +

+ + + +

Design

+ + + +

Data Model

+ +
+create table workflow_actions(
+  ...
+  child_workflow            integer
+                            constraint wf_action_child_wf_fk
+                            references workflows(workflow_id),
+  ...
+);
+
+create table workflow_fsm_states(
+  ...
+  -- does this state imply that the case is completed?
+  complete_p              boolean,
+  ...
+);
+
+create table workflow_action_fsm_output_map(
+  action_id               integer
+                          not null
+                          references workflow_actions(action_id)
+                          on delete cascade,
+  acs_sc_impl_id          integer
+                          not null
+                          references acs_sc_impls(impl_id)
+                          on delete cascade,
+  output_value            varchar(4000),
+  new_state               integer
+                          references workflow_fsm_states
+);
+
+create table workflow_action_child_role_map(
+  parent_action_id          integer
+                            constraint wf_act_chid_rl_map_prnt_act_fk
+                            references workflow_actions(action_id),
+  parent_role               integer
+                            constraint wf_act_chid_rl_map_prnt_rl_fk
+                            references workflow_roles(role_id),
+  child_role                integer
+                            constraint wf_act_chid_rl_map_chld_rl_fk
+                            references workflow_roles(role_id),
+  mapping_type              char(40)
+                            constraint wf_act_chid_rl_map_type_ck
+                            check (mapping_type in 
+                                ('per_role','per_member','per_user'))
+);
+
+
+create table workflow_cases(
+  ...
+  state                      char(40)
+                             constraint workflow_cases_state_ck
+                             check (state in ('active', 'completed',
+                             'closed', 'canceled', 'suspended'))
+                             default 'active',
+  suspended_until            timestamptz,
+  parent_enabled_action_id   integer
+                             constraint workflow_cases_parent_fk
+                             references workflow_case_enabled_actions(enabled_action_id)
+);
+
+create table workflow_case_enabled_actions(
+    enabled_action_id       integer
+                            constraint wf_case_enbl_act_case_id_pk
+                            primary key,
+    case_id                 integer
+                            constraint wf_case_enbl_act_case_id_nn
+                            not null
+                            constraint wf_case_enbl_act_case_id_fk
+                            references workflow_cases(case_id)
+                            on delete cascade,
+    action_id               integer
+                            constraint wf_case_enbl_act_action_id_nn
+                            not null
+                            constraint wf_case_enbl_act_action_id_fk
+                            references workflow_actions(action_id)
+                            on delete cascade,
+    enabled_state           char(40)
+                            constraint wf_case_enbl_act_state_ck
+                            check (enabled_state in ('enabled','completed','canceled','refused')),
+    -- the timestamp when this action automatically fires
+    fire_timestamp          timestamp
+                            constraint wf_case_enbl_act_timeout_nn
+                            not null,
+    constraint wf_case_ena_act_case_act_un
+    primary key (case_id, action_id)
+);
+
+
+ +

Callback Types

+ + + +

Callback Output

+ +

+ The callbacks returning 'output' above must enumerate all the values + they can possible output (similar contruct to GetObjectType + operation on other current workflow service contracts), and the + callback itself must return one of those possible values. +

+ +

+ The workflow engine will then allow the workflow designer to map + these possible output values of the callback to new states, in the + case of an FSM, or similar relevant state changes for other models. +

+ +

Enabled Action Logic

+ +

+ Executed when an action which was previously not enabled becomes enabled. +

+ +
    +
  1. + If the action has a timeout of 0, then execute the action and quit. +
  2. +
  3. + Insert a row into workflow_case_enabled_actions. +
  4. +
  5. + If the action has non-null timeout > 0, then the row will have a + execution_time of current_timestamp + timeout. +
  6. +
  7. + If the action has non-null child_workflow, create child cases. For + each role which has a mapping_type of 'per_member' or 'per_user', + create one case per member/user of that role. If more roles have + per_member/per_user setting, then the cartesian product of child + cases are created (DESIGN QUESTION: Would this ever be relevant?) +
  8. +
  9. + If there is any ActionEnabled callback, execute that (only the + first, if multiple exists), and use the workflow_fsm_output_map to + determine which new state to bump the workflow to, if any. +
  10. +
+ +

Un-Enabled Action Logic

+ +

+ Executed when an action which was previously enabled is no longer + enabled, because the workflow's state was changed by some other + action. +

+ +
    +
  1. + If the action has any child cases, these will be marked canceled. +
  2. +
+ +

Child Case State Changed Logic

+ +

+ We execute the OnChildCaseStateChange callback, if any. This gets to + determine whether the parent action is now complete and should fire. +

+ +

+ We provide a default implementation, which simply checks if the + child cases are in the 'complete' state, and if so, fires. +

+ +

+ NOTE: What do we do if any of the child cases are canceled? Consider + the complete and move on with the parent workflow? Cancel the parent + workflow? +

+ +

+ NOTE: Should we provide this as internal workflow logic or as a + default callback implementation? If we leave this as a service + contract with a default implementation, then applications can + customize. But would that ever be relevant? Maybe this callback is + never needed. +

+ +

On Fire Logic

+ +

+ When the action finally fires. +

+ +

+ If there's any OnFire callback defined, we execute this. +

+ +

+ If the callback has output values defined, we use the mappings in + workflow_action_fsm_output_map to determine which state to + move to. +

+ +

+ After firing, we execute the SideEffect callbacks and send off + notifications. +

+ +

+ DESIGN QUESTION: How do we handle notifications for child cases? We + should consider the child case part of the parent in terms of + notifications, so when a child action executes, we notify those who + have requested notifications on the parent. And when the last child + case completes, which will also complete the parent action, we + should avoid sending out duplicate notifications. How? +

+ +

Case State

+ +

+ Cases can be active, complete, suspended, or canceled. +

+ +

+ They start out as active. For FSMs, when they hit a state with + complete_p = t, the case is moved to 'complete'. +

+ +

+ Users can choose to cancel or suspend a case. When suspending, they + can type in a date, on which the case will spring back to 'active' + life. +

+ +

+ When a parent worfklow completes an action with a sub-workflow, the + child cases that are 'completed' are marked 'closed', and the child + cases that are 'active' are marked 'canceled'. +

+ +

+ The difference between 'completed' and 'closed' is that completed + does not prevent the workflow from continuing (e.g. bug-tracker + 'closed' state doesn't mean that it cannot be reopened), whereas a + closed case cannot be reactivarted (terminology confusion alert!). +

+ + +

Gated Actions

+ +

Requirements

+ +

+ An action does not become avilable until a given list of other + actions have completed. The advanced version is that you can also + specify for each of these other tasks how many times they must've + been executed. +

+ +

Design

+ +
+create table workflow_action_dependencies(
+  action_id                 integer
+                            constraint wf_action_dep_action_fk
+                            references workflow_actions(action_id),
+  dependent_on_action       integer
+                            constraint wf_action_dep_dep_action_fk
+                            references workflow_actions(action_id),
+  min_n                     integer default 1,
+  max_n                     integer,
+  constraint wf_action_dep_act_dep_pk
+  primary key (action_id, dependent_on_action)
+);
+
+ +

+ When an action is about to be enabled, and before calling the + CanEnableP callback, we check the workflow_case_enabled_actions table + to see that the required actions have the required number of rows in + the workflow_case_enabled_actions table with enabled_state + 'completed'. +

+ + + +

Maximum Number of Iterations

+ +

Requirements

+ +

+ An action can at most be executed a certain number of times. +

+ +

Design

+ +
+create table workflow_actions(
+  ...
+  max_n                     integer
+  ...
+);
+
+ +

+ When an action is about to be enabled, and before calling the + CanEnableP callback, we check the workflow_case_enabled_actions table + to see how many rows exist with enabled_state 'completed'. +

+ + + +

Resolution Codes

+ +

Requirements

+ +

+ The bug-tracker has resolution codes under the "Resolve" action. It + would be useful if these could be customized. +

+ +

+ In addition, I saw one other dynamic-workflow product (TrackStudio) + on the web, and they have the concept of resolution codes + included. That made me realize that this is generally useful. +

+ +

+ In general, a resolution code is a way of distinguishing different + states, even though those states are identical in terms of the + workflow process. +

+ +

+ Currently, the code to make these happen is fairly clumsy, what with + the "FormatLogTitle" callback which we invented. +

+ +

Design

+ +
+create sequence ...
+
+create table workflow_action_resolutions(
+  resolution_id           integer 
+                          constraint wf_act_res_pk
+                          primary key,
+  action_id               integer
+                          constraint wf_act_res_action_fk
+                          references workflow_actions(action_id)
+                          on delete cascade,
+  sort_order              integer
+                          constraint wf_act_res_sort_order_nn
+                          not null,
+  short_name              varchar(100)
+                          constraint wf_act_res_short_name_nn
+                          not null,
+  pretty_name             varchar(200)
+                          constraint wf_act_res_pretty_name_nn
+                          not null
+);
+
+create index workflow_act_res_act_idx on workflow_action_resolutions(action_id);
+
+create table workflow_action_res_output_map(
+  action_id               integer
+                          not null
+                          references workflow_actions(action_id)
+                          on delete cascade,
+  acs_sc_impl_id          integer
+                          not null
+                          references acs_sc_impls(impl_id)
+                          on delete cascade,
+  output_value            varchar(4000),
+  resolution_id           integer
+                          not null
+                          references workflow_action_resolutions(resolution_id)
+                          on delete cascade,
+);
+
+-- FK index on action_id
+-- FK index on acs_sc_impl_id
+-- FK index on resolution
+
+ + + + + +

Appendix: TIP Voting Process

+ +
+TIP Master Workflow
+  Model = FSM
+  Roles
+    Submitter
+    Voter
+  States
+    Proposed
+    Voting
+    Withdrawn
+    Approved
+    Rejected
+  Actions
+    Propose
+      Initial action
+      New state = Proposed
+    Vote
+      Enabled in state = Proposed
+      Role = Voter
+      Sub-workflow = Individual Vote
+        In progress state = Voting
+        Sub-role Voter = pparent role Voter
+          One sub-case per user in the Voter role
+      New state = Approved | Rejected
+      Logic = 
+        0 Rejects + > 0 Approvals = Approved
+        2/3rds Approvals => Approved
+        Otherwise => Rejected
+    Withdraw
+      Enabled in state = Proposed, Voting
+      Role = Submitter
+      New state = Withdrawn
+
+TIP Individual Vote Workflow
+  Model = FSM
+  Roles
+    Voter
+  States
+    Open
+    Approved
+    Rejected
+    Abstained
+  Actions
+    Open
+      Initial action
+      New state = Open
+    Approve
+      Enabled in state = Open
+      Role = Voter
+      New state = Approved
+    Reject
+      Enabled in state = Open
+      Role = Voter
+      New state = Rejected
+    Abstain
+      Enabled in state = Open
+      Role = Voter
+      New state = Abstained
+    No Vote
+      Enabled in state = Open
+      Timeout = 7 days
+      New state = Abstained
+
+ + +
+ + + Index: openacs-4/packages/workflow/www/doc/index.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/index.html,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/index.html 28 Aug 2003 09:41:59 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/index.html 20 Nov 2003 12:52:49 -0000 1.3 @@ -44,6 +44,13 @@ how we intended to implement the package then. It is inaccurate in a number of places where reality forced us to make changes. +
+ Fall 2003 Extensions +
+
+ Adding actions as sub-workflows, automatic/timed actions, more conditions before actions are + enabled, dynamic outcome of actions, resolution codes. +

Version History

Index: openacs-4/packages/workflow/www/doc/specification.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/specification.html,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/specification.html 28 Aug 2003 09:41:59 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/specification.html 20 Nov 2003 12:52:49 -0000 1.3 @@ -9,7 +9,7 @@

Workflow Functional Specification

-Workflow Documentation : Functional Specification +Workflow Documentation : Functional Specification