Workflow
********
.. highlight:: python
- The original ``workflow`` notes are in the app documentation:
https://gitlab.com/kb/workflow/blob/master/docs/source/index.rst
User Manual
===========
Start Event (``startEvent``)
----------------------------
.. note:: 13/01/2022, We are adding a user to the process on creation
(``process_identity_add_participant``), so perhaps we won't need the
``userPk`` in future?
For details, see `commit 8361c1103cf09021a795c648cc867ee2549736a6`_
We need to search for workflow processes started by a user (`#5553`_):
- The Flowable ``startedBy`` filter looks for the *owner* of the workflow, but
we don't set an owner.
- As a workaround, we set the ``user_pk`` in the start event when we create a
workflow.
The ``started_by`` filter searches for the ``userPk`` variable in the
``_historic_process`` and ``_process_instances`` methods (``workflow/activiti.py``)
All workflows require a ``userPk`` in the ``startEvent`` e.g::
.. note:: If the start event doesn't include the ``userPk``, then we throw an
``ActivitiError`` exception.
Form Fields
-----------
Check Box
.. image:: ./misc/workflow-form-field-check-box.png
To create a series of tick boxes, the ``type`` must be ``boolean``::
Form Headings
-------------
.. image:: ./misc/workflow-form-headings.png
To create a form heading, add a ``formProperty`` with ``_heading`` appended to
the ``id`` e.g::
The ``name`` will display as the heading.
You can add help text in *Settings*, *Mapping*, *Mapping*, ````,
*Help Text*.
Note:
- ``writable`` must be ``false``
- The property must not have a ``value`` (not sure where this comes from)
- If the ``name`` is empty, you must add help text.
Group - Claimed by User
-----------------------
Just before a user completes a task, it is *claimed* for them (using ``task_claim``).
When the task is completed, we create a ``groupTaskClaimedByUserPk`` variable
containing the ID of the user who claimed the task.
To use this variable in a future task, assign the task to:
.. code-block:: xml
${groupTaskClaimedByUserPk}
.. image:: ./misc/app-workflow/2025-01-23-group-task-claimed-by-user-pk.png
.. warning:: This is a global variable, so if you have parallel tasks, then
you may not be able to rely on the value.
Group - for a Task
------------------
Get the (first) group ID for a task (not sure if you can have more than one)::
activiti = Activiti()
group_id = activiti.task_group_id(task_id)
To get the actual group::
def group(group_pk):
try:
return Group.objects.get(pk=group_pk)
except Group.DoesNotExist:
return None
.. tip:: For more information, see `Get all identity links for a task`_
HTTP Task
---------
Add the ``apiUrl`` to the start event.
The ``requestUrl`` will use this variable as follows:
.. code-block:: javascript
${apiUrl}/refresh-variables/${execution.getProcessInstanceId()}/
See this ticket for an example API and a test workflow:
https://www.kbsoftware.co.uk/crm/ticket/4974/
.. tip:: If you are running Flowable inside Kubernetes, then make sure your
``apiUrl`` is accessible from there.
.. tip:: This is a *very* useful forum post to help with variables in the HTTP
task:
`How should I use PUT/POST in Http task when I am limited to only strings`_
Mapping
-------
*Imported User*, ``import-user``
If your workflow includes a user ID in the start event, then select
``import-user`` to have the full name and email address automatically
generated when the workflow is started.
The ``work`` API also regenerates the variables when a task is completed
(source code in ``work.api._variables_to_save``).
Options
-------
+------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| ``dataDocument`` | Attach a simple PDF document with a table containing the form variables. |
| | |
| | To get it working, add a form variable with an id of ``dataDocument``. |
| | The ``name`` will be used as the title e.g: |
| | ```` |
+------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| ``copyToDocumentManagement`` | Copy attachments to Alfresco: |
| | |
| | ```` |
+------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| ``deleteWorkflow`` | Will add a *Delete Workflow* button to the task. |
| | |
| | This is useful when the workflow is in development as the process can be deleted quickly and easily. |
+------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
Permissions
===========
Process
-------
The ``Workflow`` model has two security related fields:
- ``security_history``
(can view workflow history for any user)
- ``security_history_my``
(can view workflow history where they have been involved)
.. tip:: The code below is for checking the permissions for a single process.
To get a list of workflows where the user has permission, the
``WorkflowManager`` has ``security_history`` and
``security_history_my`` methods which can be used.
Start by checking the ``security_history``. If the user doesn't have
``security_history`` permission, check ``security_history_my``::
def user_has_security_history_for_process(user, process):
"""Does the user have permission to view the history for this process?"""
result = False
workflow = get_workflow(process.process_key)
if workflow.has_security_history(user):
result = True
else:
# permission to view workflows where they have been involved
if workflow.has_security_history_my(user):
# has the user been involved with this process?
activiti = Activiti()
identity_list = activiti.process_identity_list(process.process_id)
if user.pk in set([x.user for x in identity_list]):
result = True
return result
.. note:: Code from ``work/service.py``.
Here is an example configuration:
Created two Global Groups where needed i.e. adding the word *Delete* and then
configuring the mapping as shown below.
.. image:: ./misc/2023-11-15-app-workflow-global-groups.png
.. image:: ./misc/2023-11-15-app-workflow-security.png
Hopefully a sensible way to approach this.
Prerequisite
------------
Every task must have the following form variable:
+------------------------------+-----------------------------------------------------------------------------+
| ``auditDescription`` | The text in the ``Expression`` will be added to the audit for this process. |
+------------------------------+-----------------------------------------------------------------------------+
Script Task
-----------
From `JSON process variables - any gotchas?`_, to save data in ``json`` format:
.. code-block:: xml
.. tip:: Make sure the ``scriptFormat`` is set to ``javascript``.
Management Commands
===================
``6699-mapping-export``
-----------------------
To export the mapping for a workflow::
django-admin 6699-mapping-export myTempWorkflow
.. tip:: The management command will create an export file e.g.
``myTempWorkflow-mapping-export.json``
To import the mapping for the workflow::
django-admin 6699-mapping-import ~/temp/myTempWorkflow-mapping-export.json
``create_pending_workflows``
----------------------------
Will use ``ScheduledWorkflowUser.objects.pending_with_options`` to check the
database for pending workflows
(created since the first minute of the day) and start them in Activiti.
It takes an optional argument - the name of the process definition e.g::
django-admin.py create_pending_workflows timeOff
``history-process``
-------------------
Call the ``history_process`` API and output some information to a CSV file::
# source ('workflow')
workflow/management/commands/history-process.py
# e.g.
django-admin history-process periodicReview finished 2021-03-12 2021-03-14 document_code
django-admin history-process periodicReview started 2021-03-12 2021-03-14 document_code
.. tip:: Use ``started`` for workflows started within the two dates.
Use ``finished`` for workflows finishing within the two dates.
.. tip:: The ``document_code`` in the example above is a variable name to add
to the CSV file.
``rest_task``, ``rest_process`` and ``rest-variable``
-----------------------------------------------------
.. tip:: Source code for these management commands is in
``workflow/management/commands/``
Display *task* data (and find the process ID) e.g::
django-admin rest-task fe485d7c-ce6b-11eb-b755-000d3a7f022c
Display *process* data e.g::
django-admin rest-process ba224c9a-7db3-11eb-ad2b-000d3a7f022c
# to display the data for one of the variables
django-admin rest-process ba224c9a-7db3-11eb-ad2b-000d3a7f022c documentTitle
Display (and update) a *variable* e.g::
django-admin rest-variable ba224c9a-7db3-11eb-ad2b-000d3a7f022c documentTitle
# to update the variable
django-admin rest-variable ba224c9a-7db3-11eb-ad2b-000d3a7f022c documentTitle "Management Systems"
.. note:: ``rest-variable`` can only update ``string`` variables at present.
Feel free to update and improve!
Find all workflows for a process key and list a variable::
# source ('workflow')
workflow/management/commands/workflow-variable-to-csv.py
# e.g.
django-admin workflow-variable-to-csv timeOff document_code
# Sample output
process status created value
af3cece6-afb9 Current 10/01/2024 13:10 DOC-1991
9d3bb3e3-afb9 Current 10/01/2024 13:10 DOC-1992
a4176087-afb9 Current 10/01/2024 13:10 DOC-1993
Settings
========
Activiti
--------
A standard install will have Flowable running on ``localhost``. To configure
this, add the following settings::
# 'settings/base.py'
ACTIVITI_HOST = 'localhost'
ACTIVITI_PORT = 8080
# 'settings/local.py'
ACTIVITI_PATH = 'activiti-rest'
# 'settings/production.py'
ACTIVITI_PATH = 'activiti-rest-{}'.format(DOMAIN.replace('.', '-').replace('-', '_'))
If Flowable is running on a different server, you can configure the settings
differently e.g::
# 'settings/base.py'
ACTIVITI_PORT = 8080
# 'settings/local.py'
ACTIVITI_HOST = 'localhost'
ACTIVITI_PATH = 'activiti-rest'
# 'settings/production.py'
ACTIVITI_HOST = get_env_variable("ACTIVITI_HOST")
ACTIVITI_PATH = 'activiti-rest-{}'.format(DOMAIN.replace('.', '-').replace('-', '_'))
``workflow`` app
----------------
The URL for an API should be added to ``WORKFLOW_API_URL``.
We also have a ``WORKFLOW_PLUGIN`` system which can be used to add extra
variables to a workflow e.g::
WORKFLOW_API_URL = get_env_variable("WORKFLOW_API_URL")
WORKFLOW_PLUGIN = ("example_workflow.models.WorkflowContact",)
Add the ``WORKFLOW_API_URL`` to the ``pillar`` file for your project.
.. warning:: Do not append a ``/`` to the end of the ``WORKFLOW_API_URL`` e.g.
``http://127.0.0.1:3042/api/0.1``
Settings (Dashboard)
====================
History
-------
*Settings*, *Workflow*, *Tasks* has a new *History* menu option which
allows you to filter by workflow type e.g.
.. image:: ./misc/app-workflow-settings-history.png
Task History
------------
*Settings*, *Workflow*, *Tasks* and clicking on a process, gives you access to
*Task History*:
.. image:: ./misc/app-workflow-settings-task-history.png
Task
====
Delete by ID
------------
::
from workflow.activiti import Activiti
activiti = Activiti()
# replace `17077`` with your task ID
task = activiti.task_status(17077)
task.process_key
activiti.process_delete(task.process_id)
Owner
-----
To see if a user *owns* a task, we need to check:
1. Are they an ``assignee`` on the task (``task.assignee == user.pk``)?
2. Are they a member of the group...
.. _`#5553`: https://www.kbsoftware.co.uk/crm/ticket/5553/
.. _`commit 8361c1103cf09021a795c648cc867ee2549736a6`: https://gitlab.com/kb/workflow/-/commit/8361c1103cf09021a795c648cc867ee2549736a6
.. _`Get all identity links for a task`: https://www.flowable.com/open-source/docs/bpmn/ch14-REST#get-all-identity-links-for-a-task
.. _`How should I use PUT/POST in Http task when I am limited to only strings`: https://forum.flowable.org/t/how-exactly-should-i-use-put-post-in-http-task-when-i-am-limited-to-only-strings-for-request-body/2243/2
.. _`JSON process variables - any gotchas?`: https://forum.flowable.org/t/json-proces-variables-any-gotchas/1798/2