some updates to always pass back task intead of active_task

This commit is contained in:
jasquat 2022-07-25 11:48:42 -04:00
parent 6a22ea5167
commit f65228c87f
9 changed files with 164 additions and 126 deletions

View File

@ -1,3 +1,5 @@
from __future__ import with_statement
import logging import logging
from logging.config import fileConfig from logging.config import fileConfig

View File

@ -1,8 +1,8 @@
"""empty message """empty message
Revision ID: 3f7aa48eaf3a Revision ID: b4f678040235
Revises: Revises:
Create Date: 2022-07-11 16:08:00.002287 Create Date: 2022-07-25 09:46:39.406847
""" """
from alembic import op from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '3f7aa48eaf3a' revision = 'b4f678040235'
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -95,18 +95,22 @@ def upgrade():
) )
op.create_table('active_task', op.create_table('active_task',
sa.Column('id', sa.Integer(), nullable=False), 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('process_instance_id', sa.Integer(), nullable=False),
sa.Column('assigned_principal_id', sa.Integer(), nullable=True), 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('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('updated_at_in_seconds', sa.Integer(), nullable=True),
sa.Column('created_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(['assigned_principal_id'], ['principal.id'], ),
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
sa.PrimaryKeyConstraint('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', op.create_table('file',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),

View File

@ -1,2 +1,2 @@
"""Config.""" """Config."""
BPMN_SPEC_ABSOLUTE_DIR = "BPMN_SPECS_DEV" # BPMN_SPEC_ABSOLUTE_DIR = "BPMN_SPECS_DEV"

View File

@ -645,7 +645,7 @@ paths:
# '204': # '204':
# description: The file was removed. # description: The file was removed.
/tasks/my-tasks: /tasks:
parameters: parameters:
- name: page - name: page
in: query in: query
@ -670,53 +670,10 @@ paths:
schema: schema:
type: array type: array
items: items:
# $ref: "#/components/schemas/ActiveTask"
$ref: "#/components/schemas/Task" $ref: "#/components/schemas/Task"
/tasks/{active_task_id}: /tasks/{process_instance_id}/{task_id}:
parameters: parameters:
- name: active_task_id - name: task_id
in: path
required: true
description: The unique id of an existing process group.
schema:
type: string
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_show
summary: Gets one task that a user wants to complete
responses:
"200":
description: One task
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
/tasks/completed_user_task/{process_instance_id}/{spiffworkflow_task_id}:
parameters:
- name: process_instance_id
in: path
required: true
description: The unique id of an existing process instance.
schema:
type: integer
- name: spiffworkflow_task_id
in: path
required: true
description: The unique id of an existing process group.
schema:
type: string
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_show_completed_user_task
summary: Gets one task that a user wants to complete
responses:
"200":
description: One task
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
/tasks/{active_task_id}/submit:
parameters:
- name: active_task_id
in: path in: path
required: true required: true
description: The unique id of an existing process group. description: The unique id of an existing process group.
@ -728,9 +685,18 @@ paths:
description: Terminate the loop on a looping task description: Terminate the loop on a looping task
schema: schema:
type: boolean type: boolean
get:
post: operationId: spiffworkflow_backend.routes.process_api_blueprint.task_show
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit_user_data summary: Gets one task that a user wants to complete
responses:
"200":
description: One task
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit
summary: Update the form data for a tasks summary: Update the form data for a tasks
requestBody: requestBody:
content: content:

View File

@ -1,6 +1,7 @@
"""Active_task.""" """Active_task."""
from __future__ import annotations from __future__ import annotations
import json
from dataclasses import dataclass from dataclasses import dataclass
from flask_bpmn.models.db import db 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.principal import PrincipalModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.task import Task
@dataclass @dataclass
@ -20,26 +22,45 @@ class ActiveTaskModel(SpiffworkflowBaseDBModel):
__tablename__ = "active_task" __tablename__ = "active_task"
__table_args__ = ( __table_args__ = (
db.UniqueConstraint( 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 = "" # form_json: str | None = ""
bpmn_json: str = "" # bpmn_json: str = ""
preceding_spiffworkflow_user_task_id: int | None = None # preceding_spiffworkflow_user_task_id: int | None = None
assigned_principal: RelationshipProperty[PrincipalModel] = relationship( assigned_principal: RelationshipProperty[PrincipalModel] = relationship(
PrincipalModel PrincipalModel
) )
id: int = db.Column(db.Integer, primary_key=True) 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( process_instance_id: int = db.Column(
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
) )
assigned_principal_id: int = db.Column(ForeignKey(PrincipalModel.id)) 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)) 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) updated_at_in_seconds: int = db.Column(db.Integer)
created_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,
)

View File

@ -1,7 +1,6 @@
"""Task.""" """Task."""
import enum import enum
from typing import Any from typing import Any
from typing import Union
import marshmallow import marshmallow
from marshmallow import Schema from marshmallow import Schema
@ -102,21 +101,21 @@ class Task:
title: str, title: str,
type: str, type: str,
state: str, state: str,
lane: str, lane: str | None = None,
form: None, form: None = None,
documentation: str, documentation: str = "",
data: dict[str, Any], data: dict[str, Any] | None = None,
multi_instance_type: MultiInstanceType, multi_instance_type: MultiInstanceType | None = None,
multi_instance_count: str, multi_instance_count: str = "",
multi_instance_index: str, multi_instance_index: str = "",
process_name: str, process_name: str = "",
properties: dict, properties: dict | None = None,
process_instance_id: Union[int, None] = None, process_instance_id: int | None = None,
form_schema: Union[str, None] = None, form_schema: str | None = None,
form_ui_schema: Union[str, None] = None, form_ui_schema: str | None = None,
preceding_spiffworkflow_user_task_id: Union[str, int, None] = None, # preceding_spiffworkflow_user_task_id: Union[str, int, None] = None,
following_spiffworkflow_user_task_id: Union[str, int, None] = None, # following_spiffworkflow_user_task_id: Union[str, int, None] = None,
current_active_task_id: Union[int, None] = None, # current_active_task_id: Union[int, None] = None,
): ):
"""__init__.""" """__init__."""
self.id = id self.id = id
@ -124,17 +123,20 @@ class Task:
self.title = title self.title = title
self.type = type self.type = type
self.state = state self.state = state
self.form = None self.form = form
self.documentation = documentation self.documentation = documentation
self.data = data
self.lane = lane self.lane = lane
self.data = data
if self.data is None:
self.data = {}
self.process_instance_id = process_instance_id self.process_instance_id = process_instance_id
self.form_schema = form_schema self.form_schema = form_schema
self.form_ui_schema = form_ui_schema self.form_ui_schema = form_ui_schema
self.preceding_spiffworkflow_user_task_id = preceding_spiffworkflow_user_task_id # self.preceding_spiffworkflow_user_task_id = preceding_spiffworkflow_user_task_id
self.following_spiffworkflow_user_task_id = following_spiffworkflow_user_task_id # self.following_spiffworkflow_user_task_id = following_spiffworkflow_user_task_id
self.current_active_task_id = current_active_task_id # self.current_active_task_id = current_active_task_id
self.multi_instance_type = ( self.multi_instance_type = (
multi_instance_type # Some tasks have a repeat behavior. multi_instance_type # Some tasks have a repeat behavior.
@ -146,7 +148,33 @@ class Task:
multi_instance_index # And the index of the currently repeating task. multi_instance_index # And the index of the currently repeating task.
) )
self.process_name = process_name self.process_name = process_name
self.properties = properties # Arbitrary extension properties from BPMN editor. 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."""
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": self.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 @classmethod
def valid_property_names(cls) -> list[str]: def valid_property_names(cls) -> list[str]:
@ -246,9 +274,9 @@ class TaskSchema(Schema):
"process_instance_id", "process_instance_id",
"form_schema", "form_schema",
"form_ui_schema", "form_ui_schema",
"preceding_spiffworkflow_user_task_id", # "preceding_spiffworkflow_user_task_id",
"following_spiffworkflow_user_task_id", # "following_spiffworkflow_user_task_id",
"current_active_task_id", # "current_active_task_id",
] ]
multi_instance_type = EnumField(MultiInstanceType) multi_instance_type = EnumField(MultiInstanceType)

View File

@ -540,23 +540,26 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
.paginate(page, per_page, False) .paginate(page, per_page, False)
) )
tasks = [active_task.to_task() for active_task in active_tasks.items]
response_json = { response_json = {
"results": active_tasks.items, "results": tasks,
"pagination": { "pagination": {
"count": len(active_tasks.items), "count": len(active_tasks.items),
"total": active_tasks.total, "total": active_tasks.total,
"pages": active_tasks.pages, "pages": active_tasks.pages,
}, },
} }
return make_response(jsonify(response_json), 200) return make_response(jsonify(response_json), 200)
def task_show(active_task_id: int) -> flask.wrappers.Response: def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response:
"""Task_list_my_tasks.""" """Task_list_my_tasks."""
principal = find_principal_or_raise() principal = find_principal_or_raise()
active_task_assigned_to_me = find_active_task_by_id_or_raise( active_task_assigned_to_me = find_active_task_by_id_or_raise(
active_task_id, principal.id process_instance_id, task_id, principal.id
) )
process_instance = find_process_instance_by_id_or_raise( process_instance = find_process_instance_by_id_or_raise(
@ -571,31 +574,35 @@ def task_show(active_task_id: int) -> flask.wrappers.Response:
raise ( raise (
ApiError( ApiError(
code="missing_form_file", code="missing_form_file",
message=f"Cannot find a form file for active task {active_task_id}", message=f"Cannot find a form file for process_instance_id: {process_instance_id}, task_id: {task_id}",
status_code=500, status_code=500,
) )
) )
form_contents = prepare_form_data( form_contents = prepare_form_data(
active_task_assigned_to_me.form_file_name, active_task_assigned_to_me.form_file_name,
json.loads(active_task_assigned_to_me.spiffworkflow_task_data), json.loads(active_task_assigned_to_me.task_data),
process_model, process_model,
) )
task = active_task_assigned_to_me.to_task()
if form_contents: if form_contents:
active_task_assigned_to_me.form_json = form_contents task.form_schema = form_contents
# FIXME: This should be stored in the db when the active task is created if active_task_assigned_to_me.ui_form_file_name:
processor = ProcessInstanceProcessor(process_instance) ui_form_contents = prepare_form_data(
if processor.completed_user_tasks(): active_task_assigned_to_me.ui_form_file_name,
active_task_assigned_to_me.preceding_spiffworkflow_user_task_id = ( json.loads(active_task_assigned_to_me.task_data),
processor.completed_user_tasks()[0].id 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_show_completed_user_task( def task_show_completed_user_task(
process_instance_id: int, spiffworkflow_task_id: str process_instance_id: int, spiffworkflow_task_uuid: str
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_show_completed_user_task.""" """Task_show_completed_user_task."""
process_instance = find_process_instance_by_id_or_raise(process_instance_id) process_instance = find_process_instance_by_id_or_raise(process_instance_id)
@ -614,7 +621,7 @@ def task_show_completed_user_task(
preceding_spiffworkflow_user_task_id = None preceding_spiffworkflow_user_task_id = None
following_spiffworkflow_user_task_id = None following_spiffworkflow_user_task_id = None
for index, completed_user_task in enumerate(processor.completed_user_tasks()): for index, completed_user_task in enumerate(processor.completed_user_tasks()):
if str(completed_user_task.id) == spiffworkflow_task_id: if str(completed_user_task.id) == spiffworkflow_task_uuid:
spiffworkflow_task = completed_user_task spiffworkflow_task = completed_user_task
preceding_spiffworkflow_user_task_id = get_value_from_array_with_index( preceding_spiffworkflow_user_task_id = get_value_from_array_with_index(
processor.completed_user_tasks(), index + 1 processor.completed_user_tasks(), index + 1
@ -627,7 +634,7 @@ def task_show_completed_user_task(
raise ( raise (
ApiError( ApiError(
code="no_completed_user_task_with_id", code="no_completed_user_task_with_id",
message=f"This process instance does not have a completed user task with given id: {spiffworkflow_task_id}", message=f"This process instance does not have a completed user task with given id: {spiffworkflow_task_uuid}",
status_code=400, status_code=400,
) )
) )
@ -677,13 +684,16 @@ def task_show_completed_user_task(
) )
def task_submit_user_data( def task_submit(
active_task_id: int, body: Dict[str, Any], terminate_loop: bool = False process_instance_id: int,
task_id: str,
body: Dict[str, Any],
terminate_loop: bool = False,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_submit_user_data.""" """Task_submit_user_data."""
principal = find_principal_or_raise() principal = find_principal_or_raise()
active_task_assigned_to_me = find_active_task_by_id_or_raise( active_task_assigned_to_me = find_active_task_by_id_or_raise(
active_task_id, principal.id process_instance_id, task_id, principal.id
) )
process_instance = find_process_instance_by_id_or_raise( process_instance = find_process_instance_by_id_or_raise(
@ -691,10 +701,10 @@ def task_submit_user_data(
) )
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
spiffworkflow_task_uuid = uuid.UUID( task_uuid = uuid.UUID(
active_task_assigned_to_me.spiffworkflow_task_id active_task_assigned_to_me.task_id
) )
spiff_task = processor.bpmn_process_instance.get_task(spiffworkflow_task_uuid) spiff_task = processor.bpmn_process_instance.get_task(task_uuid)
if spiff_task is None: if spiff_task is None:
raise ( raise (
@ -738,7 +748,7 @@ def task_submit_user_data(
assigned_principal_id=principal.id, process_instance_id=process_instance.id assigned_principal_id=principal.id, process_instance_id=process_instance.id
).first() ).first()
if next_active_task_assigned_to_me: 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") return Response(json.dumps({"ok": True}), status=202, mimetype="application/json")
@ -789,17 +799,23 @@ def find_principal_or_raise() -> PrincipalModel:
def find_active_task_by_id_or_raise( def find_active_task_by_id_or_raise(
active_task_id: int, principal_id: PrincipalModel process_instance_id: int, task_id: str, principal_id: PrincipalModel
) -> ActiveTaskModel: ) -> ActiveTaskModel:
"""Find_active_task_by_id_or_raise.""" """Find_active_task_by_id_or_raise."""
active_task_assigned_to_me = ActiveTaskModel.query.filter_by( active_task_assigned_to_me = ActiveTaskModel.query.filter_by(
id=active_task_id, assigned_principal_id=principal_id process_instance_id=process_instance_id,
task_id=task_id,
assigned_principal_id=principal_id,
).first() ).first()
if active_task_assigned_to_me is None: 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 ( raise (
ApiError( ApiError(
code="task_not_found", code="task_not_found",
message=f"Task not found for principal user: {principal_id} and id: {active_task_id}", message=message,
status_code=400, status_code=400,
) )
) )

View File

@ -355,22 +355,26 @@ class ProcessInstanceProcessor:
extensions = ready_or_waiting_task.task_spec.extensions extensions = ready_or_waiting_task.task_spec.extensions
form_file_name = None form_file_name = None
ui_form_file_name = None
if "properties" in extensions: if "properties" in extensions:
properties = extensions["properties"] properties = extensions["properties"]
if "formJsonSchemaFilename" in properties: if "formJsonSchemaFilename" in properties:
form_file_name = properties["formJsonSchemaFilename"] form_file_name = properties["formJsonSchemaFilename"]
# FIXME: if "formUiSchemaFilename" in properties:
# if "formUiSchemaFilename" in properties: ui_form_file_name = properties["formUiSchemaFilename"]
# form_file_name = properties["formUiSchemaFilename"]
active_task = ActiveTaskModel( active_task = ActiveTaskModel(
spiffworkflow_task_id=str(ready_or_waiting_task.id),
process_instance_id=self.process_instance_model.id, process_instance_id=self.process_instance_model.id,
# FIXME: look for the correct principal based on ready_or_waiting_task.lane # FIXME: look for the correct principal based on ready_or_waiting_task.lane
assigned_principal_id=PrincipalModel.query.first().id, 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, 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) db.session.add(active_task)

View File

@ -431,13 +431,10 @@ class ProcessInstanceService:
spiff_task.task_spec.description, spiff_task.task_spec.description,
task_type, task_type,
spiff_task.get_state_name(), spiff_task.get_state_name(),
lane, lane=lane,
None, multi_instance_type=mi_type,
"", multi_instance_count= info["mi_count"],
{}, multi_instance_index=info["mi_index"],
mi_type,
info["mi_count"],
info["mi_index"],
process_name=spiff_task.task_spec._wf_spec.description, process_name=spiff_task.task_spec._wf_spec.description,
properties=props, properties=props,
) )