From 17a2c19227ad901af72346af4c64476687daa2d5 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 28 Jun 2022 17:11:10 -0400 Subject: [PATCH] added api call to get active tasks w/ burnettk --- migrations/env.py | 2 -- .../{86938cb17c3d_.py => a650f4061955_.py} | 8 ++--- src/spiffworkflow_backend/api.yml | 28 +++++++++++++++++ .../helpers/fixture_data.py | 7 +++++ .../load_database_models.py | 2 +- .../models/active_task.py | 13 ++++---- src/spiffworkflow_backend/models/principal.py | 4 +-- .../routes/admin_blueprint/admin_blueprint.py | 7 +++++ .../routes/process_api_blueprint.py | 30 +++++++++++++++++++ .../services/process_instance_processor.py | 22 +++++++++++++- 10 files changed, 108 insertions(+), 15 deletions(-) rename migrations/versions/{86938cb17c3d_.py => a650f4061955_.py} (98%) diff --git a/migrations/env.py b/migrations/env.py index 68feded2..630e381a 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import logging from logging.config import fileConfig diff --git a/migrations/versions/86938cb17c3d_.py b/migrations/versions/a650f4061955_.py similarity index 98% rename from migrations/versions/86938cb17c3d_.py rename to migrations/versions/a650f4061955_.py index a352d03c..9b4de3ee 100644 --- a/migrations/versions/86938cb17c3d_.py +++ b/migrations/versions/a650f4061955_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 86938cb17c3d +Revision ID: a650f4061955 Revises: -Create Date: 2022-06-28 14:21:54.294238 +Create Date: 2022-06-28 15:15:08.319053 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '86938cb17c3d' +revision = 'a650f4061955' down_revision = None branch_labels = None depends_on = None @@ -93,7 +93,7 @@ def upgrade(): sa.Column('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('task_data', sa.Text(), nullable=True), + sa.Column('process_instance_data', sa.Text(), nullable=True), sa.Column('status', sa.String(length=50), nullable=False), sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index df0ae552..fbca2f64 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -552,6 +552,34 @@ paths: # '204': # description: The file was removed. + /tasks/my-tasks: + parameters: + - name: page + in: query + required: false + description: The page number to return. Defaults to page 1. + schema: + type: integer + - name: per_page + in: query + required: false + description: The page number to return. Defaults to page 1. + schema: + type: integer + get: + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_my_tasks + summary: returns the list of ready or waiting tasks for a user + responses: + "200": + description: list of tasks + content: + application/json: + schema: + type: array + items: + # $ref: "#/components/schemas/ActiveTask" + $ref: "#/components/schemas/Task" + components: securitySchemes: jwt: diff --git a/src/spiffworkflow_backend/helpers/fixture_data.py b/src/spiffworkflow_backend/helpers/fixture_data.py index 0e640472..1ca43312 100644 --- a/src/spiffworkflow_backend/helpers/fixture_data.py +++ b/src/spiffworkflow_backend/helpers/fixture_data.py @@ -3,6 +3,7 @@ from typing import Any from flask_bpmn.models.db import db +from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.user import UserModel @@ -14,4 +15,10 @@ def find_or_create_user(username: str = "test_user1") -> Any: db.session.add(user) db.session.commit() + principal = PrincipalModel.query.filter_by(user_id=user.id).first() + if principal is None: + principal = PrincipalModel(user_id=user.id) + db.session.add(principal) + db.session.commit() + return user diff --git a/src/spiffworkflow_backend/load_database_models.py b/src/spiffworkflow_backend/load_database_models.py index a5d41227..41fa2e97 100644 --- a/src/spiffworkflow_backend/load_database_models.py +++ b/src/spiffworkflow_backend/load_database_models.py @@ -4,6 +4,7 @@ autoflake8 will remove these lines without the noqa comment """ from flask_bpmn.models.db import add_listeners +from spiffworkflow_backend.models.active_task import ActiveTaskModel # noqa: F401 from spiffworkflow_backend.models.data_store import DataStoreModel # noqa: F401 from spiffworkflow_backend.models.file import FileModel # noqa: F401 from spiffworkflow_backend.models.principal import PrincipalModel # noqa: F401 @@ -14,7 +15,6 @@ from spiffworkflow_backend.models.process_instance_report import ( ProcessInstanceReportModel, ) # noqa: F401 from spiffworkflow_backend.models.task_event import TaskEventModel # noqa: F401 -from spiffworkflow_backend.models.active_task import ActiveTaskModel # noqa: F401 from spiffworkflow_backend.models.user import UserModel # noqa: F401 from spiffworkflow_backend.models.user_group_assignment import ( UserGroupAssignmentModel, diff --git a/src/spiffworkflow_backend/models/active_task.py b/src/spiffworkflow_backend/models/active_task.py index a61a4afa..57136687 100644 --- a/src/spiffworkflow_backend/models/active_task.py +++ b/src/spiffworkflow_backend/models/active_task.py @@ -1,28 +1,31 @@ """Active_task.""" - from __future__ import annotations +from dataclasses import dataclass + from flask_bpmn.models.db import db from flask_bpmn.models.db import SpiffworkflowBaseDBModel from sqlalchemy import ForeignKey -from sqlalchemy.orm import relationship from spiffworkflow_backend.models.principal import PrincipalModel +@dataclass class ActiveTaskModel(SpiffworkflowBaseDBModel): """ActiveTaskModel.""" + __tablename__ = "active_task" __table_args__ = ( - db.UniqueConstraint("task_id", "process_instance_id", name="active_task_unique"), + db.UniqueConstraint( + "task_id", "process_instance_id", name="active_task_unique" + ), ) id: int = db.Column(db.Integer, primary_key=True) task_id: str = db.Column(db.String(50), nullable=False) process_instance_id: int = db.Column(db.Integer, nullable=False) assigned_principal_id: int = db.Column(ForeignKey(PrincipalModel.id)) - assigned_principal: relationship(PrincipalModel) - task_data: str = db.Column(db.Text) + process_instance_data: str = db.Column(db.Text) status: str = db.Column(db.String(50), nullable=False) updated_at_in_seconds: int = db.Column(db.Integer) diff --git a/src/spiffworkflow_backend/models/principal.py b/src/spiffworkflow_backend/models/principal.py index 9ee01102..68c9140c 100644 --- a/src/spiffworkflow_backend/models/principal.py +++ b/src/spiffworkflow_backend/models/principal.py @@ -15,5 +15,5 @@ class PrincipalModel(SpiffworkflowBaseDBModel): __table_args__ = (CheckConstraint("NOT(user_id IS NULL AND group_id IS NULL)"),) id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(ForeignKey(UserModel.id), nullable=True) - group_id = db.Column(ForeignKey(GroupModel.id), nullable=True) + user_id = db.Column(ForeignKey(UserModel.id), nullable=True, unique=True) + group_id = db.Column(ForeignKey(GroupModel.id), nullable=True, unique=True) diff --git a/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py b/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py index b889f753..3c19596e 100644 --- a/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py +++ b/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py @@ -12,6 +12,7 @@ from flask import url_for from flask_bpmn.models.db import db from werkzeug.wrappers.response import Response +from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, @@ -41,6 +42,12 @@ def token() -> str: db.session.add(user) db.session.commit() + principal = PrincipalModel.query.filter_by(user_id=user.id).first() + if principal is None: + principal = PrincipalModel(user_id=user.id) + db.session.add(principal) + db.session.commit() + auth_token = user.encode_auth_token() return f"auth_token: {auth_token}" diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index b5ea25dd..b8c52411 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -16,6 +16,7 @@ from flask_bpmn.models.db import db from spiffworkflow_backend.exceptions.process_entity_not_found_error import ( ProcessEntityNotFoundError, ) +from spiffworkflow_backend.models.active_task import ActiveTaskModel from spiffworkflow_backend.models.file import FileSchema from spiffworkflow_backend.models.file import FileType from spiffworkflow_backend.models.principal import PrincipalModel @@ -430,6 +431,35 @@ def process_instance_report( return Response(json.dumps(response_json), status=200, mimetype="application/json") +def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Response: + """Task_list_my_tasks.""" + principal = PrincipalModel.query.filter_by(user_id=g.user.id).first() + if principal is None: + raise ( + ApiError( + code="principal_not_found", + message=f"Principal not found from user id: {g.user.id}", + status_code=400, + ) + ) + + active_tasks = ( + ActiveTaskModel.query.filter_by(assigned_principal_id=principal.id) + .order_by(ActiveTaskModel.id.desc()) + .paginate(page, per_page, False) + ) + + response_json = { + "results": active_tasks.items, + "pagination": { + "count": len(active_tasks.items), + "total": active_tasks.total, + "pages": active_tasks.pages, + }, + } + return response_json + + def get_file_from_request() -> Any: """Get_file_from_request.""" request_file = connexion.request.files.get("file") diff --git a/src/spiffworkflow_backend/services/process_instance_processor.py b/src/spiffworkflow_backend/services/process_instance_processor.py index e0745ebd..ba7969f3 100644 --- a/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/src/spiffworkflow_backend/services/process_instance_processor.py @@ -30,10 +30,12 @@ from SpiffWorkflow.dmn.serializer import BusinessRuleTaskConverter # type: igno from SpiffWorkflow.exceptions import WorkflowTaskExecException # type: ignore from SpiffWorkflow.serializer.exceptions import MissingSpecError # type: ignore from SpiffWorkflow.specs import WorkflowSpec # type: ignore -from SpiffWorkflow.task import Task # type: ignore +from SpiffWorkflow.task import Task +from spiffworkflow_backend.models.active_task import ActiveTaskModel # type: ignore from spiffworkflow_backend.models.file import File from spiffworkflow_backend.models.file import FileType +from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus from spiffworkflow_backend.models.process_model import ProcessModelInfo @@ -331,6 +333,19 @@ class ProcessInstanceProcessor: self.process_instance_model.end_in_seconds = round(time.time()) db.session.add(self.process_instance_model) + + ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks() + for ready_or_waiting_task in ready_or_waiting_tasks: + active_task = ActiveTaskModel( + 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, + process_instance_data=json.dumps(self.get_data()), + status=ready_or_waiting_task.state.value, + ) + db.session.add(active_task) + db.session.commit() @staticmethod @@ -555,6 +570,11 @@ class ProcessInstanceProcessor: and t.state in [TaskState.COMPLETED, TaskState.CANCELLED] ] + def get_all_ready_or_waiting_tasks(self) -> list[Task]: + """Get_all_ready_or_waiting_tasks.""" + all_tasks = self.bpmn_process_instance.get_tasks(TaskState.ANY_MASK) + return [t for t in all_tasks if t.state in [TaskState.WAITING, TaskState.READY]] + def get_nav_item(self, task: Task) -> Any: """Get_nav_item.""" for nav_item in self.bpmn_process_instance.get_nav_list():