Workflow

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:

<startEvent id="startEvent1" flowable:formFieldValidation="true">
  <extensionElements>
    <flowable:formProperty id="userPk" type="long" required="true"></flowable:formProperty>
  </extensionElements>
</startEvent>

Note

If the start event doesn’t include the userPk, then we throw an ActivitiError exception.

Form Fields

Check Box

_images/workflow-form-field-check-box.png

To create a series of tick boxes, the type must be boolean:

<activiti:formProperty id="monday" name="Monday" type="boolean" expression="True"></activiti:formProperty>
<activiti:formProperty id="tuesday" name="Tuesday" type="boolean" expression="True"></activiti:formProperty>
<activiti:formProperty id="wednesday" name="Wednesday" type="boolean" expression="True"></activiti:formProperty>

Form Headings

_images/workflow-form-headings.png

To create a form heading, add a formProperty with _heading appended to the id e.g:

<activiti:formProperty id="invoice_heading" name="Invoice Heading" type="string" required="true" writable="false">

The name will display as the heading.

You can add help text in Settings, Mapping, Mapping, <field name>, 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

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:

${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: <activiti:formProperty id="dataDocument" name="My Data Document" type="string"></activiti:formProperty>

copyToDocumentManagement

Copy attachments to Alfresco:

<activiti:formProperty id="copyToDocumentManagement" name="Copy Attachments to Share" type="boolean" expression="True"></activiti:formProperty>

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.

_images/2023-11-15-app-workflow-global-groups.png _images/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:

<scriptTask id="sid-780D1BD8-9E03-4802-8EC3-C944F5B00A67" scriptFormat="javascript" flowable:autoStoreVariables="false">
  <script>
    <![CDATA[var ObjectMapper = com.fasterxml.jackson.databind.ObjectMapper;
      var data = {};
      data.name = "Andrea";
      var httpTaskData = JSON.stringify(data);
      var json = new ObjectMapper().readTree(httpTaskData);
      execution.setVariable("httpTaskData", json);]]>
  </script>
</scriptTask>

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.

_images/app-workflow-settings-history.png

Task History

Settings, Workflow, Tasks and clicking on a process, gives you access to Task History:

_images/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…