Index: openacs-4/packages/workflow/www/doc/fall-2003-extensions.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/fall-2003-extensions.adp,v diff -u -N -r1.3 -r1.4 --- openacs-4/packages/workflow/www/doc/fall-2003-extensions.adp 31 Oct 2015 12:33:01 -0000 1.3 +++ openacs-4/packages/workflow/www/doc/fall-2003-extensions.adp 12 Sep 2016 06:10:32 -0000 1.4 @@ -31,10 +31,11 @@
  • Leiden: We have several occurances of the simple AskInfo-GiveInfo question/response pair. Defining simulation templates would be simplified if that was a reusable -component.
  • TIP Voting: There's a master workflow case for the TIP itself. -When voting, there'll be a sub-workflow case for each TIP member to -vote on the issue, with timeouts so if they don't vote within a -week, their vote is automatically 'Abstained'.
  • +component.
  • TIP Voting: There's a master workflow case for the TIP +itself. When voting, there'll be a sub-workflow case for each +TIP member to vote on the issue, with timeouts so if they don't +vote within a week, their vote is automatically +'Abstained'.
  • Questions we need answered by the design

      @@ -45,16 +46,18 @@
    1. Actions will no longer be atomic. An action can be "in progress" for a long time, while the child workflow(s) completes.
    2. We will introduce an uber-state of a case, which can be -'active', 'completed', 'canceled', or 'suspended'.
    3. When the action gets enabled, a callback will create child +'active', 'completed', 'canceled', or +'suspended'.
    4. When the action gets enabled, a callback will create child cases linked to this particular enabled action.
    5. Whenever a child case changes its case_state, a callback on the parent action is invoked, which examines the state of all of its child cases and determines whether the parent action is complete and ready to fire or not. If the parent action is completed, any -remaining 'active' child cases will be marked 'canceled'.
    6. If the action should ever get un-enabled, a callback will +remaining 'active' child cases will be marked +'canceled'.
    7. If the action should ever get un-enabled, a callback will cancel all remaining 'active' child cases.
    8. If the action becomes enabled again, we will create new child cases.
    9. A case which is a child of another case cannot leave the -'completed' or 'canceled' state, unless its parent enabled action -is still enabled.
    10. +'completed' or 'canceled' state, unless its parent +enabled action is still enabled.

      Data Model

      @@ -134,44 +137,46 @@
       be in one of the following:

      When Enabled

      When an action with child workflows is enabled, we start the child cases defined by the parent workflow, executing the initial action on each of them.

      We create one case per role in workflow_action_children times one case per member/user for roles with a mapping_type of -'per_member'/'per_user'. If more than one role has a mapping_type -other than 'per_role', we will create cases for the cartesian -product of members/users of those roles in the parent workflow.

      +'per_member'/'per_user'. If more than one role has +a mapping_type other than 'per_role', we will create cases +for the cartesian product of members/users of those roles in the +parent workflow.

      When Triggered

      The action can be triggered by a timeout, by the user, by child cases reaching a certain state, or by all child cases being completed.

      -

      An example of "child cases reaching a certain state" would be -the TIP voting process, where 2/3rd Approved votes is enough to -determine the outcome, and we don't need the rest to vote -anymore.

      -

      When triggered, all child cases with a case_state of 'active' -are put into the 'canceled' state. All child cases have their -'locked_p' flag set to true, so they cannot be reopened.

      +

      An example of "child cases reaching a certain state" +would be the TIP voting process, where 2/3rd Approved votes is +enough to determine the outcome, and we don't need the rest to +vote anymore.

      +

      When triggered, all child cases with a case_state of +'active' are put into the 'canceled' state. All +child cases have their 'locked_p' flag set to true, so they +cannot be reopened.

      Hierarchy, Design 2

      @@ -265,15 +270,16 @@
       
      1. case::enabled_actions -case_id
          -
        1. Find the case state (open)
        2. Find the actions enabled in this state (ask client, abort)
        3. "abort" is final, put it on the list.
        4. "ask client" has a child; look in workflow_enabled_actions for -the state
        5. If there's no row in enabled actions for "ask client", execute -"ac-init", which will set case_enabled_actions.state_id to -ac-asking for case_id #1 and action_id "ask client".
        6. Look in case_enabled_actions.state_id for case_id #1 and +
        7. Find the case state (open)
        8. Find the actions enabled in this state (ask client, abort)
        9. "abort" is final, put it on the list.
        10. "ask client" has a child; look in +workflow_enabled_actions for the state
        11. If there's no row in enabled actions for "ask +client", execute "ac-init", which will set +case_enabled_actions.state_id to ac-asking for case_id #1 and +action_id "ask client".
        12. Look in case_enabled_actions.state_id for case_id #1 and action_id "ask client" for the substate (ac-asking).
        13. Find the enabled actions in the ac-asking state (ac-ask).
        14. ac-ask is final, put it on the list
      2. case::action::execute -case_id -action_id
          -
        1. The question is which state to change.
        2. Find the action's parent_action_id
        3. If null, then change cases.state_id
        4. Otherwise, change case_enabled_actions.state_id.
        5. +
        6. The question is which state to change.
        7. Find the action's parent_action_id
        8. If null, then change cases.state_id
        9. Otherwise, change case_enabled_actions.state_id.
      3. Which roles are assigned/allowed to perform an action? Unchanged from current design.
      4. Which roles do a user play? Unchanged from current design.
      5. What is the activity history on this case? Unchanged from @@ -287,8 +293,8 @@
        • Keeping the sub-state in the workflow_case_enabled_actions table.
        • Kill the completed rows in workflow_case_enabled_actions, move -stuff into the case-log instead => that's going to be much, much -better for performance.
        • +stuff into the case-log instead => that's going to be much, +much better for performance.

        Design 2, Parallel Actions

        Example II: Parallel

        @@ -340,7 +346,8 @@
      1. case::action::execute -case_id #1 -action_id "init"
          -
        1. set cases.state_id to init's new_state ("open")
        2. call case::state_changed_handler -case_id #1 +
        3. set cases.state_id to init's new_state +("open")
        4. call case::state_changed_handler -case_id #1
          1. find enabled actions by looking in state_action_map under the current state (open) => (rev & op)
          2. foreach enabled action, see if it was already enabled. if so, @@ -360,9 +367,9 @@

        Difference between parallel sub-actions and non-parallel -sub-actions: If they are parallel, we enable all of them and don't -maintain state there; if they're not, we look for an init-action, -and do maintain state.

        +sub-actions: If they are parallel, we enable all of them and +don't maintain state there; if they're not, we look for an +init-action, and do maintain state.

         actions               action_id      | parent_action_id | assigned_role | trigger_type | new_state  
         (workflow)           ----------------+------------------+---------------+--------------+---------------
        @@ -384,12 +391,12 @@
                                   opi-init   | opinion-wr       | lawyer        | init         | opi-open
                                   opinion    | opinion-wr       | lawyer        | user         | opi-done
         
        -

        An action with type 'workflow' will maintain state inside -itself.

        -

        Can we do away with the extra layer of 'workflow' inside the -'parallel' track? How do we know that the child workflow has been -completed -- i guess we do, because we keep the state until its -parent is gone...

        +

        An action with type 'workflow' will maintain state +inside itself.

        +

        Can we do away with the extra layer of 'workflow' inside +the 'parallel' track? How do we know that the child +workflow has been completed -- i guess we do, because we keep the +state until its parent is gone...

         actions               action_id      | parent_action_id | assigned_role | trigger_type | new_state  
         (parallel-simple)    ----------------+------------------+---------------+--------------+---------------
        @@ -415,12 +422,12 @@
                                #3 |   opinion    | #1                       | yes        | no
         
         
        -

        Simple: We'd have to keep the row in case_enabled_actions around -with completed_p = yes until the parent action is also complete. -When an action is completed, it deletes the rows for all its -children. If the action does not have a parent action, we delete -the row (thus we don't keep completed_p rows around for top-level -actions).

        +

        Simple: We'd have to keep the row in case_enabled_actions +around with completed_p = yes until the parent action is also +complete. When an action is completed, it deletes the rows for all +its children. If the action does not have a parent action, we +delete the row (thus we don't keep completed_p rows around for +top-level actions).

        Design 2, Action-Per-User (Or Dynamic Number of Parallel Actions With Different Assignees)

        @@ -508,9 +515,10 @@
         
      2. It has workflow_actions.always_enabled_p = true
      3. There is a row in workflow_fsm_action_en_in_st for the current state and the given action.
      -
    11. Assigned? It's assigned if: trigger_type = 'user', state-action -map has assigned_p = true, it has an assigned_role.
    12. What's the assigned role? (For dynamic actions, there may not -be an assigend role, if the assignees are in the +
    13. Assigned? It's assigned if: trigger_type = 'user', +state-action map has assigned_p = true, it has an +assigned_role.
    14. What's the assigned role? (For dynamic actions, there may +not be an assigend role, if the assignees are in the workflow_case_action_assignees table.) Pick the parties from the workflow_case_role_map.
    15. Who are the assignees?
        @@ -519,7 +527,7 @@
      1. Find the parties:
        1. If there are rows in workflow_case_action_assignees for the -action, that's the parties.
        2. Otherwise, the parties are in the workflow_case_role_map for +action, that's the parties.
        3. Otherwise, the parties are in the workflow_case_role_map for role workflow_actions.assigned_role.
      2. Use party_approved_member_map to find the users.
      3. @@ -542,12 +550,12 @@ ------ - if trigger_type = workflow, find and execute child action with trigger_type = init. - if trigger_type = parallel, find subactions and ::enable them -- if trigger_type = dynamic, we'll need code to determine which children to ::enable, and how to create them +- if trigger_type = dynamic, we'll need code to determine which children to ::enable, and how to create them execute_state_change -------------------- - if action.new_state not null - - if there's a state with the same parent_action_id as the action being executed, update that state with new_state + - if there's a state with the same parent_action_id as the action being executed, update that state with new_state - otherwise insert row with new_state complete @@ -574,14 +582,14 @@
         always_enabled_p
         ----------------
        -- if parent_action_id is null, it means it's always enabled
        +- if parent_action_id is null, it means it's always enabled
         
        -- if parent action is trigger_type workflow, it means it's enabled when its parent workflow is enabled.
        +- if parent action is trigger_type workflow, it means it's enabled when its parent workflow is enabled.
           -> ::enable it after starting the workflow, i.e. executing the initial action
           -> will it stay enabled?
           -> it will get disabled automatically by cascading delete when its parent is deleted
         
        -- if parent action is trigger_type parallel, it has no meaning, all the parent's children will get enabled, anyway
        +- if parent action is trigger_type parallel, it has no meaning, all the parent's children will get enabled, anyway
         
         - if parent action is trigger_type dynamic, same shit, no semantics
         
        @@ -597,7 +605,7 @@
         trigger the parent action, the trigger condition would tell us
         whether to allow the trigger to go through.

        The trigger condition could check to see if all child cases are -completed, or it could check if there's enough to determine the +completed, or it could check if there's enough to determine the outcome, e.g. a 2/3 approval.

        XXXXXXXXXXXXXXX @@ -607,7 +615,8 @@ 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.

        +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?

        @@ -621,14 +630,17 @@
        • We want to be able to suspend a case, to reopen it later, without having to create an explicit state in the workflow for -this. Suspending the case means it doesn't show up on people's task -lists or in reminder emails until it's un-suspended.
        • In the UI, we want to be able to distinguish between cases that +this. Suspending the case means it doesn't show up on +people's task lists or in reminder emails until it's +un-suspended.
        • In the UI, we want to be able to distinguish between cases that are considered active and complete, even if the closed ones could be reopened to haunt us later. A good example is bug-tracker, where -bugs in "open" or "resolved" states are considered active and -should be counted as bugs needing attention, whereas those in -"closed" state are complete and do not.
        • A case can be canceled, which is the same as suspended, except -it doesn't resurface unless someone actively goes reopen it.
        • Child cases must be locked down so they cannot be reactivated +bugs in "open" or "resolved" states are +considered active and should be counted as bugs needing attention, +whereas those in "closed" state are complete and do +not.
        • A case can be canceled, which is the same as suspended, except +it doesn't resurface unless someone actively goes reopen +it.
        • Child cases must be locked down so they cannot be reactivated when the parent workflow has moved on to some other state.

        Design

        @@ -647,17 +659,19 @@

        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'.

        +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 +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!).

        Conditional Transformation For Atomic Actions

        @@ -674,9 +688,9 @@ primary key (action_id, output_value) );
    16. -

      Callback: Action.OnFire -> (output): Executed when the -action fires. Output can be used to determine the new state of the -case (see below).

      +

      Callback: Action.OnFire -> (output): +Executed when the action fires. Output can be used to determine the +new state of the case (see below).

      The callback must enumerate all the values it can possible output (similar contruct to GetObjectType operation on other current workflow service contracts), and the callback itself must @@ -687,14 +701,14 @@ models.

      Service Contract

      -workflow.Action_OnFire:
      +workflow.Action_OnFire:
         OnFire -> string
         GetObjectType -> string
         GetOutputs -> [string]
       

      GetOutputs returns a list of short_names and pretty_names -(possibly localizable, with #...# notation) of possible -outputs.

      +(possibly localizable, with #...# notation) of +possible outputs.

      Note

      The above table could be merged with the current workflow_fsm_actions table, which only contains one possible new @@ -720,7 +734,7 @@ create table workflow_fsm_states( ... -- If this is non-null, it implies that the case has completed with - -- the given output, for use in determining the parent workflow's + -- the given output, for use in determining the parent workflow's -- new state outcome integer constraint @@ -734,8 +748,8 @@

      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.

      +specify for each of these other tasks how many times they +must've been executed.

      Also, an action can at most be executed a certain number of times.

      Design

      @@ -765,11 +779,12 @@

      Enable Condition Callback

      -Action.CanEnableP -> (CanEnabledP): Gets called when -an action is about to be enabled, and can be used to prevent the -action from actually being enabled.

      +Action.CanEnableP -> (CanEnabledP): Gets +called when an action is about to be enabled, and can be used to +prevent the action from actually being enabled.

      Is called after all database-driven enable preconditions have -been met, i.e. FSM enabled-in-state, and "gated on"-conditions.

      +been met, i.e. FSM enabled-in-state, and "gated +on"-conditions.

      This will only get called once per case state change, so if the callback refuses to let the action become enabled, it will not be asked again until the next time an action is executed.

      @@ -790,12 +805,13 @@ );

      If user_trigger_p is false, we do not show the action on any -user's task list.

      +user's task list.

      Resolution Codes

      Requirements

      -

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

      +

      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 @@ -856,13 +872,13 @@

      When someone is assigned to an action, we want the notification email to say "You are now assigned to these tasks".

      Design

      -

      We'd need to postpone the notifications until we have fully +

      We'd need to postpone the notifications until we have fully updated the workflow state to reflect the changed state, to determine who should get the normal notifications, and who should get personalized ones.

      -

      Notifications doesn't support personalized notifications, but we -could use acs-mail/acs-mail-lite to send them out instead, and -exclude them from the normal notifications if they have instant +

      Notifications doesn't support personalized notifications, +but we could use acs-mail/acs-mail-lite to send them out instead, +and exclude them from the normal notifications if they have instant notifications set up.

      Assignment Reminders

      Requirements

      @@ -880,9 +896,9 @@ determine which actions are now enabled.

      Enabled Action Logic

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

      1. Insert a row into workflow_case_enabled_actions with -enabled_state = 'enabled', with the proper fire_timestamp: timeout -= null -> fire_timestamp = nul; timeout = 0 -> fire_timestamp -= current_timestamp; timeout > 0 -> fire_timestamp = -current_timestamp + timeout.
      2. If the action has a timeout of 0, then call +enabled_state = 'enabled', with the proper fire_timestamp: +timeout = null -> fire_timestamp = nul; timeout = 0 -> +fire_timestamp = current_timestamp; timeout > 0 -> +fire_timestamp = current_timestamp + timeout.
      3. If the action has a timeout of 0, then call workflow::case::action::execute and quit.
      4. 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 +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.

          Action Execute Logic

          Executed when an enabled action is triggered.

          • XXXXXXXXXXXXXXXXXXXXXXX
          • 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?)
          • If there is any ActionEnabled callback, execute that (only the +'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?)
          • 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.
          @@ -929,7 +947,8 @@ 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.

          +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?

          @@ -940,7 +959,7 @@ never needed.

          On Fire Logic

          When the action finally fires.

          -

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

          +

          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.

          @@ -954,14 +973,15 @@ should avoid sending out duplicate notifications. How?

          Callback Types

            -
          • (Not needed) Action.OnEnable -> -(output): Gets called when an action is enabled. Output can be -used to determine the new state of the case (see below), in -particular for an in-progress state.
          • (Not needed) Action.OnUnEnable: -Gets called when an action that used to be enabled is no longer -enabled. Is not called when the action fires.
          • (Not needed) -Action.OnChildCaseStateChange -> (output, CompleteP): -Called when a child changes its case state +
          • (Not needed) Action.OnEnable -> +(output): Gets called when an action is enabled. Output +can be used to determine the new state of the case (see below), in +particular for an in-progress state.
          • (Not needed) +Action.OnUnEnable: Gets called when an action that +used to be enabled is no longer enabled. Is not called when the +action fires.
          • (Not needed) +Action.OnChildCaseStateChange -> (output, +CompleteP): Called when a child changes its case state (active/completed/canceled/suspended). Returns whether the parent action has now completed. Output can be used to determine the new state of the case (see below).
          • @@ -972,21 +992,21 @@
            • A student has one week to send a document to another role. If he/she fails to do so, a default action executes.
            • An OpenACS OCT member has one week to vote on a TIP. If he/she -does not vote within that week, a default "Abstain" action is -executed.
            • +does not vote within that week, a default "Abstain" +action is executed.

            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.

            +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.

            +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(
            @@ -1034,7 +1054,8 @@
             workflow_actions.timeout_seconds .
             

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

        +becomes enabled after this action fires, they'll fire as +well.

        The Sweeper

        The sweeper will find rows in workflow_case_enabled_actions with @@ -1055,10 +1076,10 @@ timed action is inserted, we compare with the NSV, and update if the new action fires before the old action. When the timed action referred to in the NSV is either deleted because it gets -un-enabled, or executed, we'll clear the NSV, causing the next hit -to the sweeper to execute the query to find the (case_id, +un-enabled, or executed, we'll clear the NSV, causing the next +hit to the sweeper to execute the query to find the (case_id, action_id, fire_timestamp) of the first action to fire. Finally, we would need an NSV value to represent the fact that there are no -rows in this table, so we don't keep executing the query in that -case.

      5. +rows in this table, so we don't keep executing the query in +that case.