symbiote / silverstripe-advancedworkflow

A highly configurable step-based workflow module.
BSD 3-Clause "New" or "Revised" License
47 stars 71 forks source link

Support for timed transitions #97

Open stojg opened 11 years ago

stojg commented 11 years ago

I'm currently looking at expanding the advancedworkflow to support timed transition. For example notifying content reviewers that they should have a look at something. I'm dumping a couple of ideas on how to support this. I'm keen on getting @nyeholt and @sminnee ideas on this.

Allow multiple workflows for a WorkflowApplicable extended objects

This allows us to configure multiple workflows per objects. Another approach would to use some sort of hierarchial state machine pattern or a state machine with decision trees, but I think that would complicate this a lot more.

An example would be to have:

This refactoring includes changing the WorkflowApplicable from

public static $has_one = array(
    'WorkflowDefinition' => 'WorkflowDefinition',
);

to

public static $has_many = array(
    'WorkflowDefinitions' => 'WorkflowDefinition',
);

.. and change all the relevant UI elements to support that.

Create Transitions that are triggered via timers

Waiting for review - initial state


This will require transitions to be able to save triggerstamps persistance so it can be queried and exectuted. Most likely with the help of the queuedjobs module.

How about creating a WorkflowTimeTransition?

Other refactoring ideas

Break out Create state from WorkFlowAction

The current state should should reflect which state the current Workflow is in.

A State can have:

It changes the perception from actions to state:

Example:

Waiting for approval - Initial state

  1. on entry: send notification email to Publisher (action)

-> transition to State.Approved -> transition to State.Rejected

Approved

  1. on entry: send notification email to Author (action)
    • transition to State.Published

      Rejected

  2. on entry: send notification email to Author (action)
    • transition to State.Waiting for approval
    • transition to State.Abort

      Published

    • on entry: Publish article (action)
    • on entry: close workflow (action)

      Abort

    • On entry: send email to Author and Publisher (action)

This makes states a bit more lightweight and are basically just placeholders for actions.

Allow Transitions to have actions

Good for sending notifications or scheduling embargos / offline tasks

This separates states from actions and makes actions more generic and it's easier to visualize the workflow.

stojg commented 11 years ago

On second thoughts, I'm unclear how to connect the has_many back to WorkflowApplicable from WorkflowDefinition with a has_one, since WorkflowApplicable can be any type of DataObject.

WorkflowApplicable.php

public static $has_many = array(
    'WorkflowDefinitions' => 'WorkflowDefinition',
);

WorkflowDefinition.php

public static $has_one = array(
    'WorkflowApplicable' => 'DataObject'
);
nyeholt commented 11 years ago

Multiple workflows

Many months (years?) ago I started looking at ways of applying multiple workflows to objects, and came down to

The binding between WorkflowApplicable and WorkflowDefinition would make use of a new data object type that captured the item it's applied to (using same DataClass/DataID pattern used in WorkflowInstace), type (or arbitrary name) it is relevant for, and the workflow definition to use in that instance. Then WorkflowService::getDefinitionFor will need changing to figure out the relevant definition based on the type/state of the object.

Timed Transitions

WorkflowAction classes at present represent a "state" in the workflow, and I'm not sure I'd want to see it separated out into an explicit WorkflowState and WorkflowAction classes just yet without a compelling reason. My understanding of what you're getting at is that you're wanting to have more granular actions, ie

WorkflowState 1 .. * WorkflowAction

where the actions might be bound to 'events' for the state, ie

onEnterState -> WorkflowAction onExitState -> WorkflowAction on*State -> WorkflowAction

At the moment when I need this kind of thing I end up using an immediately executed action (ie it has one inbound transition and one outbound transition, meaning it'll get executed and immediately pass on to the next action). It's not exactly the cleanest representation, but unless you have very complex states, it suffices. But again, perhaps a compelling enough use case could be put forward for making the change.

For doing timed transitions in this structure, I'd first lean towards having a TimeDelayWorkflowAction that would be set to 'wait' for X number of hours/days or until a specific time, and then its execute() method would return true and its outbound transition followed. I guess I've always wanted to keep the complexity of the workflow transitions as simple as possible code wise and keep 'business' logic in the actions.

stojg commented 11 years ago

The binding between WorkflowApplicable and WorkflowDefinition would make use of a new data object type that captured the item it's applied to (using same DataClass/DataID pattern used in WorkflowInstace), type (or arbitrary name) it is relevant for, and the workflow definition to use in that instance. Then WorkflowService::getDefinitionFor will need changing to figure out the relevant definition based on the type/state of the object.

Seems fair, this would be 'default' SilverStripe pattern to solve this. I'll give it whirl if you're okey with it.

For doing timed transitions in this structure, I'd first lean towards having a TimeDelayWorkflowAction that would be set to 'wait' for X number of hours/days or until a specific time, and then its execute() method would return true and its outbound transition followed.

Yeah, that is basically what I would do as well.

I don't have a better compelling reason that it follows a state machine pattern that I recently implemented in javascript. In my point of view it separates the state to just be an arbitrary description of the current state that maps to the business workflow.

I believe that users and developers can easier conceptualize the flow by separating a state like publish from the actions that when something gets published a bunch of things should happen at the same time.

where the actions might be bound to 'events' for the state, ie

onEnterState -> WorkflowAction onExitState -> WorkflowAction on*State -> WorkflowAction

Yeah, this and being able to set actions on Transitions makes it very generalized, but maybe to generalized?

Here's my js impementation of a state maching: http://play.stojg.se/js/state.js

stojg commented 11 years ago

I've started work on the multiple workflows per objects, outstanding are more tests and more testing. https://github.com/stojg/advancedworkflow/commits/multiple-workflows