Merge branch 'main' into cullerton
# Conflicts: # src/instance/config.py
This commit is contained in:
commit
0d59f5e59c
|
@ -1,5 +1,5 @@
|
|||
pip==22.1.2
|
||||
pip==22.2
|
||||
nox==2022.1.7
|
||||
nox-poetry==1.0.1
|
||||
poetry==1.1.14
|
||||
virtualenv==20.15.1
|
||||
virtualenv==20.16.0
|
||||
|
|
39
README.rst
39
README.rst
|
@ -42,26 +42,33 @@ Features
|
|||
* Backend API portion of the spiffworkflow engine webapp
|
||||
|
||||
|
||||
Running Locally
|
||||
---------------
|
||||
|
||||
* Install libraries using poetry:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ poetry install
|
||||
|
||||
* Setup the database - currently requires mysql:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ ./bin/recreate_db
|
||||
|
||||
* Run the server:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ ./bin/run_server_locally
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* Python 3.9+
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
You can install *Spiffworkflow Backend* via pip_ from PyPI_:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ pip install spiffworkflow-backend
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Please see the `Command-line Reference <Usage_>`_ for details.
|
||||
* Poetry
|
||||
|
||||
|
||||
Contributing
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import with_statement
|
||||
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 3f7aa48eaf3a
|
||||
Revision ID: b4f678040235
|
||||
Revises:
|
||||
Create Date: 2022-07-11 16:08:00.002287
|
||||
Create Date: 2022-07-25 09:46:39.406847
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3f7aa48eaf3a'
|
||||
revision = 'b4f678040235'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -95,18 +95,22 @@ def upgrade():
|
|||
)
|
||||
op.create_table('active_task',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('spiffworkflow_task_id', sa.String(length=50), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('assigned_principal_id', sa.Integer(), nullable=True),
|
||||
sa.Column('spiffworkflow_task_data', sa.Text(), nullable=True),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('form_file_name', sa.String(length=50), nullable=True),
|
||||
sa.Column('ui_form_file_name', sa.String(length=50), nullable=True),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('task_id', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_name', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_title', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_type', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_status', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_data', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['assigned_principal_id'], ['principal.id'], ),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('spiffworkflow_task_id', 'process_instance_id', name='active_task_unique')
|
||||
sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique')
|
||||
)
|
||||
op.create_table('file',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
@ -645,7 +645,7 @@ paths:
|
|||
# '204':
|
||||
# description: The file was removed.
|
||||
|
||||
/tasks/my-tasks:
|
||||
/tasks:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
|
@ -670,9 +670,34 @@ paths:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
# $ref: "#/components/schemas/ActiveTask"
|
||||
$ref: "#/components/schemas/Task"
|
||||
/tasks/{task_id}:
|
||||
/process-instance/{process_instance_id}/tasks:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
- name: all_tasks
|
||||
in: query
|
||||
required: false
|
||||
description: If true, this wil return all tasks associated with the process instance and not just user tasks.
|
||||
schema:
|
||||
type: boolean
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_task_list
|
||||
summary: returns the list of all user tasks associated with process instance
|
||||
responses:
|
||||
"200":
|
||||
description: list of tasks
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
/tasks/{process_instance_id}/{task_id}:
|
||||
parameters:
|
||||
- name: task_id
|
||||
in: path
|
||||
|
@ -680,6 +705,18 @@ paths:
|
|||
description: The unique id of an existing process group.
|
||||
schema:
|
||||
type: string
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
- name: terminate_loop
|
||||
in: query
|
||||
required: false
|
||||
description: Terminate the loop on a looping task
|
||||
schema:
|
||||
type: boolean
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_show
|
||||
summary: Gets one task that a user wants to complete
|
||||
|
@ -690,23 +727,8 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Task"
|
||||
/tasks/{task_id}/submit:
|
||||
parameters:
|
||||
- name: task_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process group.
|
||||
schema:
|
||||
type: string
|
||||
- name: terminate_loop
|
||||
in: query
|
||||
required: false
|
||||
description: Terminate the loop on a looping task
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit_user_data
|
||||
put:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit
|
||||
summary: Update the form data for a tasks
|
||||
requestBody:
|
||||
content:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Active_task."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
|
@ -11,6 +12,7 @@ from sqlalchemy.orm import RelationshipProperty
|
|||
|
||||
from spiffworkflow_backend.models.principal import PrincipalModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.task import Task
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -20,24 +22,41 @@ class ActiveTaskModel(SpiffworkflowBaseDBModel):
|
|||
__tablename__ = "active_task"
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"spiffworkflow_task_id", "process_instance_id", name="active_task_unique"
|
||||
"task_id", "process_instance_id", name="active_task_unique"
|
||||
),
|
||||
)
|
||||
|
||||
form_json: str | None = ""
|
||||
bpmn_json: str = ""
|
||||
assigned_principal: RelationshipProperty[PrincipalModel] = relationship(
|
||||
PrincipalModel
|
||||
)
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
spiffworkflow_task_id: str = db.Column(db.String(50), nullable=False)
|
||||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
assigned_principal_id: int = db.Column(ForeignKey(PrincipalModel.id))
|
||||
spiffworkflow_task_data: str = db.Column(db.Text)
|
||||
status: str = db.Column(db.String(20), nullable=False)
|
||||
form_file_name: str | None = db.Column(db.String(50))
|
||||
ui_form_file_name: str | None = db.Column(db.String(50))
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||
created_at_in_seconds: int = db.Column(db.Integer)
|
||||
|
||||
task_id = db.Column(db.String(50))
|
||||
task_name = db.Column(db.String(50))
|
||||
task_title = db.Column(db.String(50))
|
||||
task_type = db.Column(db.String(50))
|
||||
task_status = db.Column(db.String(50))
|
||||
task_data: str = db.Column(db.Text)
|
||||
|
||||
def to_task(self) -> Task:
|
||||
"""To_task."""
|
||||
task_data = json.loads(self.task_data)
|
||||
|
||||
return Task(
|
||||
self.task_id,
|
||||
self.task_name,
|
||||
self.task_title,
|
||||
self.task_type,
|
||||
self.task_status,
|
||||
data=task_data,
|
||||
process_instance_id=self.process_instance_id,
|
||||
)
|
||||
|
|
|
@ -99,11 +99,15 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||
created_at_in_seconds: int = db.Column(db.Integer)
|
||||
status: str = db.Column(db.String(50))
|
||||
|
||||
data: dict | None = None
|
||||
bpmn_xml_file_contents: bytes | None = None
|
||||
|
||||
@property
|
||||
def serialized(self) -> dict[str, Any]:
|
||||
"""Return object data in serializeable format."""
|
||||
local_bpmn_xml_file_contents = ""
|
||||
if self.bpmn_xml_file_contents:
|
||||
local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8")
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"process_model_identifier": self.process_model_identifier,
|
||||
|
@ -113,7 +117,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||
"start_in_seconds": self.start_in_seconds,
|
||||
"end_in_seconds": self.end_in_seconds,
|
||||
"process_initiator_id": self.process_initiator_id,
|
||||
"data": self.data,
|
||||
"bpmn_xml_file_contents": local_bpmn_xml_file_contents,
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -139,8 +143,6 @@ class ProcessInstanceModelSchema(Schema):
|
|||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_initiator_id",
|
||||
# "process_initiator",
|
||||
"bpmn_json",
|
||||
"start_in_seconds",
|
||||
"end_in_seconds",
|
||||
"updated_at_in_seconds",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Task."""
|
||||
import enum
|
||||
from typing import Any
|
||||
from typing import Union
|
||||
|
||||
import marshmallow
|
||||
from marshmallow import Schema
|
||||
|
@ -101,15 +102,18 @@ class Task:
|
|||
title: str,
|
||||
type: str,
|
||||
state: str,
|
||||
lane: str,
|
||||
form: None,
|
||||
documentation: str,
|
||||
data: dict[str, Any],
|
||||
multi_instance_type: MultiInstanceType,
|
||||
multi_instance_count: str,
|
||||
multi_instance_index: str,
|
||||
process_name: str,
|
||||
properties: dict,
|
||||
lane: Union[str, None] = None,
|
||||
form: None = None,
|
||||
documentation: str = "",
|
||||
data: Union[dict[str, Any], None] = None,
|
||||
multi_instance_type: Union[MultiInstanceType, None] = None,
|
||||
multi_instance_count: str = "",
|
||||
multi_instance_index: str = "",
|
||||
process_name: str = "",
|
||||
properties: Union[dict, None] = None,
|
||||
process_instance_id: Union[int, None] = None,
|
||||
form_schema: Union[str, None] = None,
|
||||
form_ui_schema: Union[str, None] = None,
|
||||
):
|
||||
"""__init__."""
|
||||
self.id = id
|
||||
|
@ -117,10 +121,18 @@ class Task:
|
|||
self.title = title
|
||||
self.type = type
|
||||
self.state = state
|
||||
self.form = None
|
||||
self.form = form
|
||||
self.documentation = documentation
|
||||
self.data = data
|
||||
self.lane = lane
|
||||
|
||||
self.data = data
|
||||
if self.data is None:
|
||||
self.data = {}
|
||||
|
||||
self.process_instance_id = process_instance_id
|
||||
self.form_schema = form_schema
|
||||
self.form_ui_schema = form_ui_schema
|
||||
|
||||
self.multi_instance_type = (
|
||||
multi_instance_type # Some tasks have a repeat behavior.
|
||||
)
|
||||
|
@ -131,7 +143,37 @@ class Task:
|
|||
multi_instance_index # And the index of the currently repeating task.
|
||||
)
|
||||
self.process_name = process_name
|
||||
|
||||
self.properties = properties # Arbitrary extension properties from BPMN editor.
|
||||
if self.properties is None:
|
||||
self.properties = {}
|
||||
|
||||
@property
|
||||
def serialized(self) -> dict[str, Any]:
|
||||
"""Return object data in serializeable format."""
|
||||
multi_instance_type = None
|
||||
if self.multi_instance_type:
|
||||
MultiInstanceType(self.multi_instance_type)
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"title": self.title,
|
||||
"type": self.type,
|
||||
"state": self.state,
|
||||
"lane": self.lane,
|
||||
"form": self.form,
|
||||
"documentation": self.documentation,
|
||||
"data": self.data,
|
||||
"multi_instance_type": multi_instance_type,
|
||||
"multi_instance_count": self.multi_instance_count,
|
||||
"multi_instance_index": self.multi_instance_index,
|
||||
"process_name": self.process_name,
|
||||
"properties": self.properties,
|
||||
"process_instance_id": self.process_instance_id,
|
||||
"form_schema": self.form_schema,
|
||||
"form_ui_schema": self.form_ui_schema,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def valid_property_names(cls) -> list[str]:
|
||||
|
@ -228,6 +270,9 @@ class TaskSchema(Schema):
|
|||
"multi_instance_index",
|
||||
"process_name",
|
||||
"properties",
|
||||
"process_instance_id",
|
||||
"form_schema",
|
||||
"form_ui_schema",
|
||||
]
|
||||
|
||||
multi_instance_type = EnumField(MultiInstanceType)
|
||||
|
|
|
@ -16,7 +16,8 @@ from flask import request
|
|||
from flask.wrappers import Response
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
from flask_bpmn.models.db import db
|
||||
from SpiffWorkflow import TaskState # type: ignore
|
||||
from SpiffWorkflow import Task as SpiffTask # type: ignore
|
||||
from SpiffWorkflow import TaskState
|
||||
from sqlalchemy import desc
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
|
@ -391,8 +392,13 @@ def process_instance_show(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Create_process_instance."""
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
process_instance.data = processor.get_data()
|
||||
process_model = get_process_model(process_model_id, process_group_id)
|
||||
|
||||
if process_model.primary_file_name:
|
||||
bpmn_xml_file_contents = SpecFileService.get_data(
|
||||
process_model, process_model.primary_file_name
|
||||
)
|
||||
process_instance.bpmn_xml_file_contents = bpmn_xml_file_contents
|
||||
|
||||
return make_response(jsonify(process_instance), 200)
|
||||
|
||||
|
@ -539,83 +545,129 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
|
|||
.paginate(page, per_page, False)
|
||||
)
|
||||
|
||||
tasks = [active_task.to_task() for active_task in active_tasks.items]
|
||||
|
||||
response_json = {
|
||||
"results": active_tasks.items,
|
||||
"results": tasks,
|
||||
"pagination": {
|
||||
"count": len(active_tasks.items),
|
||||
"total": active_tasks.total,
|
||||
"pages": active_tasks.pages,
|
||||
},
|
||||
}
|
||||
|
||||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def task_show(task_id: int) -> flask.wrappers.Response:
|
||||
def process_instance_task_list(
|
||||
process_instance_id: int, all_tasks: bool = False
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_task_list."""
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
|
||||
spiff_tasks = None
|
||||
if all_tasks:
|
||||
spiff_tasks = processor.bpmn_process_instance.get_tasks(TaskState.ANY_MASK)
|
||||
else:
|
||||
spiff_tasks = processor.get_all_user_tasks()
|
||||
|
||||
tasks = []
|
||||
for spiff_task in spiff_tasks:
|
||||
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
||||
task.data = spiff_task.data
|
||||
tasks.append(task)
|
||||
|
||||
return make_response(jsonify(tasks), 200)
|
||||
|
||||
|
||||
def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response:
|
||||
"""Task_list_my_tasks."""
|
||||
principal = find_principal_or_raise()
|
||||
|
||||
active_task_assigned_to_me = find_active_task_by_id_or_raise(task_id, principal.id)
|
||||
|
||||
process_instance = find_process_instance_by_id_or_raise(
|
||||
active_task_assigned_to_me.process_instance_id
|
||||
)
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
process_model = get_process_model(
|
||||
process_instance.process_model_identifier,
|
||||
process_instance.process_group_identifier,
|
||||
)
|
||||
|
||||
if active_task_assigned_to_me.form_file_name is None:
|
||||
active_task_assigned_to_me = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=process_instance_id,
|
||||
task_id=task_id,
|
||||
assigned_principal_id=principal.id,
|
||||
).first()
|
||||
|
||||
form_schema_file_name = ""
|
||||
form_ui_schema_file_name = ""
|
||||
task = None
|
||||
if active_task_assigned_to_me:
|
||||
form_schema_file_name = active_task_assigned_to_me.form_file_name
|
||||
form_ui_schema_file_name = active_task_assigned_to_me.ui_form_file_name
|
||||
task = active_task_assigned_to_me.to_task()
|
||||
else:
|
||||
spiff_task = get_spiff_task_from_process_instance(task_id, process_instance)
|
||||
extensions = spiff_task.task_spec.extensions
|
||||
|
||||
if "properties" in extensions:
|
||||
properties = extensions["properties"]
|
||||
if "formJsonSchemaFilename" in properties:
|
||||
form_schema_file_name = properties["formJsonSchemaFilename"]
|
||||
if "formUiSchemaFilename" in properties:
|
||||
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
||||
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
||||
task.data = spiff_task.data
|
||||
|
||||
if form_schema_file_name is None:
|
||||
raise (
|
||||
ApiError(
|
||||
code="missing_form_file",
|
||||
message=f"Cannot find a form file for active task {task_id}",
|
||||
message=f"Cannot find a form file for process_instance_id: {process_instance_id}, task_id: {task_id}",
|
||||
status_code=500,
|
||||
)
|
||||
)
|
||||
|
||||
file_contents = SpecFileService.get_data(
|
||||
process_model, active_task_assigned_to_me.form_file_name
|
||||
).decode("utf-8")
|
||||
|
||||
spiffworkflow_data_json = json.loads(
|
||||
active_task_assigned_to_me.spiffworkflow_task_data
|
||||
form_contents = prepare_form_data(
|
||||
form_schema_file_name,
|
||||
task.data,
|
||||
process_model,
|
||||
)
|
||||
|
||||
# trade out pieces like "{{variable_name}}" for the corresponding form data value
|
||||
for key, value in spiffworkflow_data_json.items():
|
||||
if isinstance(value, str) or isinstance(value, int):
|
||||
file_contents = file_contents.replace("{{" + key + "}}", str(value))
|
||||
if form_contents:
|
||||
task.form_schema = form_contents
|
||||
|
||||
active_task_assigned_to_me.form_json = file_contents
|
||||
if form_ui_schema_file_name:
|
||||
ui_form_contents = prepare_form_data(
|
||||
form_ui_schema_file_name,
|
||||
task.data,
|
||||
process_model,
|
||||
)
|
||||
if ui_form_contents:
|
||||
task.form_ui_schema = ui_form_contents
|
||||
|
||||
return make_response(jsonify(active_task_assigned_to_me), 200)
|
||||
return make_response(jsonify(task), 200)
|
||||
|
||||
|
||||
def task_submit_user_data(
|
||||
task_id: int, body: Dict[str, Any], terminate_loop: bool = False
|
||||
def task_submit(
|
||||
process_instance_id: int,
|
||||
task_id: str,
|
||||
body: Dict[str, Any],
|
||||
terminate_loop: bool = False,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Task_submit_user_data."""
|
||||
principal = find_principal_or_raise()
|
||||
active_task_assigned_to_me = find_active_task_by_id_or_raise(task_id, principal.id)
|
||||
active_task_assigned_to_me = find_active_task_by_id_or_raise(
|
||||
process_instance_id, task_id, principal.id
|
||||
)
|
||||
|
||||
process_instance = find_process_instance_by_id_or_raise(
|
||||
active_task_assigned_to_me.process_instance_id
|
||||
)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
spiffworkflow_task_uuid = uuid.UUID(
|
||||
active_task_assigned_to_me.spiffworkflow_task_id
|
||||
spiff_task = get_spiff_task_from_process_instance(
|
||||
task_id, process_instance, processor=processor
|
||||
)
|
||||
spiff_task = processor.bpmn_process_instance.get_task(spiffworkflow_task_uuid)
|
||||
|
||||
if spiff_task is None:
|
||||
raise (
|
||||
ApiError(
|
||||
code="empty_task",
|
||||
message="Processor failed to obtain task.",
|
||||
status_code=500,
|
||||
)
|
||||
)
|
||||
if spiff_task.state != TaskState.READY:
|
||||
raise (
|
||||
ApiError(
|
||||
|
@ -650,7 +702,7 @@ def task_submit_user_data(
|
|||
assigned_principal_id=principal.id, process_instance_id=process_instance.id
|
||||
).first()
|
||||
if next_active_task_assigned_to_me:
|
||||
return make_response(jsonify(next_active_task_assigned_to_me), 200)
|
||||
return make_response(jsonify(next_active_task_assigned_to_me.to_task()), 200)
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=202, mimetype="application/json")
|
||||
|
||||
|
@ -701,17 +753,23 @@ def find_principal_or_raise() -> PrincipalModel:
|
|||
|
||||
|
||||
def find_active_task_by_id_or_raise(
|
||||
task_id: int, principal_id: PrincipalModel
|
||||
process_instance_id: int, task_id: str, principal_id: PrincipalModel
|
||||
) -> ActiveTaskModel:
|
||||
"""Find_active_task_by_id_or_raise."""
|
||||
active_task_assigned_to_me = ActiveTaskModel.query.filter_by(
|
||||
id=task_id, assigned_principal_id=principal_id
|
||||
process_instance_id=process_instance_id,
|
||||
task_id=task_id,
|
||||
assigned_principal_id=principal_id,
|
||||
).first()
|
||||
if active_task_assigned_to_me is None:
|
||||
message = (
|
||||
f"Task not found for principal user {principal_id} "
|
||||
f"process_instance_id: {process_instance_id}, task_id: {task_id}"
|
||||
)
|
||||
raise (
|
||||
ApiError(
|
||||
code="task_not_found",
|
||||
message=f"Task not found for principal user: {principal_id} and id: {task_id}",
|
||||
message=message,
|
||||
status_code=400,
|
||||
)
|
||||
)
|
||||
|
@ -734,3 +792,53 @@ def find_process_instance_by_id_or_raise(
|
|||
)
|
||||
)
|
||||
return process_instance # type: ignore
|
||||
|
||||
|
||||
def get_value_from_array_with_index(array: list, index: int) -> Any:
|
||||
"""Get_value_from_array_with_index."""
|
||||
if index < 0:
|
||||
return None
|
||||
|
||||
if index >= len(array):
|
||||
return None
|
||||
|
||||
return array[index]
|
||||
|
||||
|
||||
def prepare_form_data(
|
||||
form_file: str, task_data: Union[dict, None], process_model: ProcessModelInfo
|
||||
) -> str:
|
||||
"""Prepare_form_data."""
|
||||
if task_data is None:
|
||||
return ""
|
||||
|
||||
file_contents = SpecFileService.get_data(process_model, form_file).decode("utf-8")
|
||||
|
||||
# trade out pieces like "{{variable_name}}" for the corresponding form data value
|
||||
for key, value in task_data.items():
|
||||
if isinstance(value, str) or isinstance(value, int):
|
||||
file_contents = file_contents.replace("{{" + key + "}}", str(value))
|
||||
|
||||
return file_contents
|
||||
|
||||
|
||||
def get_spiff_task_from_process_instance(
|
||||
task_id: str,
|
||||
process_instance: ProcessInstanceModel,
|
||||
processor: Union[ProcessInstanceProcessor, None] = None,
|
||||
) -> SpiffTask:
|
||||
"""Get_spiff_task_from_process_instance."""
|
||||
if processor is None:
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
task_uuid = uuid.UUID(task_id)
|
||||
spiff_task = processor.bpmn_process_instance.get_task(task_uuid)
|
||||
|
||||
if spiff_task is None:
|
||||
raise (
|
||||
ApiError(
|
||||
code="empty_task",
|
||||
message="Processor failed to obtain task.",
|
||||
status_code=500,
|
||||
)
|
||||
)
|
||||
return spiff_task
|
||||
|
|
|
@ -150,6 +150,10 @@ class Script:
|
|||
def add_data_to_task(self, task: Task, data: Any) -> None:
|
||||
"""Add_data_to_task."""
|
||||
key = self.__class__.__name__
|
||||
|
||||
if task.data is None:
|
||||
task.data = {}
|
||||
|
||||
if key in task.data:
|
||||
task.data[key].update(data)
|
||||
else:
|
||||
|
|
|
@ -355,22 +355,26 @@ class ProcessInstanceProcessor:
|
|||
extensions = ready_or_waiting_task.task_spec.extensions
|
||||
|
||||
form_file_name = None
|
||||
ui_form_file_name = None
|
||||
if "properties" in extensions:
|
||||
properties = extensions["properties"]
|
||||
if "formJsonSchemaFilename" in properties:
|
||||
form_file_name = properties["formJsonSchemaFilename"]
|
||||
# FIXME:
|
||||
# if "formUiSchemaFilename" in properties:
|
||||
# form_file_name = properties["formUiSchemaFilename"]
|
||||
if "formUiSchemaFilename" in properties:
|
||||
ui_form_file_name = properties["formUiSchemaFilename"]
|
||||
|
||||
active_task = ActiveTaskModel(
|
||||
spiffworkflow_task_id=str(ready_or_waiting_task.id),
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
# FIXME: look for the correct principal based on ready_or_waiting_task.lane
|
||||
assigned_principal_id=PrincipalModel.query.first().id,
|
||||
spiffworkflow_task_data=json.dumps(ready_or_waiting_task.data),
|
||||
status=ready_or_waiting_task.state.name,
|
||||
form_file_name=form_file_name,
|
||||
ui_form_file_name=ui_form_file_name,
|
||||
task_id=str(ready_or_waiting_task.id),
|
||||
task_name=ready_or_waiting_task.task_spec.name,
|
||||
task_title=ready_or_waiting_task.task_spec.description,
|
||||
task_type=ready_or_waiting_task.task_spec.__class__.__name__,
|
||||
task_status=ready_or_waiting_task.state.name,
|
||||
task_data=json.dumps(ready_or_waiting_task.data),
|
||||
)
|
||||
db.session.add(active_task)
|
||||
|
||||
|
|
|
@ -304,6 +304,10 @@ class ProcessInstanceService:
|
|||
form_data = ProcessInstanceService.extract_form_data(
|
||||
spiff_task.data, spiff_task
|
||||
)
|
||||
multi_instance_type_value = ""
|
||||
if task.multi_instance_type:
|
||||
multi_instance_type_value = task.multi_instance_type.value
|
||||
|
||||
task_event = TaskEventModel(
|
||||
# study_id=processor.workflow_model.study_id,
|
||||
user_id=user_id,
|
||||
|
@ -317,7 +321,7 @@ class ProcessInstanceService:
|
|||
task_state=task.state,
|
||||
task_lane=task.lane,
|
||||
form_data=form_data,
|
||||
mi_type=task.multi_instance_type.value, # Some tasks have a repeat behavior.
|
||||
mi_type=multi_instance_type_value, # Some tasks have a repeat behavior.
|
||||
mi_count=task.multi_instance_count, # This is the number of times the task could repeat.
|
||||
mi_index=task.multi_instance_index, # And the index of the currently repeating task.
|
||||
process_name=task.process_name,
|
||||
|
@ -431,13 +435,10 @@ class ProcessInstanceService:
|
|||
spiff_task.task_spec.description,
|
||||
task_type,
|
||||
spiff_task.get_state_name(),
|
||||
lane,
|
||||
None,
|
||||
"",
|
||||
{},
|
||||
mi_type,
|
||||
info["mi_count"],
|
||||
info["mi_index"],
|
||||
lane=lane,
|
||||
multi_instance_type=mi_type,
|
||||
multi_instance_count=info["mi_count"],
|
||||
multi_instance_index=info["mi_index"],
|
||||
process_name=spiff_task.task_spec._wf_spec.description,
|
||||
properties=props,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue