222 lines
7.1 KiB
ReStructuredText
222 lines
7.1 KiB
ReStructuredText
Tasks
|
|
=====
|
|
|
|
BPMN Model
|
|
----------
|
|
|
|
In this example, we'll model a customer selecting a product to illustrate
|
|
the basic task types that can be used with SpiffWorkflow.
|
|
|
|
We'll be using the following files from `spiff-example-cli <https://github.com/sartography/spiff-example-cli>`_.
|
|
|
|
- `task_types <https://github.com/sartography/spiff-example-cli/blob/master/bpmn/task_types.bpmn>`_ workflow
|
|
- `product_prices <https://github.com/sartography/spiff-example-cli/blob/master/bpmn/product_prices.dmn>`_ DMN table
|
|
|
|
User Tasks
|
|
^^^^^^^^^^
|
|
|
|
User tasks would typically be used in the case where the task would be
|
|
completed from within the application.
|
|
|
|
User tasks can include forms that ask the user questions. When you click on a
|
|
user task in a BPMN modeler, the Properties Panel includes a form tab. Use this
|
|
tab to build your questions.
|
|
|
|
We'll ask our hypothetical user to choose a product and quantity.
|
|
|
|
The following example shows how a form might be set up in Camumda.
|
|
|
|
.. figure:: figures/user_task.png
|
|
:scale: 30%
|
|
:align: center
|
|
|
|
User Task configuration
|
|
|
|
.. note::
|
|
|
|
SpiffWorkflow has some basic support for the free Camunda modeler, to use its
|
|
form building capabilities, but we intend to encapsulate this support in an
|
|
extension module and remove it from the core library eventually.
|
|
|
|
See the `Handling User Tasks`_ section for a discussion of sample code.
|
|
|
|
Business Rule Tasks
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
In our business rule task, we'll use a DMN table to look up the price of the
|
|
product the user chose.
|
|
|
|
We'll need to create a DMN table.
|
|
|
|
What is DMN?
|
|
++++++++++++
|
|
|
|
Decision Model and Notation (DMN) is a standard for business decision
|
|
modeling. DMN allows modelers to separate decision logic from process logic
|
|
and maintain it in a table format. DMN is linked into BPMN with a *decision
|
|
task*.
|
|
|
|
With DMN, business analysts can model the rules that lead to a decision
|
|
in an easy to read table. Those tables can be executed directly by SpiffWorkflow.
|
|
|
|
This minimizes the risk of misunderstandings between business analysts and
|
|
developers, and allows rapid changes in production.
|
|
|
|
BPMN includes a decision task that refers to the decision table. The outcome of
|
|
the decision lookup allows the next gateway or activity to route the flow.
|
|
|
|
Our Business Rule Task will make use of a DMN table.
|
|
|
|
.. figure:: figures/dmn_table.png
|
|
:scale: 30%
|
|
:align: center
|
|
|
|
DMN Table
|
|
|
|
.. note::
|
|
We add quote marks around the product names in the table. Spiff will
|
|
create an expression based on the exact contents of the table, so if
|
|
the quotes are omitted, the content will be interpreted as a variable
|
|
rather than a string.
|
|
|
|
Then we'll refer to this table in the task configuration.
|
|
|
|
.. figure:: figures/business_rule_task.png
|
|
:scale: 30%
|
|
:align: center
|
|
|
|
Business Rule Task configuration
|
|
|
|
Script Tasks
|
|
^^^^^^^^^^^^
|
|
|
|
The total order cost will need to be calculated on the fly. We can do this in
|
|
a script task. We'll configure the task with some simple Python code.
|
|
|
|
.. figure:: figures/script_task.png
|
|
:scale: 30%
|
|
:align: center
|
|
|
|
Script Task configuration
|
|
|
|
The code in the script will have access to the task data, so variables that
|
|
have been defined previously will be available to it.
|
|
|
|
Manual Tasks
|
|
^^^^^^^^^^^^
|
|
|
|
Our final task type is a manual task. We would use this task in the situation
|
|
where the application might simply need to mark a task that requires user
|
|
involvement complete without gathering any additional information from them.
|
|
|
|
There is no special configuration for manual tasks. However, this is a good
|
|
place to note that we can use the BPMN element Documentation field to display
|
|
more information about the context of the item.
|
|
|
|
Spiff is set up in a way that you could use any templating library you want, but
|
|
we have used `Jinja <https://jinja.palletsprojects.com/en/3.0.x/>`_.
|
|
|
|
In this example, we'll present an order summary to our customer.
|
|
|
|
.. figure:: figures/documentation.png
|
|
:scale: 30%
|
|
:align: center
|
|
|
|
Element Documentation
|
|
|
|
See the `Handling Manual Tasks`_ section for a discussion of sample code.
|
|
|
|
Running The Model
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
If you have set up our example repository, this model can be run with the
|
|
following command:
|
|
|
|
.. code-block:: console
|
|
|
|
./run.py -p order_product -d bpmn/product_prices.dmn -b bpmn/task_types.bpmn
|
|
|
|
Example Application Code
|
|
------------------------
|
|
|
|
Handling User Tasks
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
We will need to provide a way to display the form data and collect the user's
|
|
responses.
|
|
|
|
.. code:: python
|
|
|
|
for field in task.task_spec.form.fields:
|
|
if isinstance(field, EnumFormField):
|
|
option_map = dict([ (opt.name, opt.id) for opt in field.options ])
|
|
options = "(" + ', '.join(option_map) + ")"
|
|
prompt = f"{field.label} {options} "
|
|
option = select_option(prompt, option_map.keys())
|
|
response = option_map[option]
|
|
else:
|
|
response = input(f"{field.label} ")
|
|
if field.type == "long":
|
|
response = int(response)
|
|
task.update_data_var(field.id, response)
|
|
|
|
The list of form fields for a task is stored in :code:`task.task_spec.form_fields`.
|
|
|
|
For Enumerated fields, we want to get the possible options and present them to the
|
|
user. The variable names of the fields were stored in :code:`field.id`, but since
|
|
we set labels for each of the fields, we'd like to display those instead, and map
|
|
the user's selection back to the variable name.
|
|
|
|
Our :code:`select_option` function simply repeats the prompt until the user
|
|
enters a value contained in the option list.
|
|
|
|
For other fields, we'll just store whatever the user enters, although in the case
|
|
where the data type was specified to be a :code:`long`, we'll convert it to a
|
|
number.
|
|
|
|
Finally, we need to explicitly store the user-provided response in a variable
|
|
with the expected name with :code:`task.update_data_var(field.id, response)`.
|
|
|
|
|
|
Handling Business Rule Tasks
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
We do not need to do any special configuration to handle these business rule
|
|
tasks. SpiffWorkflow does it all for us.
|
|
|
|
Handling Script Tasks
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
We do not need to do any special configuration to handle script tasks, although it
|
|
is possible to implement a custom script engine. We demonstrate that process in
|
|
Custom Script Engines section :doc:`advanced` features. However, the default script
|
|
engine will work in many cases.
|
|
|
|
Handling Manual Tasks
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Our code for manual tasks simply asks the user to confirm that the task has been
|
|
completed.
|
|
|
|
.. code:: python
|
|
|
|
def complete_manual_task(task):
|
|
display_task(task)
|
|
input("Press any key to mark task complete")
|
|
|
|
:code:`display_task()` is the code for converting the Documentation property of the task
|
|
into something that can be presented to the user.
|
|
|
|
.. code:: python
|
|
|
|
def display_task(task):
|
|
print(f'\n{task.task_spec.description}')
|
|
if task.task_spec.documentation is not None:
|
|
template = Template(task.task_spec.documentation)
|
|
print(template.render(task.data))
|
|
|
|
The template string can be obtained from :code:`task.task_spec.documentation`.
|
|
|
|
As noted above, our template class comes from Jinja. We render the template
|
|
using the task data, which is just a dictionary.
|