From f65228c87fb4c880ab5e2251a9e885a739d56335 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 25 Jul 2022 11:48:42 -0400 Subject: [PATCH] some updates to always pass back task intead of active_task --- migrations/env.py | 2 + .../{3f7aa48eaf3a_.py => b4f678040235_.py} | 18 +++-- src/instance/config.py | 2 +- src/spiffworkflow_backend/api.yml | 64 ++++------------ .../models/active_task.py | 35 +++++++-- src/spiffworkflow_backend/models/task.py | 76 +++++++++++++------ .../routes/process_api_blueprint.py | 66 ++++++++++------ .../services/process_instance_processor.py | 16 ++-- .../services/process_instance_service.py | 11 +-- 9 files changed, 164 insertions(+), 126 deletions(-) rename migrations/versions/{3f7aa48eaf3a_.py => b4f678040235_.py} (93%) diff --git a/migrations/env.py b/migrations/env.py index 630e381a..68feded2 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,3 +1,5 @@ +from __future__ import with_statement + import logging from logging.config import fileConfig diff --git a/migrations/versions/3f7aa48eaf3a_.py b/migrations/versions/b4f678040235_.py similarity index 93% rename from migrations/versions/3f7aa48eaf3a_.py rename to migrations/versions/b4f678040235_.py index d3795dd6..efb50d48 100644 --- a/migrations/versions/3f7aa48eaf3a_.py +++ b/migrations/versions/b4f678040235_.py @@ -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), diff --git a/src/instance/config.py b/src/instance/config.py index 7c254351..9901d211 100644 --- a/src/instance/config.py +++ b/src/instance/config.py @@ -1,2 +1,2 @@ """Config.""" -BPMN_SPEC_ABSOLUTE_DIR = "BPMN_SPECS_DEV" +# BPMN_SPEC_ABSOLUTE_DIR = "BPMN_SPECS_DEV" diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index 05356176..86bab573 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -645,7 +645,7 @@ paths: # '204': # description: The file was removed. - /tasks/my-tasks: + /tasks: parameters: - name: page in: query @@ -670,53 +670,10 @@ paths: schema: type: array items: - # $ref: "#/components/schemas/ActiveTask" $ref: "#/components/schemas/Task" - /tasks/{active_task_id}: + /tasks/{process_instance_id}/{task_id}: parameters: - - name: active_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 + - name: task_id in: path required: true description: The unique id of an existing process group. @@ -728,9 +685,18 @@ paths: description: Terminate the loop on a looping task schema: type: boolean - - post: - operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit_user_data + 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" + put: + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit summary: Update the form data for a tasks requestBody: content: diff --git a/src/spiffworkflow_backend/models/active_task.py b/src/spiffworkflow_backend/models/active_task.py index 113b9fc1..180b4358 100644 --- a/src/spiffworkflow_backend/models/active_task.py +++ b/src/spiffworkflow_backend/models/active_task.py @@ -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,26 +22,45 @@ 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 = "" - preceding_spiffworkflow_user_task_id: int | None = None + # form_json: str | None = "" + # bpmn_json: str = "" + # preceding_spiffworkflow_user_task_id: int | None = None 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, + ) diff --git a/src/spiffworkflow_backend/models/task.py b/src/spiffworkflow_backend/models/task.py index 89b1788f..d5f9201e 100644 --- a/src/spiffworkflow_backend/models/task.py +++ b/src/spiffworkflow_backend/models/task.py @@ -1,7 +1,6 @@ """Task.""" import enum from typing import Any -from typing import Union import marshmallow from marshmallow import Schema @@ -102,21 +101,21 @@ 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, - process_instance_id: Union[int, None] = None, - form_schema: Union[str, None] = None, - form_ui_schema: Union[str, None] = None, - preceding_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, + lane: str | None = None, + form: None = None, + documentation: str = "", + data: dict[str, Any] | None = None, + multi_instance_type: MultiInstanceType | None = None, + multi_instance_count: str = "", + multi_instance_index: str = "", + process_name: str = "", + properties: dict | None = None, + process_instance_id: int | None = None, + form_schema: str | None = None, + form_ui_schema: str | None = None, + # preceding_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, ): """__init__.""" self.id = id @@ -124,17 +123,20 @@ 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.preceding_spiffworkflow_user_task_id = preceding_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.preceding_spiffworkflow_user_task_id = preceding_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.multi_instance_type = ( 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. ) 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.""" + 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 def valid_property_names(cls) -> list[str]: @@ -246,9 +274,9 @@ class TaskSchema(Schema): "process_instance_id", "form_schema", "form_ui_schema", - "preceding_spiffworkflow_user_task_id", - "following_spiffworkflow_user_task_id", - "current_active_task_id", + # "preceding_spiffworkflow_user_task_id", + # "following_spiffworkflow_user_task_id", + # "current_active_task_id", ] multi_instance_type = EnumField(MultiInstanceType) diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index 26904bb7..29ce0da7 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -540,23 +540,26 @@ 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(active_task_id: int) -> flask.wrappers.Response: +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( - active_task_id, principal.id + process_instance_id, task_id, principal.id ) process_instance = find_process_instance_by_id_or_raise( @@ -571,31 +574,35 @@ def task_show(active_task_id: int) -> flask.wrappers.Response: raise ( ApiError( 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, ) ) form_contents = prepare_form_data( 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, ) + + task = active_task_assigned_to_me.to_task() 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 - processor = ProcessInstanceProcessor(process_instance) - if processor.completed_user_tasks(): - active_task_assigned_to_me.preceding_spiffworkflow_user_task_id = ( - processor.completed_user_tasks()[0].id + if active_task_assigned_to_me.ui_form_file_name: + ui_form_contents = prepare_form_data( + active_task_assigned_to_me.ui_form_file_name, + json.loads(active_task_assigned_to_me.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_show_completed_user_task( - process_instance_id: int, spiffworkflow_task_id: str + process_instance_id: int, spiffworkflow_task_uuid: str ) -> flask.wrappers.Response: """Task_show_completed_user_task.""" 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 following_spiffworkflow_user_task_id = None 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 preceding_spiffworkflow_user_task_id = get_value_from_array_with_index( processor.completed_user_tasks(), index + 1 @@ -627,7 +634,7 @@ def task_show_completed_user_task( raise ( ApiError( 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, ) ) @@ -677,13 +684,16 @@ def task_show_completed_user_task( ) -def task_submit_user_data( - active_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( - active_task_id, principal.id + process_instance_id, task_id, principal.id ) process_instance = find_process_instance_by_id_or_raise( @@ -691,10 +701,10 @@ def task_submit_user_data( ) processor = ProcessInstanceProcessor(process_instance) - spiffworkflow_task_uuid = uuid.UUID( - active_task_assigned_to_me.spiffworkflow_task_id + task_uuid = uuid.UUID( + 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: raise ( @@ -738,7 +748,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") @@ -789,17 +799,23 @@ def find_principal_or_raise() -> PrincipalModel: 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: """Find_active_task_by_id_or_raise.""" 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() 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: {active_task_id}", + message=message, status_code=400, ) ) diff --git a/src/spiffworkflow_backend/services/process_instance_processor.py b/src/spiffworkflow_backend/services/process_instance_processor.py index c633d519..59e804fd 100644 --- a/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/src/spiffworkflow_backend/services/process_instance_processor.py @@ -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) diff --git a/src/spiffworkflow_backend/services/process_instance_service.py b/src/spiffworkflow_backend/services/process_instance_service.py index cea76786..2e5fec8f 100644 --- a/src/spiffworkflow_backend/services/process_instance_service.py +++ b/src/spiffworkflow_backend/services/process_instance_service.py @@ -431,13 +431,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, )