Index: openacs-4/packages/edit-this-page/www/doc/applications.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/edit-this-page/www/doc/applications.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/edit-this-page/www/doc/applications.adp 26 Aug 2015 18:03:34 -0000 1.2 +++ openacs-4/packages/edit-this-page/www/doc/applications.adp 12 Sep 2016 06:05:38 -0000 1.3 @@ -8,12 +8,12 @@ :ETP Applications
The default application is defined by the ETP package at server startup time as follows:
@@ -46,11 +46,11 @@ content of your web pages.etp_define_application
procedure. The real power of
defining new applications is that you can create templates that
Index: openacs-4/packages/edit-this-page/www/doc/contenttypes.adp
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/edit-this-page/www/doc/contenttypes.adp,v
diff -u -r1.2 -r1.3
--- openacs-4/packages/edit-this-page/www/doc/contenttypes.adp 26 Aug 2015 18:03:35 -0000 1.2
+++ openacs-4/packages/edit-this-page/www/doc/contenttypes.adp 12 Sep 2016 06:05:38 -0000 1.3
@@ -9,9 +9,9 @@
content_revision
object type refers to a row in the
@@ -23,7 +23,7 @@
attributes are stored for each revision,
such as the creation date and creating user.
Referring back to the definition of the default ETP application,
-you'll notice that it specifies that the
+you'll notice that it specifies that the
etp_page_revision
content type is to be used for the
index page and for all content pages. etp_page_revision is a
subtype of content_revision, but does not add any additional
@@ -36,7 +36,7 @@
The standard page attributes, while providing all the basic
functionality for a collaboratively edited website, are rarely
sufficient to implement real world designs.
-
Imagine you're creating an online scientific journal. The +
Imagine you're creating an online scientific journal. The graphic design mockup shows you that each issue of the journal contains a table of contents listing all the articles in that issue. However, the table of contents is organized into multiple @@ -53,11 +53,12 @@ to have a section and an abstract.
-etp::define_content_type
+Creating a new content type is done by calling the
+
+etp::define_content_type
-procedure from one of your tcl library files. Here's how you would
-accomplish the journal example discussed above:
+procedure from one of your tcl library files. Here's how you
+would accomplish the journal example discussed above:
etp::define_content_type journal_issue "Journal Issue" "Journal Issues" { { publication_date "Publication Date" "Publication Dates" string "size=60" "" } @@ -78,19 +79,20 @@-Once you've defined a content type, you may refer to it when +Once you've defined a content type, you may refer to it when calling the
- attribute_name
- pretty_name
- pretty_plural
- datatype (must be one of the entries in acs_datatypes: string, boolean, number, integer, date, etc.)
- html (a string containing html attributes for the input -control. useful attributes are "size=X" to specify the size of -standard input controls, and "rows=X cols=X" to specify the size of -a textarea. Textareas will be used only if the datatype is string -and html specifies rows or cols.)
- default_value (can either be a string denoting a single default -value, or the name of a callback function you've defined in the etp -namespace which is used to provide values for select lists).
+control. useful attributes are "size=X" to specify the +size of standard input controls, and "rows=X cols=X" to +specify the size of a textarea. Textareas will be used only if the +datatype is string and html specifies rows or cols.)- default_value (can either be a string denoting a single default +value, or the name of a callback function you've defined in the +etp namespace which is used to provide values for select +lists).
etp::define_application
procedure to set -up a new application. To continue with our journal example, you'd -want to do this as follows: +up a new application. To continue with our journal example, +you'd want to do this as follows:etp::define_application journal { index_template www/templates/journal-issue @@ -106,12 +108,13 @@ Creating the templates that make use of your custom content types is the subject of the next page . After -that's been done, the authors of the journal will be able to create -a new issue of the journal simply by creating a new instance of the -ETP package (a process that's automated within the ETP interface by -the "create subtopic" command) and ensuring that the new content -section is using the journal application. This setup can be -automated, since it's possible to specify the application to use -for any subtopic created within a particular directory. +that's been done, the authors of the journal will be able to +create a new issue of the journal simply by creating a new instance +of the ETP package (a process that's automated within the ETP +interface by the "create subtopic" command) and ensuring +that the new content section is using the journal application. This +setup can be automated, since it's possible to specify the +application to use for any subtopic created within a particular +directory.
Index: openacs-4/packages/edit-this-page/www/doc/install.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/edit-this-page/www/doc/install.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/edit-this-page/www/doc/install.adp 26 Aug 2015 18:03:35 -0000 1.2 +++ openacs-4/packages/edit-this-page/www/doc/install.adp 12 Sep 2016 06:05:38 -0000 1.3 @@ -7,35 +7,37 @@ ETP Documentation :Install -I'm assuming you've already installed OpenACS 4, loaded the data -model, and you can see the "Congratulations!" page in your web -browser. First, install the ETP package by navigating to the -package manager (found at
luke\@museatech.net acs-admin/apm
+I'm assuming you've already installed OpenACS 4, loaded the +data model, and you can see the "Congratulations!" page +in your web browser. First, install the ETP package by navigating +to the package manager (found atacs-admin/apm
), and -selecting "Load a new package from a URL or local directory". +selecting "Load a new package from a URL or local +directory".The Package Manager will retrieve the package code, place it in -your server's
+your server'spackages/editthispage
directory, and -perform the necessary database setup. When that's done, you'll need -to restart your server to load all of the package's tcl code.packages/editthispage
directory, and +perform the necessary database setup. When that's done, +you'll need to restart your server to load all of the +package's tcl code.Now you may test the package by visiting the Site Map (found at
-admin/site-map
), creating a new directory, selecting -"new application", and choosing Edit This Page. Within that -directory, anyone who has "write" permission will see a link that -takes them to the ETP interface from which they may edit the -content of the page.However, you're not really having fun until you can modify your -home page through your web browser. If you go the Main Site +"new application", and choosing Edit This Page. Within +that directory, anyone who has "write" permission will +see a link that takes them to the ETP interface from which they may +edit the content of the page.
+However, you're not really having fun until you can modify +your home page through your web browser. If you go the Main Site Administration page, and click on Parameters, you can set the URL for the main site, under IndexRedirectUrl. If you have mounted an Edit-this-page instance at /intranet, for example, you can enter intranet here, and requests for the main page (/) will be redirected to the Edit this page instance (/intranet).
Or, if you want requests for (/) to still go to /, then by -entering the following commands, you'll set up your site so that -Edit This Page can also serve pages at the top level of the URL -hierarchy, including your home page. Doing this will not prevent -access to the top-level admin pages; the acs-subsite package -remains mounted at the top level.
+entering the following commands, you'll set up your site so +that Edit This Page can also serve pages at the top level of the +URL hierarchy, including your home page. Doing this will not +prevent access to the top-level admin pages; the acs-subsite +package remains mounted at the top level.cd /web/MYSERVER/www mkdir index-backup Index: openacs-4/packages/edit-this-page/www/doc/searching.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/edit-this-page/www/doc/searching.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/edit-this-page/www/doc/searching.adp 26 Aug 2015 18:03:35 -0000 1.2 +++ openacs-4/packages/edit-this-page/www/doc/searching.adp 12 Sep 2016 06:05:38 -0000 1.3 @@ -13,9 +13,9 @@By default it will allow indexing of the title, content, and description attributes of an item. To add indexing of custom content_type attributes a developer creates a specially named Tcl -proc:
etp::search::content_type where content_type is -the customer content type you have created. For a sample Tcl proc -see edit-this-page/tcl/etp-sc-procs.tcl for +proc:
etp::search::content_type where content_type +is the customer content type you have created. For a sample Tcl +proc see edit-this-page/tcl/etp-sc-procs.tcl for etp::search::etp_page_revision. The calling proc will pass in an array name. The custom search proc should modify the passed in array using upvar as per the example. The elements of that array Index: openacs-4/packages/edit-this-page/www/doc/templates.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/edit-this-page/www/doc/templates.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/edit-this-page/www/doc/templates.adp 26 Aug 2015 18:03:35 -0000 1.2 +++ openacs-4/packages/edit-this-page/www/doc/templates.adp 12 Sep 2016 06:05:38 -0000 1.3 @@ -7,66 +7,69 @@ ETP Documentation :ETP Templates -To use ETP, or in fact to effectively use OpenACS 4, it's essential -that you become familiar with the OpenACS Templating System -. ETP's support -for rapid application development includes procedures for creating -the data sources that will be used by your page templates. You can -copy code from the examples in the +To use ETP, or in fact to effectively use OpenACS 4, it's +essential that you become familiar with the OpenACS Templating System +. ETP's +support for rapid application development includes procedures for +creating the data sources that will be used by your page templates. +You can copy code from the examples in the
packages/editthispage/templates
directory to get -started, but here's an overview of what you need to know. +started, but here's an overview of what you need to know.Providing the "Edit this page" link
As demonstrated inpackages/editthispage/www/master.tcl
, you should call -the procedure etp::get_etp_link - from your own master -template, in order to determine whether or not to present the user -with the "Edit this page" option. The procedure returns the html -link only within an instance of the ETP package, and then only if -the user has write access. Otherwise an empty string is returned. +the procedure etp::get_etp_link + from your own +master template, in order to determine whether or not to present +the user with the "Edit this page" option. The procedure +returns the html link only within an instance of the ETP package, +and then only if the user has write access. Otherwise an empty +string is returned.Retrieving page attributes for the template to display
Every ETP template will make use of the -etp::get_page_attributes - procedure. It creates an array -variable calledpa
- in the caller's stack frame, -containing all the attributes necessary to render the current page. -These attributes include the standard elements from the -cr_revisions table such as title, description, and content. If the -page is using a custom content type -, any -extended page attributes that correspond to it will be included. +etp::get_page_attributes + procedure. It creates an +array variable calledpa
+ in the caller's stack +frame, containing all the attributes necessary to render the +current page. These attributes include the standard elements from +the cr_revisions table such as title, description, and content. If +the page is using a custom content type +, +any extended page attributes that correspond to it will be +included.The complete list of standard attributes in the pa array is as follows:
-
The procedure is designed to be efficient under heavy load. The database is accessed once to retrieve the attributes, and a second -time to generate the page's context bar. The resulting array is -then cached in the server's memory until someone edits it. +time to generate the page's context bar. The resulting array is +then cached in the server's memory until someone edits it.- item_id
- name
- revision_id
- title
- context_bar
- description
- publish_date
- content
- extended attributes, if any, defined by -etp::make_content_type
+- item_id
- name
- revision_id
- title
- context_bar
- description
- publish_date
- content
- extended attributes, if any, defined by +etp::make_content_type
Once the
+it contains using the standard syntax for "onerow" data +sources; for example,pa
array variable has been created as a template data source, the template itself may reference the values -it contains using the standard syntax for "onerow" data sources; -for example,\@pa.content\@
.\@pa.content\@
.Retrieving the list of pages in a content section
ETP templates used for the index page will almost always make use -of the etp::get_content_items - procedure. It creates a -variable calledcontent_items
- in the caller's stack -frame. This is a multirow result set suitable for passing to an -index template, containing all the structured data necessary to -present a list of links to content pages, folders, extlinks, or -symlinks. By making use of the procedure's switches you may modify -the query results it produces: +of the etp::get_content_items + procedure. It +creates a variable calledcontent_items
+ in the +caller's stack frame. This is a multirow result set suitable +for passing to an index template, containing all the structured +data necessary to present a list of links to content pages, +folders, extlinks, or symlinks. By making use of the +procedure's switches you may modify the query results it +produces:
-attributes [list] - list of additional page attributes to return (when required for display)
-orderby [list] - list of columns on which to Index: openacs-4/packages/workflow/www/doc/developer-guide.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/developer-guide.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/developer-guide.adp 26 Aug 2015 18:03:35 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/developer-guide.adp 12 Sep 2016 06:10:32 -0000 1.3 @@ -1,10 +1,11 @@ -
{/doc/workflow {Workflow}} {Package Developer's Guide to Workflow} -Package Developer's Guide to Workflow +{/doc/workflow {Workflow}} {Package Developer's Guide to Workflow} +Package Developer's Guide to Workflow - Package Developer's Guide to Workflow
+Package Developer's Guide to Workflow
Workflow Documentation - : Package Developer's Guide + : Package Developer's +GuideBy Lars Pind
Introduction
@@ -13,91 +14,97 @@Examples of the object could be a bug in a bug tracker, a ticket in a ticket tracker, a content item in a content management system, a user contribution in a moderated forum/comments/whatever -application, a user's request for particpation in a +application, a user's request for particpation in a course/event/whatever.
-For example, when a new bug is submitted, someone's assigned to -fix it, and whoever submitted it is assigned to verify the fix and -close the bug. Once the bug's fixed, the submitter will get -notified, and the bug will wait in the 'resolved' state until the -submitter has verified and then closed the bug.
+For example, when a new bug is submitted, someone's assigned +to fix it, and whoever submitted it is assigned to verify the fix +and close the bug. Once the bug's fixed, the submitter will get +notified, and the bug will wait in the 'resolved' state +until the submitter has verified and then closed the bug.
In order to make use of workflow in your own application, here are the things you need to consider:
- Define your default process. The idea typically is to allow your end users to modify the process to suit their needs, but -you'll want eto provide a process which they can use as a starting -point.
- Identify, declare, and implement the callbacks that your +you'll want eto provide a process which they can use as a +starting point.
- Identify, declare, and implement the callbacks that your application will need.
- Write the code to set up the initial process, and to clone that -process for each package instance.
- Integrate workflow support into your application's API.
- Integrate workflow support into your application's user -interface.
- Integrate workflow into your application's queries
+process for each package instance.- Integrate workflow support into your application's +API.
- Integrate workflow support into your application's user +interface.
- Integrate workflow into your application's queries
-Let's first look at some concepts before getting into the +
Let's first look at some concepts before getting into the technicalities of how you actually do this. For a working example of building workflow-based applications, we recommend you take a look at bug-tracker.
Workflow Concepts
-What's in a workflow
+What's in a workflow
In its broadest, most conceptual sense, a workflow is defined as (and this does get a little hairy, so feel free to skip if you just want to start developing your applicaton):
-
- A number of actions that can be executed (open, edit, -comment, resolve, edit, publish, approve, reject, etc.) Whether or -not an action is enabled, meaning it can be executed at this -point in time, will depend on the current state of the -workflow. Executing an action may change the state of the -workflow.
- A definition of the possible states that the workflow -can be in. This could simply be an enumeration of states, such as -"Open", "Resolved", "Closed", in which case we're talking about a -"Finite State Machine" (because there's a finite number of possible +
- A number of actions that can be executed +(open, edit, comment, resolve, edit, publish, approve, reject, +etc.) Whether or not an action is enabled, meaning +it can be executed at this point in time, will depend on the +current state of the workflow. Executing an action +may change the state of the workflow.
- A definition of the possible states that the +workflow can be in. This could simply be an enumeration of states, +such as "Open", "Resolved", "Closed", +in which case we're talking about a "Finite State +Machine" (because there's a finite number of possible states). Other models, such as petri nets, have an infinite number of possible states. This makes things a lot more complicated, and -you don't need to bother since this first implementation of -workflow only supports finite state machines.
- People participate in a workflow through their role. For -a bug tracker, the roles could be "Submitter", "Resolver", and -"Tester". For a publication workflow, they'd be "Author", "Editor", -and "Publisher". Actions will be to roles, which in turn will be -linked to users or groups of users in a particular workflow case. -The reason we link people to actions through the concept of roles -is to allow you to specify that two or more actions should be done -by the same party. In bug-tracker, for example, the same user who -opened the bug should also close it.
+you don't need to bother since this first implementation of +workflow only supports finite state machines.- People participate in a workflow through their +role. For a bug tracker, the roles could be +"Submitter", "Resolver", and +"Tester". For a publication workflow, they'd be +"Author", "Editor", and "Publisher". +Actions will be to roles, which in turn will be linked to users or +groups of users in a particular workflow case. The reason we link +people to actions through the concept of roles is to allow you to +specify that two or more actions should be done by the same party. +In bug-tracker, for example, the same user who opened the bug +should also close it.
Finite State Machines (FSMs)
As mentioned, workflow is designed to support workflows based on any model, however the only model currently implemented is the finite state machine.
-The workflow API is designed so that whenver you're using +
The workflow API is designed so that whenver you're using features that are specific to finite state machines, the procedure name will include the letters "fsm" in the name, as in
-workflow::case::fsm::get
. -That way you'll know when you're hard-wiring a dependency on the -particular workflow model.It's considered good practice to only use non-fsm API calls, but -in practice, it can be hard to create good user experiences +That way you'll know when you're hard-wiring a dependency +on the particular workflow model.
+It's considered good practice to only use non-fsm API calls, +but in practice, it can be hard to create good user experiences without. So feel free.
-Notation:[Action] (State) {Role}
(State1) -> [Action1] -> +Notation:
[Action] (State) {Role}
(State1) -> [Action1] -> (State2) -> [Action2] -> (State3)
Cases
-So much for defining the workflow. When you start "running" your -workflow, that's called a workflow case, or simply a case. A -case is concerned with a particular object, it's always in a -particular state, and it has specific people or groups assigned to -its different roles.
+So much for defining the workflow. When you start +"running" your workflow, that's called a +workflow case, or simply a case. A case is +concerned with a particular object, it's always in a particular +state, and it has specific people or groups assigned to its +different roles.
In-flow and out-of-flow
-When defining actions, we differentiate between in-flow and -out-of-flow. In-flow refers to the normal idealized flow of the -workflow, out-of-flow are the rest. Concretely what it means is -that if you're assigned to an in-flow action, we'll bug you about -it through notifications, and potentially get mad at you if you -don't come and do something to get the workflow moving along. We -don't do that with out-of-flow actions. So we'll send a -notification that says "Please come back and resolve this bug", -whereas we'll not notify everybody who are allowed to comment -saying "Please come back and comment on this bug".
+When defining actions, we differentiate between in-flow +and out-of-flow. In-flow refers to the normal idealized +flow of the workflow, out-of-flow are the rest. Concretely what it +means is that if you're assigned to an in-flow action, +we'll bug you about it through notifications, and potentially +get mad at you if you don't come and do something to get the +workflow moving along. We don't do that with out-of-flow +actions. So we'll send a notification that says "Please +come back and resolve this bug", whereas we'll not notify +everybody who are allowed to comment saying "Please come back +and comment on this bug".
For bug-tracker, the normal flow (in-flow) is this:
(Open) -> [Resolve] -> (Resolved) -> [Close] -> (Closed)@@ -106,53 +113,58 @@ which throw you back to the (Open) state. And finally [Resolved] is in-flow when in the (Open) state, but out-of-flow when in the (Resolved) state, meaning that you can re-resolve a bug if you need -to, but you're not required to. +to, but you're not required to.In-flow and out-of-flow depends on the action, the state, and -the user's role in the case. For example, it might be that users in -the {Submitter} role are allowed to resolve their own bugs, but the -[Resolve] action will still only be considered in-flow to people in -the {Assignee} or {Resolver} role.
+the user's role in the case. For example, it might be that +users in the {Submitter} role are allowed to resolve their own +bugs, but the [Resolve] action will still only be considered +in-flow to people in the {Assignee} or {Resolver} role.The Six Steps Conceptually
The recommended way a workflow is linked to an application is this: As part of developing your application, you define your -default workflow, which will be used as a template for -customization by the users of your applications. This workflow will -be installed using the APM after-install callback, and will be -linked to your application's package-key.
+default workflow, which will be used as a template +for customization by the users of your applications. This workflow +will be installed using the APM after-install callback, and will be +linked to your application's package-key.Then when a new instance of your application is created, your -default workflow will be cloned, and the clone linked to the -new instance, so that your users can customize the workflow for -each instance of your application individually. The default copy -installed along with your package is never actually used except for -cloning when creating a new instance. This means that your users -can customize this deafult workflow, and the modified version will -serve as the boilerplate for all new package instances.
-In order to integrate workflow with your application, you'll -want to implement one or more of the callback service -contracts. These can do things like determine default assignees -based on certain data in your application, get information about -your application's object for use when sending out notifications, -or perform side-effects, such as actually changing the publication -state of a content item when you execute the [Publish] action.
-When integrating the workflow with your application's user -experience, what workflow will give you is essentially the list -of actions that the given user can perform on the given object -at the given time. In bug-tracker, for example, bug-tracker takes -care of displaying the form displaying and editing a bug, while -workflow takes care of displaying the buttons that say [Comment], -[Edit], [Resolve], [Reopen], [Close], etc., along the bottom of the -form. Workflow also has a place to store which form elements should -be opened for editing depending on the action being executed.
+default workflow will be cloned, and the clone +linked to the new instance, so that your users can customize the +workflow for each instance of your application individually. The +default copy installed along with your package is never actually +used except for cloning when creating a new instance. This means +that your users can customize this deafult workflow, and the +modified version will serve as the boilerplate for all new package +instances. +In order to integrate workflow with your application, you'll +want to implement one or more of the callback service +contracts. These can do things like determine default +assignees based on certain data in your application, get +information about your application's object for use when +sending out notifications, or perform side-effects, such as +actually changing the publication state of a content item when you +execute the [Publish] action.
+When integrating the workflow with your application's user +experience, what workflow will give you is essentially the +list of actions that the given user can perform on +the given object at the given time. In bug-tracker, for example, +bug-tracker takes care of displaying the form displaying and +editing a bug, while workflow takes care of displaying the buttons +that say [Comment], [Edit], [Resolve], [Reopen], [Close], etc., +along the bottom of the form. Workflow also has a place to store +which form elements should be opened for editing depending on the +action being executed.
Your application should typically have an API for creating a new -object, editing an object, etc. This application object API will -need to be workflow-aware, so when a new object is created, a -new workflow case will be started as well. And when the object's -edited, that should generally only happen through a workflow -action, so that needs to be taken into account as well.
-The final step is integrating workflow into your application's -queries when you want to filter an object listing/count based on -the workflow state. This is the only place where you'll -directly be dependent on the workflow data model.
+object, editing an object, etc. This application object API +will need to be workflow-aware, so when a new object is +created, a new workflow case will be started as well. And when the +object's edited, that should generally only happen through a +workflow action, so that needs to be taken into account as +well. +The final step is integrating workflow into your +application's queries when you want to filter an object +listing/count based on the workflow state. This is the +only place where you'll directly be dependent on the workflow +data model.
Defining Your Process (FSM)
The current version of workflow only supports workflows based on a finite state machine (FSM). Support for other models, such as @@ -163,22 +175,22 @@
You define a new workflow like this:
set spec { - workflow-short-name { + workflow-short-name { ... roles { - role-short-name { + role-short-name { ... } ... } states { - state-short-name { + state-short-name { ... } ... } actions { - action-short-name { + action-short-name { ... } ... @@ -199,9 +211,9 @@Finally, you can also refer states, roles, and actions in your application using short names, although this creates a dependency in your application on a particular form of the workflow, and -there's currently no mechanism for ensuring that your workflow -contains the states, roles, and actions you'd refer to. This is on -the todo-list.
+there's currently no mechanism for ensuring that your workflow +contains the states, roles, and actions you'd refer to. This is +on the todo-list.Workflow
These are the attributes you can specify for the workflow itself:
@@ -214,29 +226,32 @@package_key
The package that defined this workflow. object_type
The parent object type which this workflow can be applied to. -If your workflow applies to any object, say 'acs_object'. This is -used in relation to callbacks when we build the user interface for -defining workflows. More on this in the section on callbacks. +If your workflow applies to any object, say 'acs_object'. +This is used in relation to callbacks when we build the user +interface for defining workflows. More on this in the section on +callbacks.callbacks
Callbacks that apply to the whole workflow. If you add side-effect callbacks, these are executed every time any action is executed. - roles
Denotes the section of the spec that defines the workflow's + roles
Denotes the section of the spec that defines the workflow's roles. - states
Denotes the section of the spec that defines the workflow's + states
Denotes the section of the spec that defines the workflow's states. - actions
Denotes the section of the spec that defines the workflow's + actions
Denotes the section of the spec that defines the workflow's actions. -Internationalization Note:When we make workflow +Internationalization +Note:
When we make workflow internationalized for OpenACS 5.0, pretty names will contain -message keys in the form "#message-key#". More about this in -the package developer's guide to internationalization.
+message keys in the form "#message-key#". More +about this in the package developer's guide to +internationalization.Roles
Attributes for roles:
@@ -273,86 +288,89 @@pretty_name
Name used in the user interface. - hide_fields
A tcl list of form elements/object attributes that don't make -sense in this state. In bug-tracker, the element "Fixed in version" -doesn't make sense when the bug is (Open), and thus not yet fixed. -It's currently up to your application to do incorporate this into -your application. +hide_fields
A tcl list of form elements/object attributes that don't +make sense in this state. In bug-tracker, the element "Fixed +in version" doesn't make sense when the bug is (Open), and +thus not yet fixed. It's currently up to your application to do +incorporate this into your application. Actions
Actions are what the workflow allows you to do to your object.
-Terminology:--
- Enabled
- The action is allowed to be executed in the workflow's current -state.
- Allowed
- The given user is allowed to execute the action given his +Terminology:
+
- Enabled
- The action is allowed to be executed in the workflow's +current state.
- Allowed
- The given user is allowed to execute the action given his current relationship to the workflow case and the object.
- Assigned
- The same as allowed, but the action is in-flow for this user.
- Available
- The action is both enabled and allowed for this user.
Some actions will always be enabled. In bug-tracker, for -example, we have [Comment] and [Edit] actions, which are always -allowed, regardless of whether the bug is (Open), (Resolved), or -(Closed).
-Other actions, however, will only be enabled in certain -states. In bug-tracker, for example, the [Close] action is only -available in the (Resolved) state.
-Another distinction is that some actions change the state, -and others do not. [Comment] and [Edit], for example, do not. -[Resolve], [Close], and [Reopen] do. For an FSM, when an action -changes the state, you simply specify what the new state should -be.
-There's a special action called the initial action. This -is implicitly executed when a new case is started for this -workflow, and must always specify the "new_state" attribute to -define which state new cases start out in.
+Some actions will always be enabled. In +bug-tracker, for example, we have [Comment] and [Edit] actions, +which are always allowed, regardless of whether the bug is (Open), +(Resolved), or (Closed).
+Other actions, however, will only be enabled in certain +states. In bug-tracker, for example, the [Close] action is +only available in the (Resolved) state.
+Another distinction is that some actions change the +state, and others do not. [Comment] and [Edit], for +example, do not. [Resolve], [Close], and [Reopen] do. For an FSM, +when an action changes the state, you simply specify what the new +state should be.
+There's a special action called the initial +action. This is implicitly executed when a new case is +started for this workflow, and must always specify the +"new_state" attribute to define which state new cases +start out in.
Attributes for actions:
Action Attributes Attribute Description pretty_name
Name used in the user interface. - pretty_past_tense
This is used in the case log to say "<pretty_past_teense> -by <user> on <date>", for example "Resolved by Jeff -Davis on April 15, 2003". +pretty_past_tense
This is used in the case log to say +"<pretty_past_teense> by <user> on +<date>", for example "Resolved by Jeff Davis on +April 15, 2003". new_state
The short_name of the state this action puts the case into. -Leave out if the action shouldn't change the state. +Leave out if the action shouldn't change the state.- initial_action_p
Say 't' if this is the initial action. Leave out or set to 'f' -otherwise. +initial_action_p
Say 't' if this is the initial action. Leave out or set +to 'f' otherwise. allowed_roles
A list of roles that are allowed but not assigned to perform this action. assigned_role
A single role which is assigned to this action. privileges
A list of privileges. Users who have been granted one of these -privileges on the case's object will be allowed to execute this +privileges on the case's object will be allowed to execute this action. - always_enabled_p
Say 't' if this action should be enabled regardless of the -case's current state. Say 'f' or leave out otherwise. +always_enabled_p
Say 't' if this action should be enabled regardless of +the case's current state. Say 'f' or leave out +otherwise. enabled_states
If not always enabled, enumerate the states in which this -action is enabled but not assigned. +action is enabled but not assigned.- assigned_states
Enumerate the states in which this action is enabled and -assigned. +assigned_states
Enumerate the states in which this action is enabled +and assigned. edit_fields
A tcl list of fields which should be opened for editing when -the user is performing this action. Again, it's up to the +the user is performing this action. Again, it's up to the application to act on this. callbacks
Side-effect callbacks which are executed when this action is executed. Putting A Workflow Together
-When you put this all together, here's a real live example of -what defining a workflow could look like:
+When you put this all together, here's a real live example +of what defining a workflow could look like:
ad_proc -private bug_tracker::bug::workflow_create {} { Create the 'bug' workflow for bug-tracker @@ -473,36 +491,36 @@Defining Callbacks
There are a number of different types of callbacks, each of which applies to different workflow items (workflows, roles, -states, actions). They're all defined as service contracts.
+states, actions). They're all defined as service contracts.In order to make use of them, your application will need to implement these service contracts, and register the implementation -with the relevant workflow item through the 'callbacks' attribute -in the spec above.
-Here are the types of callbacks defined, how they're used, and -the workflow items they apply to.
+with the relevant workflow item through the 'callbacks' +attribute in the spec above. +Here are the types of callbacks defined, how they're used, +and the workflow items they apply to.
Service contracts Service Contract Applies To Description Workflow.Role_DefaultAssignees
Roles Used to get the default assignees for a role. Called for all roles when a case is started. Also called for roles with no assignees, when that role is assigned to an action. Should return a -list of party_id's. +list of party_id's.Workflow.Role_AssigneePickList
Roles Used when the users wants to reassign a role to populate a drop-down list of the most likely users/groups to assign this role to. Should return less than 25 users/groups. Should return a list -of party_id's. +of party_id's.- Workflow.Role_AssigneeSubQuery
Roles A subquery used to limit the scope of the user's search for a -new assignee for a role. Could typically be used to limit the + Workflow.Role_AssigneeSubQuery
Roles A subquery used to limit the scope of the user's search for +a new assignee for a role. Could typically be used to limit the search to members of a particular group, organizers of a particular event, etc. Should return a subquery ready to be included in the from-clause of a query, which will be used when querying for users, for example -'(select * from parties where ...)', (sub-selects must be in -parenthesis), or simply 'cc_users' or 'parties'. Defaults to -'cc_users'.
+'(select * from parties where ...)', (sub-selects must be +in parenthesis), or simply 'cc_users' or 'parties'. +Defaults to 'cc_users'.Workflow.Action_SideEffect
Workflows, Actions This is executed whenever the given action is executed. If @@ -517,7 +535,8 @@ Workflow.ActivityLog_FormatTitle
Workflows Used to format the title of the case log. In bug-tracker, this is used to get the resolution code displayed in the case log as -"Resolved (Fixed)" or "Resolved (Not Reproducable)". +"Resolved (Fixed)" or "Resolved (Not +Reproducable)". The implementation should return the text string that should go into the parenthesis. The parenthesis are automatically added if the returned string is non-empty.
@@ -542,11 +561,11 @@ operationsOperation Input Output Description - GetObjectType None object_type:string Get the object type for which this implementation is valid. If -your implementation is valid for any object, return 'acs_object', -otherwise return the object type. +GetObjectType None object_type:string Get the object type for which this implementation is valid. If +your implementation is valid for any object, return +'acs_object', otherwise return the object type. - @@ -571,7 +590,7 @@ role_id:integerGetPrettyName None pretty_name:string Get the pretty name of this implementation. This will be used + GetPrettyName None pretty_name:string Get the pretty name of this implementation. This will be used in the user interface to let the workflow designer pick which implementation to use. subquery:string Workflow.Action_SideEffect DoSideEffect case_id:integer object_id:integer action_id:integer -entry_id:integer None +entry_id:integerNone Workflow.ActivityLog_FormatTitle GetTitle case_id:integer object_id:integer action_id:integer entry_id:integer data_arraylist:string,multiple title:string @@ -594,34 +613,36 @@ be retrieved later using workflow::case::get_log_data_by_key, and workflow::case::get_log_data.Installing and Instantiating (APM Tcl Callbacks)
-Here are the workflow-related operations that you'll typically -want your application to do from the APM Tcl Callbacks:
+Here are the workflow-related operations that you'll +typically want your application to do from the APM Tcl +Callbacks:
- after-install
- Register service contract implementations
- Create default workflow (
workflow::fsm::new_from_spec
)- before-uninstall
- Delete default workflow (
workflow::delete
)- Unregister service contract implementations
- before-upgrade
- Add new service contract implementations
- Add new workflows
- Make changes to existing default workflows (if the installed -verison is not modified) (this isn't yet supported on the workflow -API)
+verison is not modified) (this isn't yet supported on the +workflow API)- after-instantiate
- Clone default workflow to create a new workflow attached to the instance (
workflow::fsm::clone
)- before-uninstantiate
- Delete the workflow attached to the instance (
workflow::delete
)To see what this could look like in practice, check out
-packages/bug-tracker/tcl/install-procs.tcl
.Integrating With Your Application's API
+Integrating With Your Application's API
Newer applications will define a namespace for each of the -objects it defines, which will contain procs like "get", "new", -"delete", etc., to manipulate these objects.
+objects it defines, which will contain procs like "get", +"new", "delete", etc., to manipulate these +objects.Given such a setup, here are the procs that you want to integrate workflow into for your workflow-integrated objects. For a real-life example, see
packages/bug-tracker/tcl/bug-procs.tcl
and search for "workflow::".-
-- get
- In addition to your application's object data, you'll want to -call
workflow::case::get_id
+- get
- In addition to your application's object data, you'll +want to call
workflow::case::get_id
to get the case_id for your object, and then eitherworkflow::case::get
orworkflow::case::fsm::get
in order to get state information from workflow to include in the @@ -639,52 +660,53 @@ (entry_id is for double-click protection).First, you should update your application object data as normal. -Then you'll probably want to use
workflow::case::get_id
+Then you'll probably want to useworkflow::case::get_id
to find the case_id. If you have assignment integrated in your -form, you'll want to callworkflow::case::role::assign
-to pass these on to workflow, and finally you'll sayworkflow::case::action::execute
+form, you'll want to callworkflow::case::role::assign
+to pass these on to workflow, and finally you'll say +workflow::case::action::execute
to execute the action, including state changes, side-effects, and notifications.Integrating With Your Application's User Interface
-Usually, you'll want one page that lists all the objects in your -package instance, and another that lets the user view/edit one +
Integrating With Your Application's User Interface
+Usually, you'll want one page that lists all the objects in +your package instance, and another that lets the user view/edit one object, called the object form page. This section is about the object form page, the next section is about the object listing page.
For a real-life example, look at
-packages/bug-tracker/www/bug.tcl
. You may want to have that handy while reading through this.We're hoping to make some streamlining of both ad_form and +
We're hoping to make some streamlining of both ad_form and workflow to make this form page integration even easier at some point. But no promises.
Use
workflow::case::get_id
to get the case_id.If you want buttons along the bottom of the form like -bug-tracker, use the new 'action' feature of the form builder. What -you do is pass a list of possible actions to the form builder as a -list-of-lists with { label value }. These will be displayed as -buttons at the bottom of the form.
+bug-tracker, use the new 'action' feature of the form +builder. What you do is pass a list of possible actions to the form +builder as a list-of-lists with { label value }. These will be +displayed as buttons at the bottom of the form.When one of these buttons are clicked, the form will be in edit-mode, and you can use
-form get_action
to get the value of the action chosen.So up top, you'll want to ask the form if an action is in +
So up top, you'll want to ask the form if an action is in progress, and which one it is, by saying
+get_action form-id]. If no action is in progress +this will return the empty string.set action_id [form -get_action form-id]
. If no action is in progress this -will return the empty string.Then you should check that this action is currently available to this user by saying
workflow::case::action::available_p
.To get the currently available actions so you can offer them to the user, use
-workflow::case::get_available_actions
-which will return the action_id's, thenworkflow::action::get
+which will return the action_id's, thenworkflow::action::get
to get the details about each of the actions.If you're using
ad_form
, and you want only one +If you're using
+widgets. It'll do anad_form
, and you want only one assignee per role, and you want assignment integrated with your form, useworkflow::case::role::add_assignee_widgets
to add the -widgets. It'll do anad_form -extend
, so they'll -appear at the point at which you call this proc.ad_form -extend
, so +they'll appear at the point at which you call this proc.To set the editable fields as defined for the action, do this:
@@ -694,16 +716,16 @@ } }-Similarly, on submit, you'll want to set the value of the +
Similarly, on submit, you'll want to set the value of the editable fields.
To populate values of the assignee widgets, use
workflow::case::role::set_assignee_values
in youron_request
block.To add the case log to the comment field, use
-workflow::case::get_activity_html
and feed it to thebefore_html
property of a textarea.Integrating With Your Application's Queries
-Here's an example of how the bug-tracker integrates with +
Integrating With Your Application's Queries
+Here's an example of how the bug-tracker integrates with workflow for nformation about the current state of bugs.
select b.bug_id, @@ -741,10 +763,11 @@Note the outer join to get the assignee(s). The joins to get information about the current state is straight-forward.
Good Luck!
-That's all I think you'll need to know to start developing -workflow-enabled applications.
-Let me know how it goes, or of something's missing, by posting -on the OpenACS Forums.
+That's all I think you'll need to know to start +developing workflow-enabled applications.
+Let me know how it goes, or of something's missing, by +posting on the OpenACS +Forums.
lars\@pinds.com 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 -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 @@
- Actions will no longer be atomic. An action can be "in progress" for a long time, while the child workflow(s) completes.
- We will introduce an uber-state of a case, which can be -'active', 'completed', 'canceled', or 'suspended'.
- When the action gets enabled, a callback will create child +'active', 'completed', 'canceled', or +'suspended'.
- When the action gets enabled, a callback will create child cases linked to this particular enabled action.
- 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'.
- If the action should ever get un-enabled, a callback will +remaining 'active' child cases will be marked +'canceled'.
- If the action should ever get un-enabled, a callback will cancel all remaining 'active' child cases.
- If the action becomes enabled again, we will create new child cases.
- A case which is a child of another case cannot leave the -'completed' or 'canceled' state, unless its parent enabled action -is still enabled.
+'completed' or 'canceled' state, unless its parent +enabled action is still enabled.Data Model
@@ -134,44 +137,46 @@ be in one of the following:
- -Enabled. The action is currently enabled.
- -Running. The action is currently running, specifically -meaning that there are active child cases. XXXXXXXXXXXXXX do we -need this?
- -Completed. The action has completed executing. The row -will still stay around so we have a history of what was executed -when and we're able to count the number of times a given action was -executed.
- -Canceled. The action was enabled, but the case's state -changed before the action was triggered. (Note: This is not -necessary, we could just delete the row instead.)
- -Refused. The action had its database-driven +Enabled. The action is currently enabled.
- +Running. The action is currently running, +specifically meaning that there are active child cases. +XXXXXXXXXXXXXX do we need this?
- +Completed. The action has completed executing. +The row will still stay around so we have a history of what was +executed when and we're able to count the number of times a +given action was executed.
- +Canceled. The action was enabled, but the +case's state changed before the action was triggered. (Note: +This is not necessary, we could just delete the row instead.)
- +Refused. The action had its database-driven preconditions for being enabled met (e.g. enabled-in-states for FSM, input places with tokens in Petri, plus dependencies on other -tasks met), but the "CanEnableP" callback refused to let the action -become enabled. (Note: This is not necessary, we could just delete -the row instead.)
+tasks met), but the "CanEnableP" callback refused to let +the action become enabled. (Note: This is not necessary, we could +just delete the row instead.)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 @@
- case::enabled_actions -case_id
-
- Find the case state (open)
- Find the actions enabled in this state (ask client, abort)
- "abort" is final, put it on the list.
- "ask client" has a child; look in workflow_enabled_actions for -the state
- 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".
- Look in case_enabled_actions.state_id for case_id #1 and +
- Find the case state (open)
- Find the actions enabled in this state (ask client, abort)
- "abort" is final, put it on the list.
- "ask client" has a child; look in +workflow_enabled_actions for the state
- 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".
- Look in case_enabled_actions.state_id for case_id #1 and action_id "ask client" for the substate (ac-asking).
- Find the enabled actions in the ac-asking state (ac-ask).
- ac-ask is final, put it on the list
- case::action::execute -case_id -action_id
-
- The question is which state to change.
- Find the action's parent_action_id
- If null, then change cases.state_id
- Otherwise, change case_enabled_actions.state_id.
+- The question is which state to change.
- Find the action's parent_action_id
- If null, then change cases.state_id
- Otherwise, change case_enabled_actions.state_id.
- Which roles are assigned/allowed to perform an action? Unchanged from current design.
- Which roles do a user play? Unchanged from current design.
- 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 @@-
- case::action::execute -case_id #1 -action_id "init"
-
- set cases.state_id to init's new_state ("open")
- call case::state_changed_handler -case_id #1 +
- set cases.state_id to init's new_state +("open")
- call case::state_changed_handler -case_id #1
- find enabled actions by looking in state_action_map under the current state (open) => (rev & op)
- 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 @@- It has workflow_actions.always_enabled_p = true
- There is a row in workflow_fsm_action_en_in_st for the current state and the given action.
- Assigned? It's assigned if: trigger_type = 'user', state-action -map has assigned_p = true, it has an assigned_role.
- What's the assigned role? (For dynamic actions, there may not -be an assigend role, if the assignees are in the +
- Assigned? It's assigned if: trigger_type = 'user', +state-action map has assigned_p = true, it has an +assigned_role.
- 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.
- Who are the assignees?
@@ -519,7 +527,7 @@
- Find the parties:
- If there are rows in workflow_case_action_assignees for the -action, that's the parties.
- Otherwise, the parties are in the workflow_case_role_map for +action, that's the parties.
- Otherwise, the parties are in the workflow_case_role_map for role workflow_actions.assigned_role.
- Use party_approved_member_map to find the users.
@@ -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) ); -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.
- If there are any rows in workflow_case_enabled_actions for this -case with enabled_state 'running', no actions can be enabled, the -action is not enabled.
- Is the model-specific precondition met, e.g. are we in one of -the action's enabled-in states? If not, the action is not +case with enabled_state 'running', no actions can be +enabled, the action is not enabled.
- Is the model-specific precondition met, e.g. are we in one of +the action's enabled-in states? If not, the action is not enabled.
- Are other preconditions met, e.g. if the action is gated on other actions having executed a minimum number of times, or itself having executed a maximum number fo times? If not, the action is @@ -892,33 +908,35 @@
If the action is enabled:
- If there are any rows in workflow_case_enabled_actions for this -action with enabled_state of 'enabled', the action was already -enabled before. Quit.
- Otherwise start the "Enabled Action Logic" below.
+action with enabled_state of 'enabled', the action was +already enabled before. Quit.- Otherwise start the "Enabled Action Logic" +below.
If the action is not enabled.
+action with enabled_state of 'enabled', the action was +enabled before. Update the row to set 'enabled_state' to +'canceled'.
- If there are any rows in workflow_case_enabled_actions for this -action with enabled_state of 'enabled', the action was enabled -before. Update the row to set 'enabled_state' to 'canceled'.
Enabled Action Logic
Executed when an action which was previously not enabled becomes enabled.
- 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.
- 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.
- If the action has a timeout of 0, then call workflow::case::action::execute and quit.
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.
- If the action has any child cases, these will be marked +longer enabled, because the workflow's state was changed by +some other action.
- If the action has any child cases, these will be marked canceled.
Action Execute Logic
Executed when an enabled action is triggered.
@@ -929,7 +947,8 @@ gets to determine whether the parent action is now complete and should fire.
- 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.
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
@@ -954,14 +973,15 @@ should avoid sending out duplicate notifications. How?workflow_action_fsm_output_map
to determine which state to move to.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. +rows in this table, so we don't keep executing the query in +that case.
Index: openacs-4/packages/workflow/www/doc/index.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/index.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/index.adp 26 Aug 2015 18:03:36 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/index.adp 12 Sep 2016 06:10:32 -0000 1.3 @@ -10,11 +10,11 @@The workflow package provides a service to keep track of a process involving multiple people around some object. Workflow keeps track of the process you wish to follow, of where you -currently are in the process (the current state), and who's +currently are in the process (the current state), and who's supposed to do what.
-
- Mockups of alternatives for compound -workflows
- Package Developer's Guide to +workflows
- Package Developer's Guide to Workflow
- This is for developers developing applications that should take advantage of the workflow service.
- Functional Specification
- This is the document we wrote before implementing workflow specifying how we intended to implement the package then. It is @@ -27,33 +27,35 @@
Version History
- -1.0d4 Resolved conflicts with old acs-workflow package, -so they install side by side. (May 11, 2003)
- -1.0d3 Added Tcl API workflow::case::delete ; fixed bug -in PL/SQL implementation of +1.0d4 Resolved conflicts with old acs-workflow +package, so they install side by side. (May 11, 2003)
- +1.0d3 Added Tcl API workflow::case::delete ; +fixed bug in PL/SQL implementation of workflow_case.delete/workflow_case__delete ; added \@see to workflow::case::insert.
- -1.0d2 Completed package developer's guide. Added --action_id switch to workflow::case::get_activity_html.
- -1.0d1 Bumped up the version number to 1.0 to reflect the -fact that this package is actually at a steady state and fully -useful as is. Also added a little API and cleaned up things a bit, -the kind of things you learn while writing the documentation.
- -0.2d2 First version released along with OpenACS -4.6.2.
+1.0d2 Completed package developer's guide. +Added -action_id switch to workflow::case::get_activity_html.- +1.0d1 Bumped up the version number to 1.0 to +reflect the fact that this package is actually at a steady state +and fully useful as is. Also added a little API and cleaned up +things a bit, the kind of things you learn while writing the +documentation.
- +0.2d2 First version released along with +OpenACS 4.6.2.
Todo
- Internationalization.
- Add API for modifying live workflows, including ensuring that -the modifications are always safe (i.e. you can't delete a state -that's used.)
- Add a user interface for defining workflows.
- Add a user interface for monitoring workflows and bulk changing +the modifications are always safe (i.e. you can't delete a +state that's used.)
- Add a user interface for defining workflows.
- Add a user interface for monitoring workflows and bulk changing the state of workflows.
- Periodically notify people of their outstanding assigned actions.
- Add a task list user interface, either as part of the Workflow package, or as a separate package.
- Add support for petri nets and other models.
- Add timing of actions, deadlines, and integrate those with calendar.
- Application integration with certain states and actions. For -example, in bug-tracker, we treat the "Open" and "Closed" states -specially. We also treat the "Resolve" action specially. Should be -possible to define this link.
- Add workflow variants, so you can ship your application with +example, in bug-tracker, we treat the "Open" and +"Closed" states specially. We also treat the +"Resolve" action specially. Should be possible to define +this link.
- Add workflow variants, so you can ship your application with multiple default implementations of the same workflow and let the user choose between the available variants (e.g. simple approval vs. multiple approval variants, choice of triage and Q&A steps Index: openacs-4/packages/workflow/www/doc/specification.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/specification.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/specification.adp 26 Aug 2015 18:03:36 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/specification.adp 12 Sep 2016 06:10:32 -0000 1.3 @@ -11,29 +11,29 @@
Overview
I recently built a typical workflow-based application, bug-tracker, and decided against using the acs-workflow package that I myself built. -That's not a good recommendation. We need to fix that.
+That's not a good recommendation. We need to fix that.Goals
The goal is to implement a workflow package that:
- Is ideally suited for at least 3 use-cases: Bug-tracker, -CMS-style publication process, and simple approval.
- Gives people a usable UI.
- Can be used entirely through a clean Tcl API
- Doesn't require people to learn Petri Nets
- Is much easier for developers to use in their applications
+CMS-style publication process, and simple approval.- Gives people a usable UI.
- Can be used entirely through a clean Tcl API
- Doesn't require people to learn Petri Nets
- Is much easier for developers to use in their applications
Gripes with the current acs-workflow:
- Engine is in PL/SQL, not in Tcl, which makes it hard to write callbacks.
- Petri net is just too complicated for people to learn how to use, and there are too many ways for them to mess up. The primary -benefit is parallel routing, which I've never actually come across -any applications that seriously needed.
- The UI sucks, and it's really hard to use workflow without +benefit is parallel routing, which I've never actually come +across any applications that seriously needed.
- The UI sucks, and it's really hard to use workflow without using the interface.
- Graphviz is actually not terribly great at displaying workflow nets, as it tries to fit everything into a circle, whereas most of the time with workflow what you really want is to have it appear as -a nice sequence of events with some loops back here and there.
- It's too restrictive and inflexible. It's hard to change your -mind and go back, or to manually bash the case into a certain -state.
- Also, it was never finished. Unfortunately, finishing it would +a nice sequence of events with some loops back here and there.
- It's too restrictive and inflexible. It's hard to +change your mind and go back, or to manually bash the case into a +certain state.
- Also, it was never finished. Unfortunately, finishing it would be a tremendous amount of work for limited benefit.
- Data model has some big issues: Workflows are object types. -Workflows aren't tied to packages, and the context idea isn't -working very well. It's based on petri nets.
+Workflows aren't tied to packages, and the context idea +isn't working very well. It's based on petri nets.Finite State Machine
Take bug-tracker as an example. The bug-tracker workflow and @@ -50,75 +50,77 @@ state.
I've finally come to the realization that we'll be better off in -the short to medium term with just a well-functioning +
I've finally come to the realization that we'll be +better off in the short to medium term with just a well-functioning implementation of a finite state machine based workflow module. In general, a workflow consists of a finite set of states, and a finite set of actions. Each action has a set of states in which -it's enabled, or it can be always enabled in all states. And each -action can cause the workflow case to move into a new state, or it -can leave the state unaltered.
+it's enabled, or it can be always enabled in all states. And +each action can cause the workflow case to move into a new state, +or it can leave the state unaltered.Note that the ability to have an action enabled in more than one state is a convenience, and not part of the mathematical model of -finite state machines. Likeways with actions that don't change the -state. But it's mighty convenient, as you've seen illustrated by -the bug-tracker example above.
+finite state machines. Likeways with actions that don't change +the state. But it's mighty convenient, as you've seen +illustrated by the bug-tracker example above.Workflows
A workflow is a set of roles, actions, and states, and their relations.
A workflow is associated with an object, which would typically be one of the following:
-
-- A package type: This is the default workflow for the -bug-tracker package.
- A package instance: This is the default workflow for a -particular instance of the bug-tracker package.
- A single case: Future versions could allow you to -customize your workflow for a particular bug or content story.
+- A package type: This is the default workflow +for the bug-tracker package.
- A package instance: This is the default +workflow for a particular instance of the bug-tracker package.
- A single case: Future versions could allow you +to customize your workflow for a particular bug or content +story.
There's also a short_name, so you can easily distinguish between -multiple workflows for the same package, e.g., one for handling the -bug, and another for approving creation of new versions or -components in the bug-tracker.
-A workflow is also associated with an object type. The -reason for this is that assignments will frequently depend on -attributes of the specific object for the case. In bug-tracker, for -example, the default assignee for a bug will be the maintainer of -the component in which the bug has been found. The bug-tracker will -provide one or more assignment service contract implementations, -which, given the bug_id will give you the component maintainer, or -the project maintainer. These can be used to set up automatic -assignment through a nice web-based user interface.
+There's also a short_name, so you can easily distinguish +between multiple workflows for the same package, e.g., one for +handling the bug, and another for approving creation of new +versions or components in the bug-tracker.
+A workflow is also associated with an object +type. The reason for this is that assignments will +frequently depend on attributes of the specific object for the +case. In bug-tracker, for example, the default assignee for a bug +will be the maintainer of the component in which the bug has been +found. The bug-tracker will provide one or more assignment service +contract implementations, which, given the bug_id will give you the +component maintainer, or the project maintainer. These can be used +to set up automatic assignment through a nice web-based user +interface.
When you create a new workflow case for a specific object, we will check that this object descends from the object type for which the workflow is for. If your workflow is general enough to work for all object types, then you can simply associate it with the common ancestor of all objects, 'acs_object'.
When you create a new instance of the bug-tracker, we would -make a copy of the default bug-tracker workflow for your -particular package, so that you can make local changes to the -workflow, to the assignments, etc.
-A workflow can have side-effects, which fire when -any action is triggered on that workflow. These fire after -the specific actions. See more under action side-effects. These are -declared as a standard "Action_SideEffect" service contract -implementation.
+make a copy of the default bug-tracker workflow +for your particular package, so that you can make local changes to +the workflow, to the assignments, etc. +A workflow can have side-effects, which fire +when any action is triggered on that workflow. +These fire after the specific actions. See more under action +side-effects. These are declared as a standard +"Action_SideEffect" service contract implementation.
Another service contract on the workflow level is the -activity log entry title formatting contract. Using a -side-effect callback, you can store additional key/value pairs in -the activity log. You can use the title formatting service contract -to pull these out, along with any other data you like, and use them -to format the title of the log entry for display.
+activity log entry title formatting contract. +Using a side-effect callback, you can store additional key/value +pairs in the activity log. You can use the title formatting service +contract to pull these out, along with any other data you like, and +use them to format the title of the log entry for display.Roles
A workflow has a set of roles. For bug-tracker, this is Submitter, and Assignee. More complex bug-tracker workflows, could -add Triager and Tester. For a typical pulication workflow, you'd -have Author, Editor, and Publisher. Normally, you'd always include -an 'Administrator' role.
+add Triager and Tester. For a typical pulication workflow, +you'd have Author, Editor, and Publisher. Normally, you'd +always include an 'Administrator' role.Each role is associated wtih one or more actions in the -workflow. The assignee is assigned to the 'Resolve' action, but -also has permission to perform the Edit, Comment and Reassign -actions. The submitter is assigned to the 'Close' action, but also -has permission to 'Reopen', 'Edit', 'Comment', and possibly -'Reassign'.
+workflow. The assignee is assigned to the 'Resolve' action, +but also has permission to perform the Edit, Comment and Reassign +actions. The submitter is assigned to the 'Close' action, +but also has permission to 'Reopen', 'Edit', +'Comment', and possibly 'Reassign'.The idea behind introducing roles is that you do not want to go through the bother of assigning each action individually, when normally they are grouped together.
@@ -127,57 +129,60 @@ reassigned at any time.Default Assignment
The tricky part, however, is the rules saying who should be -assigned by default, or who can be assigned to this role. -First, let's look at how the default assignees can be -determined.
+assigned by default, or who can be assigned to this role. +First, let's look at how the default assignees +can be determined.
- If you only have one publisher, then you simply want to assign -the Publisher role to that publisher always. That's called a -static assignment, and the information about that current -assignee is kept in the workflow data model.
- The Submitter role in bug-tracker, you want to assign to -whoever opened the bug, namely the object creation -user.
- The Assignee role in bug-tracker is given to the maintainer of +the Publisher role to that publisher always. That's called a +static assignment, and the information about that +current assignee is kept in the workflow data model.
- The Submitter role in bug-tracker, you want to assign to +whoever opened the bug, namely the object creation +user.
- The Assignee role in bug-tracker is given to the maintainer of the component in which the bug was found. This is an example of a -completely application-specific assignment, one which is -only relevant for bug-tracker bug objects, because we need to know -the particular bug-tracker data model for this to work.
+completely application-specific assignment, one +which is only relevant for bug-tracker bug objects, because we need +to know the particular bug-tracker data model for this to +work.These different options are supplied by programmers as implementations of a particular service contract (see below under service contracts).
-In the definition of a workflow, you can select an ordered -list of default assignment methods Each will be tried in the -order you specify. The first to return a non-empty list of -assignees is the one which will be used, and the rest won't get -called. So for example you can say "first try component maintainer, -and if non is specified, use the project maintainer".
+In the definition of a workflow, you can select an +ordered list of default assignment methods Each will be +tried in the order you specify. The first to return a non-empty +list of assignees is the one which will be used, and the rest +won't get called. So for example you can say "first try +component maintainer, and if non is specified, use the project +maintainer".
The workflow package will supply a few standard implementations:
-
- Creation user: Assign to the user who created the given object.
- Static assignee: Use the static assignment from the workflow definition.
Default assignment is done in a lazy fashion, in that we don't -try to find the default assignees until we need to. We need to the -first time an action assigned to that role is enabled. This allows -your default assignment to depend on things that happened in prior -tasks.
+Default assignment is done in a lazy fashion, in that we +don't try to find the default assignees until we need to. We +need to the first time an action assigned to that role is enabled. +This allows your default assignment to depend on things that +happened in prior tasks.
Reassignment
-Now, let's look at what happens when you want to reassign a role -to someone else. the
+Now, let's look at what happens when you want to reassign a +role to someone else. the
- If you want to reassign the role, the user interface offers a -pick-list of the users and groups which you're most likely -to want to reassign the role to. Who they are will depend on the -particular application. One common idea is to display the users who -are currently assigned to this role in other cases.
- If the desired person or group wasn't in the pick-list, you can -search. The search is conducted among all the users who -could possibly be assigned to this role, which, again, will depend -on the application. It could be all registered users on the site, -it could be all members of the nearest surrounding subsite, it -could be all members of a particular named group, or it could be -some other calculation based on the application.
+pick-list of the users and groups which you're +most likely to want to reassign the role to. Who they are will +depend on the particular application. One common idea is to display +the users who are currently assigned to this role in other +cases.- If the desired person or group wasn't in the pick-list, you +can search. The search is conducted among all the +users who could possibly be assigned to this role, which, again, +will depend on the application. It could be all registered users on +the site, it could be all members of the nearest surrounding +subsite, it could be all members of a particular named group, or it +could be some other calculation based on the application.
A couple of default implementations will be supplied by the workflow package. For the pick-list:
@@ -192,41 +197,45 @@ parties in the workflow_role_allowed_parties table.Actions
-In order to determine who are supposed to perform an -action, and who are allowed to perform the action, we let +
In order to determine who are supposed to perform an +action, and who are allowed to perform the action, we let you specify these three things for each action:
-
- -Assigned role(s): People who are mapped to this role -will be assigned to this action, e.g., the submitter is -assigned to the Close action once the bug is resolved. When you're -assigned to something, you're expected to go and do something about -it.
- -Allowed role(s): People who are mapped to this role will -have the permission to perform this action, e.g., the -submitter is allowed to Reopen the bug once it's resolved, but not -assigned to it. She's only assigned to "Close".
- -Privileges: People who have these privileges on the -object pointed to by workflow_case.object_id (e.g. the bug object -for bug-tracker) will also have permission to do perform -this action. Same as above, but allows for using permissions to -grant 'feedback', 'write', and 'admin', for example.
+Assigned role(s): People who are mapped to +this role will be assigned to this action, e.g., the +submitter is assigned to the Close action once the bug is resolved. +When you're assigned to something, you're expected to go +and do something about it.- +Allowed role(s): People who are mapped to this +role will have the permission to perform this action, +e.g., the submitter is allowed to Reopen the bug once it's +resolved, but not assigned to it. She's only assigned to +"Close".
- +Privileges: People who have these privileges +on the object pointed to by workflow_case.object_id (e.g. the bug +object for bug-tracker) will also have permission to do +perform this action. Same as above, but allows for using +permissions to grant 'feedback', 'write', and +'admin', for example.
Actions can also have side-effects, which simply means -that whenever an action is triggered, one or more specified service -contract implementations will get executed. These side-effects are -executed after all other updates, both to the case object, -and to the workflow tables, have been completed.
+Actions can also have side-effects, which +simply means that whenever an action is triggered, one or more +specified service contract implementations will get executed. These +side-effects are executed after all other updates, +both to the case object, and to the workflow tables, have been +completed.
States
This is specific to the FSM-model. A workflow has a finite set -of states, for example "open", "resolved", and "closed". A case -will always be in exactly one such state. When you perform an -action, the workflow can be pushed into a new state.
+of states, for example "open", "resolved", and +"closed". A case will always be in exactly one such +state. When you perform an action, the workflow can be pushed into +a new state.There will be one initial state, which the workflow will start out in. This will be the first state according to the sort order from workflow_fsm_states
-States have almost no information associated with them, they're -simply used to govern which actions are available.
+States have almost no information associated with them, +they're simply used to govern which actions are available.
Cases
A case is the term for a workflow in action. A case always revolves around a specific object. and we currently only allow one @@ -404,52 +413,52 @@
Service Contracts
-workflow.Role_DefaultAssignees: +workflow.Role_DefaultAssignees: GetObjectType -> string GetPrettyName -> string GetAssignees (case_id, object_id, role_id) -> { list of party_id }-workflow.Role_AssigneePickList +workflow.Role_AssigneePickList GetObjectType -> string GetPrettyName -> string GetPickList (case_id, object_id, role_id) -> { list of party_id }-workflow.Role_AssigneeSubQuery +workflow.Role_AssigneeSubQuery GetObjectType -> string GetPrettyName -> string GetSubQueryName (case_id, object_id, role_id) -> { subquery_name { bind variable list } }-workflow.Action_SideEffect +workflow.Action_SideEffect GetObjectType -> string GetPrettyName -> string DoSideEffect (case_id, object_id, action_id, entry_id) -> (none)-workflow.ActivityLog_FormatTitle +workflow.ActivityLog_FormatTitle GetObjectType -> string GetPrettyName -> string GetTitle (entry_id) -> title-The GetObjectType method is used for the service contract -implementation to tell which object types it is valid for. For -example, a DefaultAssignee implementation can look at a bug, find -out which component it is found in, then look up the component -definition to find the default maintainer. This implementation, -though, is only valid for objects of type 'bt_bugs', or any -descendants thereof. Thus, this is what the GetObjectType call -would return for this implementation. If your implementation is -valid for any ACS Object, then simply return 'acs_object', as this -is the mother of all objects.
-The GetPrettyName method will be run through a +
The GetObjectType method is used for the +service contract implementation to tell which object types it is +valid for. For example, a DefaultAssignee implementation can look +at a bug, find out which component it is found in, then look up the +component definition to find the default maintainer. This +implementation, though, is only valid for objects of type +'bt_bugs', or any descendants thereof. Thus, this is what +the GetObjectType call would return for this implementation. If +your implementation is valid for any ACS Object, then simply return +'acs_object', as this is the mother of all objects.
+The GetPrettyName method will be run through a localization filter, meaning that any occurance of the -
-#message-key#
notation will be replaced with a -message catalog lookup for the current domain.The AssigneeQuery service contract probably needs a -little explanation. You're supposed to supply a valid subquery, -which will select the columns party_id, name, email, and +
+#message-key#
notation will be replaced with +a message catalog lookup for the current domain.The AssigneeQuery service contract probably +needs a little explanation. You're supposed to supply a valid +subquery, which will select the columns party_id, name, email, and screen_name (nulls are okay) of all the parties that a role can possibly be assigned to. A simple version could simply be "
@@ -466,17 +475,17 @@ select distinct q.party_id, q.name || ' (' || u.email || ')' as name_and_email -from (your subquery goes here) q +from (your subquery goes here) q where upper(coalesce(q.name, '') || q.email || ' ' || coalesce(q.screen_name, '')) like upper('%'||:value||'%') order by name_and_emailcc_users
". Another would be:Now, one little caveat is that you have to return the query -dispatcher query name, not the actual query. The query name -will then get passed to db_map to produce the actual -subquery.
+dispatcher query name, not the actual query. The +query name will then get passed to db_map to +produce the actual subquery.Workflow will supply these service contract implementations by default:
@@ -496,23 +505,25 @@
-Notifications
You can sign up for notifications at several levels:
-
- Notify me of all actions to which I'm assigned. You don't have -to manually go sign up for these notifications, but you should be -able to change the delivery method and frequency.
- Notify me of all activity on ... +
- Notify me of all actions to which I'm assigned. You +don't have to manually go sign up for these notifications, but +you should be able to change the delivery method and +frequency.
- Notify me of all activity on ...
-
- Any case where I'm assigned to some role.
- A particular case (one bug-tracker bug)
- All cases in the particular workflow (entire bug-tracker +
- Any case where I'm assigned to some role.
- A particular case (one bug-tracker bug)
- All cases in the particular workflow (entire bug-tracker project)
You should always receive at most one notification per activity. -They're sent out in the order in which they're listed here, and if -you get the first, you won't get the second, third or fourth; if -you get the second, you won't get the third or fourth, etc.
-A special case is that the first notification isn't optional. -You don't have to manually go sign up for those notifications, and -you can't turn them off entirely. You can still change the delivery -method and the frequency, though.
+They're sent out in the order in which they're listed here, +and if you get the first, you won't get the second, third or +fourth; if you get the second, you won't get the third or +fourth, etc. +A special case is that the first notification isn't +optional. You don't have to manually go sign up for those +notifications, and you can't turn them off entirely. You can +still change the delivery method and the frequency, though.
In order to implement this, we need to make three fairly trivial enhancements to the notifications package.
@@ -522,17 +533,18 @@
@@ -732,19 +744,20 @@ label name } }-already_notified
and to not notify those again, and likewise, to return the list of users notified by the given notification.- We need to be able to limit notifications to only a subset of -the subscribed base. If you have a subscription on "any case where -I'm assigned to some role", that's a dynamic relationship. So the -call to
notification::new
would take as a parameter -the list of people who are assigned to some role on this particular -case. Only people who are subscribed and on that list will -get notified. I can't think of a good name for such a parameter, -perhaps-positive_list
.- Finally, we need to force people on the positive list above to -get notifications even though they don't currently have a +the subscribed base. If you have a subscription on "any case +where I'm assigned to some role", that's a dynamic +relationship. So the call to
notification::new
would +take as a parameter the list of people who are assigned to some +role on this particular case. Only people who are subscribed +and on that list will get notified. I can't think of a +good name for such a parameter, perhaps +-positive_list
.- Finally, we need to force people on the positive list above to +get notifications even though they don't currently have a subscription. This could be a
-force:boolean
parameter which works in conjunction with the positive list, so that people -on the positive list who aren't subscribers get a default +on the positive list who aren't subscribers get a default email/instant subscription automatically. They can then go back and change their delivery method and frequency later.The enabled actions which the current user has permission to perform.
- case::action::get_editable_fields(case_id, action) -> { list of field names }
Which fields should we edit, depending on the current action. -NOTE! We probably won't be able to support this in the first +NOTE! We probably won't be able to support this in the first version.
- case::state::get_hidden_fields(case_id) -> { list of field names }
Which fields should we hide, depending on the state. NOTE! We -probably won't be able to support this in the first version.
- case::action::available_p(case_id, user_id, action_id) -> +probably won't be able to support this in the first +version.
- case::action::available_p(case_id, user_id, action_id) -> (boolean)
Is this action enabled and allowed for this user?
- case::action::new_state(case_id, action_id) -> (state_id)
The new state which the case will have after this action has -been performed (if action doesn't change state, returns the current -state again.
- case::action::execute(case_id, action_id, comment, +been performed (if action doesn't change state, returns the +current state again.
- case::action::execute(case_id, action_id, comment, comment_format) -> (state_id)
Perform the action, updating the workflow state, etc. This should be called from inside a db_transaction where the case object has just been updated.
Here's what the form page would look like:
+Here's what the form page would look like:
ad_page_contract { ... } { bug_id:integer,notnull @@ -801,39 +814,39 @@-
- Implement metadata spec and integrate with that so you can pick which fields in your form to view/edit/hide depending on state and action.
Nice-to-haves that aren't entirely pie-in-the-sky +
Nice-to-haves that aren't entirely pie-in-the-sky include:
- User interface components that can generate a user interface -like bug-tracker's, i.e. buttons below the form showing the actions -that you can take, the resolution entries, the sub-status codes, -etc.
- Pluggable models, for example, finite-state machines, +like bug-tracker's, i.e. buttons below the form showing the +actions that you can take, the resolution entries, the sub-status +codes, etc.
- Pluggable models, for example, finite-state machines, petri-nets, dependency graphs. A service-contract-based interface -allows you to plug in a new model.
- Integration with a task-list application to maintain the user's -one task list (synchronization with Palm, etc.).
- Integration with calendar, so deadlines show up there.
+allows you to plug in a new model.- Integration with a task-list application to maintain the +user's one task list (synchronization with Palm, etc.).
- Integration with calendar, so deadlines show up there.
Appendix A. Pluggable Models
-I've looked into pluggable models before, and it's not too -complicated. The trick is that you have four areas where the +
I've looked into pluggable models before, and it's not +too complicated. The trick is that you have four areas where the generic workflow framework/engine will interface with the plugin model:
-
-- All workflow models have some definition of 'state'. For finite -state machines, it's simply the name of the state, you're currently -in: The structure here is a value from an enumeration. A petri net -has as its state a list of tokens, each of which is currently in a -particular place. A dependency graph model has as its state the -list of tasks that have been completed. The workflow engine must -provide an API for the pluggable model to access, manipulate, and -store its state, but need not know anything about the internals of -the state or how it's manipulated.
- All workflow models has some elements that go into its workflow +
- All workflow models have some definition of 'state'. +For finite state machines, it's simply the name of the state, +you're currently in: The structure here is a value from an +enumeration. A petri net has as its state a list of tokens, each of +which is currently in a particular place. A dependency graph model +has as its state the list of tasks that have been completed. The +workflow engine must provide an API for the pluggable model to +access, manipulate, and store its state, but need not know anything +about the internals of the state or how it's manipulated.
- All workflow models has some elements that go into its workflow specification: FSMs have states and transitions; a transition is an arc from one state to another. Petri nets have places and transitions, and it has arcs that point from a place to a transition, or a transition to a state. Dependency graphcs has tasks and dependencies, where a depency goes from one task to another task.
- All workflow models has some concept of actions (tasks, -transitions). An action has some precondition for when it's +transitions). An action has some precondition for when it's enabled, i.e., for when a user can or should perform this action. This is a function of the state. And actions also cause a well-defined change to the state, i.e., we move to a different @@ -845,15 +858,15 @@ engine, and its specific model implementations.
Appendix B. Fix or Rewrite
Should we discard workflow and rewrite, or should we try to -incrementally improve what's there?
+incrementally improve what's there?In general, you should be weary of rewriting if:
-
-- You have many users of your software who'll want to upgrade, -because they'll be annoyed by small changes to how things -work.
- You have a different set of people implementing it the second +
- You have many users of your software who'll want to +upgrade, because they'll be annoyed by small changes to how +things work.
- You have a different set of people implementing it the second time than you had the first time.
Neither of these are the case here. We don't have any +
Neither of these are the case here. We don't have any significant users of workflow, and we have access to the same people (person) who did the original implementation to implement it again.
@@ -864,5 +877,5 @@ engine and most of the admin UI- Discard Graphviz for admin UI
- Current UI not using form builder/ad_form
- Current data model not using acs-kernel properly, e.g., a new workflow is an object type.
Hence, we've concluded that a rewrite is in fact the most +
Hence, we've concluded that a rewrite is in fact the most productive strategy.
Index: openacs-4/packages/workflow/www/doc/workflow.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/www/doc/workflow.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/workflow/www/doc/workflow.adp 26 Aug 2015 18:03:36 -0000 1.2 +++ openacs-4/packages/workflow/www/doc/workflow.adp 12 Sep 2016 06:10:32 -0000 1.3 @@ -9,13 +9,14 @@- Your task list
- The page for the case (e.g. the content item page in the right package and folder).
- The object list page (e.g. the folder page)
- The "create new object" page.
-This will be specified in the workflow spec as an "redirect_to" -on actions, with possible values of "task_list", "case", -"case_list", or "new_case".
+This will be specified in the workflow spec as an +"redirect_to" on actions, with possible values of +"task_list", "case", "case_list", or +"new_case".
This assumes we have already implemented #6 below.
It will require changes to the API, in order for workflow::case::action::execute to be able to return a URL.
-TODO: Look in more detail at what's needed.
+TODO: Look in more detail at what's needed.
Estimate: 6 hours. Risk: Med. Recommended.
A: WF #10: Task list UI
Write a generic task list UI that lets you find tasks which you