diff --git a/spiffworkflow-backend/migrations/versions/3bd6b0b1b8ae_.py b/spiffworkflow-backend/migrations/versions/bdd1d64689db_.py similarity index 83% rename from spiffworkflow-backend/migrations/versions/3bd6b0b1b8ae_.py rename to spiffworkflow-backend/migrations/versions/bdd1d64689db_.py index 80c47958..55566149 100644 --- a/spiffworkflow-backend/migrations/versions/3bd6b0b1b8ae_.py +++ b/spiffworkflow-backend/migrations/versions/bdd1d64689db_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 3bd6b0b1b8ae +Revision ID: bdd1d64689db Revises: -Create Date: 2022-10-25 12:31:50.177599 +Create Date: 2022-11-02 11:31:50.606843 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '3bd6b0b1b8ae' +revision = 'bdd1d64689db' down_revision = None branch_labels = None depends_on = None @@ -18,13 +18,6 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('admin_session', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('token', sa.String(length=50), nullable=True), - sa.Column('admin_impersonate_uid', sa.String(length=50), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('token') - ) op.create_table('bpmn_process_id_lookup', sa.Column('id', sa.Integer(), nullable=False), sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=True), @@ -183,25 +176,6 @@ def upgrade(): sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique') ) - op.create_table('file', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=50), nullable=False), - sa.Column('type', sa.String(length=50), nullable=False), - sa.Column('content_type', sa.String(length=50), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=True), - sa.Column('task_spec', sa.String(length=50), nullable=True), - sa.Column('irb_doc_code', sa.String(length=50), nullable=False), - sa.Column('md5_hash', sa.String(length=50), nullable=False), - sa.Column('data', sa.LargeBinary(), nullable=True), - sa.Column('size', 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('user_uid', sa.String(length=50), nullable=True), - sa.Column('archived', sa.Boolean(), nullable=True), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.ForeignKeyConstraint(['user_uid'], ['user.uid'], ), - sa.PrimaryKeyConstraint('id') - ) op.create_table('message_correlation', sa.Column('id', sa.Integer(), nullable=False), sa.Column('process_instance_id', sa.Integer(), nullable=False), @@ -259,28 +233,6 @@ def upgrade(): sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), sa.PrimaryKeyConstraint('id') ) - op.create_table('task_event', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('spec_version', sa.String(length=50), nullable=True), - sa.Column('action', sa.String(length=50), 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_state', sa.String(length=50), nullable=True), - sa.Column('task_lane', sa.String(length=50), nullable=True), - sa.Column('form_data', sa.JSON(), nullable=True), - sa.Column('mi_type', sa.String(length=50), nullable=True), - sa.Column('mi_count', sa.Integer(), nullable=True), - sa.Column('mi_index', sa.Integer(), nullable=True), - sa.Column('process_name', sa.String(length=50), nullable=True), - sa.Column('date', sa.DateTime(timezone=True), nullable=True), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) op.create_table('active_task_user', sa.Column('id', sa.Integer(), nullable=False), sa.Column('active_task_id', sa.Integer(), nullable=False), @@ -292,19 +244,6 @@ def upgrade(): ) op.create_index(op.f('ix_active_task_user_active_task_id'), 'active_task_user', ['active_task_id'], unique=False) op.create_index(op.f('ix_active_task_user_user_id'), 'active_task_user', ['user_id'], unique=False) - op.create_table('data_store', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('key', sa.String(length=50), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=True), - sa.Column('task_spec', sa.String(length=50), nullable=True), - sa.Column('spec_id', sa.String(length=50), nullable=True), - sa.Column('user_id', sa.String(length=50), nullable=True), - sa.Column('file_id', sa.Integer(), nullable=True), - sa.Column('value', sa.String(length=50), nullable=True), - sa.ForeignKeyConstraint(['file_id'], ['file.id'], ), - sa.PrimaryKeyConstraint('id') - ) op.create_table('message_correlation_message_instance', sa.Column('id', sa.Integer(), nullable=False), sa.Column('message_instance_id', sa.Integer(), nullable=False), @@ -324,11 +263,9 @@ def downgrade(): op.drop_index(op.f('ix_message_correlation_message_instance_message_instance_id'), table_name='message_correlation_message_instance') op.drop_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), table_name='message_correlation_message_instance') op.drop_table('message_correlation_message_instance') - op.drop_table('data_store') op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user') op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user') op.drop_table('active_task_user') - op.drop_table('task_event') op.drop_table('spiff_logging') op.drop_table('permission_assignment') op.drop_table('message_instance') @@ -337,7 +274,6 @@ def downgrade(): op.drop_index(op.f('ix_message_correlation_name'), table_name='message_correlation') op.drop_index(op.f('ix_message_correlation_message_correlation_property_id'), table_name='message_correlation') op.drop_table('message_correlation') - op.drop_table('file') op.drop_table('active_task') op.drop_table('user_group_assignment') op.drop_table('secret') @@ -363,5 +299,4 @@ def downgrade(): op.drop_table('group') op.drop_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), table_name='bpmn_process_id_lookup') op.drop_table('bpmn_process_id_lookup') - op.drop_table('admin_session') # ### end Alembic commands ### diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 06b2b562..c474c696 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -1873,7 +1873,7 @@ pytz = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "5cdb881edc4621502bfd61ce67565cf1148199f0" +resolved_reference = "a6392d19061f623394f5705fb78af23673d3940d" [[package]] name = "SQLAlchemy" @@ -2621,6 +2621,7 @@ greenlet = [ {file = "greenlet-1.1.3.post0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a954002064ee919b444b19c1185e8cce307a1f20600f47d6f4b6d336972c809"}, {file = "greenlet-1.1.3.post0-cp39-cp39-win32.whl", hash = "sha256:2ccdc818cc106cc238ff7eba0d71b9c77be868fdca31d6c3b1347a54c9b187b2"}, {file = "greenlet-1.1.3.post0-cp39-cp39-win_amd64.whl", hash = "sha256:91a84faf718e6f8b888ca63d0b2d6d185c8e2a198d2a7322d75c303e7097c8b7"}, + {file = "greenlet-1.1.3.post0.tar.gz", hash = "sha256:f5e09dc5c6e1796969fd4b775ea1417d70e49a5df29aaa8e5d10675d9e11872c"}, ] gunicorn = [ {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, @@ -2945,7 +2946,10 @@ orjson = [ {file = "orjson-3.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b68a42a31f8429728183c21fb440c21de1b62e5378d0d73f280e2d894ef8942e"}, {file = "orjson-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ff13410ddbdda5d4197a4a4c09969cb78c722a67550f0a63c02c07aadc624833"}, {file = "orjson-3.8.0-cp310-none-win_amd64.whl", hash = "sha256:2d81e6e56bbea44be0222fb53f7b255b4e7426290516771592738ca01dbd053b"}, + {file = "orjson-3.8.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:200eae21c33f1f8b02a11f5d88d76950cd6fd986d88f1afe497a8ae2627c49aa"}, + {file = "orjson-3.8.0-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9529990f3eab54b976d327360aa1ff244a4b12cb5e4c5b3712fcdd96e8fe56d4"}, {file = "orjson-3.8.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e2defd9527651ad39ec20ae03c812adf47ef7662bdd6bc07dabb10888d70dc62"}, + {file = "orjson-3.8.0-cp311-none-win_amd64.whl", hash = "sha256:b21c7af0ff6228ca7105f54f0800636eb49201133e15ddb80ac20c1ce973ef07"}, {file = "orjson-3.8.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9e6ac22cec72d5b39035b566e4b86c74b84866f12b5b0b6541506a080fb67d6d"}, {file = "orjson-3.8.0-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e2f4a5542f50e3d336a18cb224fc757245ca66b1fd0b70b5dd4471b8ff5f2b0e"}, {file = "orjson-3.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1418feeb8b698b9224b1f024555895169d481604d5d884498c1838d7412794c"}, @@ -3058,18 +3062,7 @@ py = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] pycodestyle = [ diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/load_database_models.py b/spiffworkflow-backend/src/spiffworkflow_backend/load_database_models.py index 12b33a7e..7283b19b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/load_database_models.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/load_database_models.py @@ -21,8 +21,6 @@ from spiffworkflow_backend.models.active_task import ActiveTaskModel # noqa: F4 from spiffworkflow_backend.models.bpmn_process_id_lookup import ( BpmnProcessIdLookup, ) # 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.message_correlation_property import ( MessageCorrelationPropertyModel, ) # noqa: F401 @@ -48,7 +46,6 @@ from spiffworkflow_backend.models.process_instance_report import ( from spiffworkflow_backend.models.refresh_token import RefreshTokenModel # noqa: F401 from spiffworkflow_backend.models.secret_model import SecretModel # noqa: F401 from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel # noqa: F401 -from spiffworkflow_backend.models.task_event import TaskEventModel # noqa: F401 from spiffworkflow_backend.models.user import UserModel # noqa: F401 from spiffworkflow_backend.models.group import GroupModel # noqa: F401 diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/data_store.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/data_store.py deleted file mode 100644 index abbfcf34..00000000 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/data_store.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Data_store.""" -from flask_bpmn.models.db import db -from flask_bpmn.models.db import SpiffworkflowBaseDBModel -from flask_marshmallow.sqla import SQLAlchemyAutoSchema # type: ignore - - -class DataStoreModel(SpiffworkflowBaseDBModel): - """DataStoreModel.""" - - __tablename__ = "data_store" - id = db.Column(db.Integer, primary_key=True) - updated_at_in_seconds = db.Column(db.Integer) - key = db.Column(db.String(50), nullable=False) - process_instance_id = db.Column(db.Integer) - task_spec = db.Column(db.String(50)) - spec_id = db.Column(db.String(50)) - user_id = db.Column(db.String(50), nullable=True) - file_id = db.Column(db.Integer, db.ForeignKey("file.id"), nullable=True) - value = db.Column(db.String(50)) - - -class DataStoreSchema(SQLAlchemyAutoSchema): # type: ignore - """DataStoreSchema.""" - - class Meta: - """Meta.""" - - model = DataStoreModel - load_instance = True - include_fk = True - sqla_session = db.session diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py index eb49b873..0c844766 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py @@ -4,40 +4,10 @@ from dataclasses import field from datetime import datetime from typing import Optional -from flask_bpmn.models.db import db -from flask_bpmn.models.db import SpiffworkflowBaseDBModel from marshmallow import INCLUDE from marshmallow import Schema -from sqlalchemy.orm import deferred -from sqlalchemy.orm import relationship from spiffworkflow_backend.helpers.spiff_enum import SpiffEnum -from spiffworkflow_backend.models.data_store import DataStoreModel - - -class FileModel(SpiffworkflowBaseDBModel): - """FileModel.""" - - __tablename__ = "file" - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - type = db.Column(db.String(50), nullable=False) - content_type = db.Column(db.String(50), nullable=False) - process_instance_id = db.Column( - db.Integer, db.ForeignKey("process_instance.id"), nullable=True - ) - task_spec = db.Column(db.String(50), nullable=True) - irb_doc_code = db.Column( - db.String(50), nullable=False - ) # Code reference to the documents.xlsx reference file. - data_stores = relationship(DataStoreModel, cascade="all,delete", backref="file") - md5_hash = db.Column(db.String(50), unique=False, nullable=False) - data = deferred(db.Column(db.LargeBinary)) # type: ignore - size = db.Column(db.Integer, default=0) - updated_at_in_seconds = db.Column(db.Integer) - created_at_in_seconds = db.Column(db.Integer) - user_uid = db.Column(db.String(50), db.ForeignKey("user.uid"), nullable=True) - archived = db.Column(db.Boolean, default=False) class FileType(SpiffEnum): diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index d1d117c8..1c2098e9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -78,7 +78,6 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): process_initiator = relationship("UserModel") active_tasks = relationship("ActiveTaskModel", cascade="delete") # type: ignore - task_events = relationship("TaskEventModel", cascade="delete") # type: ignore spiff_logs = relationship("SpiffLoggingModel", cascade="delete") # type: ignore message_instances = relationship("MessageInstanceModel", cascade="delete") # type: ignore message_correlations = relationship("MessageCorrelationModel", cascade="delete") # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task_event.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task_event.py deleted file mode 100644 index 5bb668b4..00000000 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task_event.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Task_event.""" -from __future__ import annotations - -import enum -from typing import TYPE_CHECKING - -from flask_bpmn.models.db import db -from flask_bpmn.models.db import SpiffworkflowBaseDBModel -from marshmallow import fields -from marshmallow import INCLUDE -from marshmallow import Schema -from sqlalchemy import func - - -if TYPE_CHECKING: - from spiffworkflow_backend.models.process_instance import ( - ProcessInstanceModel, - ) # noqa: F401 - - -class TaskAction(enum.Enum): - """TaskAction.""" - - COMPLETE = "COMPLETE" - TOKEN_RESET = "TOKEN_RESET" # noqa: S105 - HARD_RESET = "HARD_RESET" - SOFT_RESET = "SOFT_RESET" - ASSIGNMENT = "ASSIGNMENT" # Whenever the lane changes between tasks we assign the task to specific user. - - -class TaskEventModel(SpiffworkflowBaseDBModel): - """TaskEventModel.""" - - __tablename__ = "task_event" - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column( - db.Integer, db.ForeignKey("user.id"), nullable=False - ) # In some cases the unique user id may not exist in the db yet. - process_instance_id = db.Column( - db.Integer, db.ForeignKey("process_instance.id"), nullable=False - ) - spec_version = db.Column(db.String(50)) - action = db.Column(db.String(50)) - 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_state = db.Column(db.String(50)) - task_lane = db.Column(db.String(50)) - form_data = db.Column( - db.JSON - ) # And form data submitted when the task was completed. - mi_type = db.Column(db.String(50)) - mi_count = db.Column(db.Integer) - mi_index = db.Column(db.Integer) - process_name = db.Column(db.String(50)) - date = db.Column(db.DateTime(timezone=True), default=func.now()) - - -class TaskEvent: - """TaskEvent.""" - - def __init__(self, model: TaskEventModel, process_instance: ProcessInstanceModel): - """__init__.""" - self.id = model.id - self.process_instance = process_instance - self.user_id = model.user_id - self.action = model.action - self.task_id = model.task_id - self.task_title = model.task_title - self.task_name = model.task_name - self.task_type = model.task_type - self.task_state = model.task_state - self.task_lane = model.task_lane - self.date = model.date - - -class TaskEventSchema(Schema): - """TaskEventSchema.""" - - process_instance = fields.Nested("ProcessInstanceMetadataSchema", dump_only=True) - task_lane = fields.String(allow_none=True, required=False) - - class Meta: - """Meta.""" - - model = TaskEvent - additional = [ - "id", - "user_id", - "action", - "task_id", - "task_title", - "task_name", - "task_type", - "task_state", - "task_lane", - "date", - ] - unknown = INCLUDE diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py index 22cdfb69..c33a72e7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py @@ -112,12 +112,3 @@ class UserModelSchema(Schema): id = marshmallow.fields.String(required=True) username = marshmallow.fields.String(required=True) - - -class AdminSessionModel(SpiffworkflowBaseDBModel): - """AdminSessionModel.""" - - __tablename__ = "admin_session" - id = db.Column(db.Integer, primary_key=True) - token = db.Column(db.String(50), unique=True) - admin_impersonate_uid = db.Column(db.String(50)) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index f0a93fd9..0523ad25 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -424,7 +424,6 @@ def process_instance_run( task=task, ) from e processor.save() - ProcessInstanceService.update_task_assignments(processor) if not current_app.config["RUN_BACKGROUND_SCHEDULER"]: MessageService.process_message_instances() @@ -1123,8 +1122,6 @@ def task_submit( # last_index = next_task.task_info()["mi_index"] # next_task = processor.next_task() - ProcessInstanceService.update_task_assignments(processor) - next_active_task_assigned_to_me = ( ActiveTaskModel.query.filter_by(process_instance_id=process_instance_id) .order_by(asc(ActiveTaskModel.id)) # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index b499081f..fecde1b9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -79,8 +79,6 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) -from spiffworkflow_backend.models.task_event import TaskAction -from spiffworkflow_backend.models.task_event import TaskEventModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModelSchema from spiffworkflow_backend.scripts.script import Script @@ -419,7 +417,7 @@ class ProcessInstanceProcessor: """Add_user_info_to_process_instance.""" current_user = None if UserService.has_user(): - current_user = UserService.current_user(allow_admin_impersonate=True) + current_user = UserService.current_user() # fall back to initiator if g.user is not set # this is for background processes when there will not be a user @@ -433,59 +431,6 @@ class ProcessInstanceProcessor: for task in tasks: task.data["current_user"] = current_user_data - @staticmethod - def reset( - process_instance_model: ProcessInstanceModel, clear_data: bool = False - ) -> None: - """Resets the process_instance back to an unstarted state - where nothing has happened yet. - - If clear_data is set to false, then the information - previously used in forms will be re-populated when the form is re- - displayed, and any files that were updated will remain in place, otherwise - files will also be cleared out. - """ - # Try to execute a cancel notify - try: - bpmn_process_instance = ( - ProcessInstanceProcessor.__get_bpmn_process_instance( - process_instance_model - ) - ) - ProcessInstanceProcessor.__cancel_notify(bpmn_process_instance) - except Exception as e: - db.session.rollback() # in case the above left the database with a bad transaction - current_app.logger.error( - "Unable to send a cancel notify for process_instance %s during a reset." - " Continuing with the reset anyway so we don't get in an unresolvable" - " state. An %s error occured with the following information: %s" - % (process_instance_model.id, e.__class__.__name__, str(e)) - ) - process_instance_model.bpmn_json = None - process_instance_model.status = ProcessInstanceStatus.not_started.value - - # clear out any task assignments - db.session.query(TaskEventModel).filter( - TaskEventModel.process_instance_id == process_instance_model.id - ).filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).delete() - - if clear_data: - # Clear out data in previous task events - task_events = ( - db.session.query(TaskEventModel) - .filter(TaskEventModel.process_instance_id == process_instance_model.id) - .all() - ) - for task_event in task_events: - task_event.form_data = {} - db.session.add(task_event) - # Remove any uploaded files. - - # TODO: grab UserFileService - # files = FileModel.query.filter(FileModel.process_instance_id == process_instance_model.id).all() - # for file in files: - # UserFileService().delete_file(file.id) - db.session.commit() - @staticmethod def get_bpmn_process_instance_from_workflow_spec( spec: BpmnProcessSpec, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index f3d080a8..062d0ef7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -1,7 +1,6 @@ """Process_instance_service.""" import time from typing import Any -from typing import Dict from typing import List from typing import Optional @@ -9,15 +8,12 @@ from flask import current_app from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db from SpiffWorkflow.task import Task as SpiffTask # type: ignore -from SpiffWorkflow.util.deep_merge import DeepMerge # type: ignore from spiffworkflow_backend.models.process_instance import ProcessInstanceApi from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus from spiffworkflow_backend.models.task import MultiInstanceType from spiffworkflow_backend.models.task import Task -from spiffworkflow_backend.models.task_event import TaskAction -from spiffworkflow_backend.models.task_event import TaskEventModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.git_service import GitService @@ -108,70 +104,9 @@ class ProcessInstanceService: is_review=is_review_value, title=title_value, ) - next_task_trying_again = next_task - if ( - not next_task - ): # The Next Task can be requested to be a certain task, useful for parallel tasks. - # This may or may not work, sometimes there is no next task to complete. - next_task_trying_again = processor.next_task() - - if next_task_trying_again is not None: - previous_form_data = ProcessInstanceService.get_previously_submitted_data( - processor.process_instance_model.id, next_task_trying_again - ) - # DeepMerge.merge(next_task_trying_again.data, previous_form_data) - next_task_trying_again.data = DeepMerge.merge( - previous_form_data, next_task_trying_again.data - ) - - process_instance_api.next_task = ( - ProcessInstanceService.spiff_task_to_api_task( - next_task_trying_again, add_docs_and_forms=True - ) - ) - # TODO: Hack for now, until we decide how to implment forms - process_instance_api.next_task.form = None - - # Update the state of the task to locked if the current user does not own the task. - # user_uids = WorkflowService.get_users_assigned_to_task(processor, next_task) - # if not UserService.in_list(user_uids, allow_admin_impersonate=True): - # workflow_api.next_task.state = WorkflowService.TASK_STATE_LOCKED return process_instance_api - @staticmethod - def get_previously_submitted_data( - process_instance_id: int, spiff_task: SpiffTask - ) -> Dict[Any, Any]: - """If the user has completed this task previously, find the form data for the last submission.""" - query = ( - db.session.query(TaskEventModel) - .filter_by(process_instance_id=process_instance_id) - .filter_by(task_name=spiff_task.task_spec.name) - .filter_by(action=TaskAction.COMPLETE.value) - ) - - if ( - hasattr(spiff_task, "internal_data") - and "runtimes" in spiff_task.internal_data - ): - query = query.filter_by(mi_index=spiff_task.internal_data["runtimes"]) - - latest_event = query.order_by(TaskEventModel.date.desc()).first() - if latest_event: - if latest_event.form_data is not None: - return latest_event.form_data # type: ignore - else: - missing_form_error = ( - f"We have lost data for workflow {process_instance_id}, " - f"task {spiff_task.task_spec.name}, it is not in the task event model, " - f"and it should be." - ) - current_app.logger.exception("missing_form_data", missing_form_error) - return {} - else: - return {} - def get_process_instance(self, process_instance_id: int) -> Any: """Get_process_instance.""" result = ( @@ -181,30 +116,6 @@ class ProcessInstanceService: ) return result - @staticmethod - def update_task_assignments(processor: ProcessInstanceProcessor) -> None: - """For every upcoming user task, log a task action that connects the assigned user(s) to that task. - - All existing assignment actions for this workflow are removed from the database, - so that only the current valid actions are available. update_task_assignments - should be called whenever progress is made on a workflow. - """ - db.session.query(TaskEventModel).filter( - TaskEventModel.process_instance_id == processor.process_instance_model.id - ).filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).delete() - db.session.commit() - - tasks = processor.get_current_user_tasks() - for task in tasks: - user_ids = ProcessInstanceService.get_users_assigned_to_task( - processor, task - ) - - for user_id in user_ids: - ProcessInstanceService().log_task_action( - user_id, processor, task, TaskAction.ASSIGNMENT.value - ) - @staticmethod def get_users_assigned_to_task( processor: ProcessInstanceProcessor, spiff_task: SpiffTask @@ -279,52 +190,8 @@ class ProcessInstanceService: spiff_task.update_data(dot_dct) # ProcessInstanceService.post_process_form(spiff_task) # some properties may update the data store. processor.complete_task(spiff_task) - # Log the action before doing the engine steps, as doing so could effect the state of the task - # the workflow could wrap around in the ngine steps, and the task could jump from being completed to - # another state. What we are logging here is the completion. - ProcessInstanceService.log_task_action( - user.id, processor, spiff_task, TaskAction.COMPLETE.value - ) processor.do_engine_steps(save=True) - @staticmethod - def log_task_action( - user_id: int, - processor: ProcessInstanceProcessor, - spiff_task: SpiffTask, - action: str, - ) -> None: - """Log_task_action.""" - task = ProcessInstanceService.spiff_task_to_api_task(spiff_task) - 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, - process_instance_id=processor.process_instance_model.id, - # workflow_spec_id=processor.workflow_model.workflow_spec_id, - action=action, - task_id=str(task.id), - task_name=task.name, - task_title=task.title, - task_type=str(task.type), - task_state=task.state, - task_lane=task.lane, - form_data=form_data, - 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, - # date=datetime.utcnow(), <=== For future reference, NEVER do this. Let the database set the time. - ) - db.session.add(task_event) - db.session.commit() - @staticmethod def extract_form_data(latest_data: dict, task: SpiffTask) -> dict: """Extracts data from the latest_data that is directly related to the form that is being submitted.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index d4749e01..0e8e65c2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -11,7 +11,6 @@ from spiffworkflow_backend.models.active_task import ActiveTaskModel from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.principal import PrincipalModel -from spiffworkflow_backend.models.user import AdminSessionModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel @@ -103,27 +102,6 @@ class UserService: """Has_user.""" return "token" in g and bool(g.token) and "user" in g and bool(g.user) - # Returns true if the current user is an admin. - @staticmethod - def user_is_admin() -> bool: - """User_is_admin.""" - return UserService.has_user() and g.user.is_admin() - - # Returns true if the current admin user is impersonating another user. - @staticmethod - def admin_is_impersonating() -> bool: - """Admin_is_impersonating.""" - if UserService.user_is_admin(): - admin_session = UserService.get_admin_session() - return admin_session is not None - - else: - raise ApiError( - "unauthorized", - "You do not have permissions to do this.", - status_code=403, - ) - # Returns true if the given user uid is different from the current user's uid. @staticmethod def is_different_user(uid: str) -> bool: @@ -131,84 +109,16 @@ class UserService: return UserService.has_user() and uid is not None and uid is not g.user.uid @staticmethod - def current_user(allow_admin_impersonate: bool = False) -> Any: + def current_user() -> Any: """Current_user.""" if not UserService.has_user(): raise ApiError( "logged_out", "You are no longer logged in.", status_code=401 ) - - # Admins can pretend to be different users and act on a user's behalf in - # some circumstances. - if ( - UserService.user_is_admin() - and allow_admin_impersonate - and UserService.admin_is_impersonating() - ): - return UserService.get_admin_session_user() - else: - return g.user - - # Admins can pretend to be different users and act on a user's behalf in some circumstances. - # This method allows an admin user to start impersonating another user with the given uid. - # Stops impersonating if the uid is None or invalid. - @staticmethod - def start_impersonating(uid: Optional[str] = None) -> None: - """Start_impersonating.""" - if not UserService.has_user(): - raise ApiError( - "logged_out", "You are no longer logged in.", status_code=401 - ) - - if not UserService.user_is_admin(): - raise ApiError( - "unauthorized", - "You do not have permissions to do this.", - status_code=403, - ) - - if uid is None: - raise ApiError("invalid_uid", "Please provide a valid user uid.") - - if UserService.is_different_user(uid): - # Impersonate the user if the given uid is valid. - impersonate_user = ( - db.session.query(UserModel).filter(UserModel.uid == uid).first() - ) - - if impersonate_user is not None: - g.impersonate_user = impersonate_user - - # Store the uid and user session token. - db.session.query(AdminSessionModel).filter( - AdminSessionModel.token == g.token - ).delete() - db.session.add( - AdminSessionModel(token=g.token, admin_impersonate_uid=uid) - ) - db.session.commit() - else: - raise ApiError("invalid_uid", "The uid provided is not valid.") + return g.user @staticmethod - def stop_impersonating() -> None: - """Stop_impersonating.""" - if not UserService.has_user(): - raise ApiError( - "logged_out", "You are no longer logged in.", status_code=401 - ) - - # Clear out the current impersonating user. - if "impersonate_user" in g: - del g.impersonate_user - - admin_session = UserService.get_admin_session() - if admin_session: - db.session.delete(admin_session) - db.session.commit() - - @staticmethod - def in_list(uids: list[str], allow_admin_impersonate: bool = False) -> bool: + def in_list(uids: list[str]) -> bool: """Returns true if the current user's id is in the given list of ids. False if there is no user, or the user is not in the list. @@ -216,46 +126,11 @@ class UserService: if ( UserService.has_user() ): # If someone is logged in, lock tasks that don't belong to them. - user = UserService.current_user(allow_admin_impersonate) + user = UserService.current_user() if user.uid in uids: return True return False - @staticmethod - def get_admin_session() -> Any: - """Get_admin_session.""" - if UserService.user_is_admin(): - return ( - db.session.query(AdminSessionModel) - .filter(AdminSessionModel.token == g.token) - .first() - ) - else: - raise ApiError( - "unauthorized", - "You do not have permissions to do this.", - status_code=403, - ) - - @staticmethod - def get_admin_session_user() -> Any: - """Get_admin_session_user.""" - if UserService.user_is_admin(): - admin_session = UserService.get_admin_session() - - if admin_session is not None: - return ( - db.session.query(UserModel) - .filter(UserModel.uid == admin_session.admin_impersonate_uid) - .first() - ) - else: - raise ApiError( - "unauthorized", - "You do not have permissions to do this.", - status_code=403, - ) - @staticmethod def get_principal_by_user_id(user_id: int) -> PrincipalModel: """Get_principal_by_user_id.""" diff --git a/spiffworkflow-backend/tests/data/get_group_members/get_group_members.bpmn b/spiffworkflow-backend/tests/data/get_group_members/get_group_members.bpmn index 23f6df07..b6e849ba 100644 --- a/spiffworkflow-backend/tests/data/get_group_members/get_group_members.bpmn +++ b/spiffworkflow-backend/tests/data/get_group_members/get_group_members.bpmn @@ -11,13 +11,13 @@ Flow_1j4jzft Flow_10xyk22 - members_a = get_group_members("GroupA") + members_a = get_group_members("groupA") Flow_10xyk22 Flow_01xr2ac - members_b = get_group_members("GroupB") + members_b = get_group_members("groupB") diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index b7d2c5d7..a573e8a3 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -275,25 +275,6 @@ class BaseTest: user: UserModel, _redirect_url: str = "http://some/frontend/url" ) -> Dict[str, str]: """Logged_in_headers.""" - # if user is None: - # uid = 'test_user' - # user_info = {'uid': 'test_user'} - # else: - # uid = user.uid - # user_info = {'uid': user.uid} - - # query_string = user_info_to_query_string(user_info, redirect_url) - # rv = self.app.get("/v1.0/login%s" % query_string, follow_redirects=False) - # self.assertTrue(rv.status_code == 302) - # self.assertTrue(str.startswith(rv.location, redirect_url)) - # - # user_model = session.query(UserModel).filter_by(uid=uid).first() - # self.assertIsNotNone(user_model.ldap_info.display_name) - # self.assertEqual(user_model.uid, uid) - # self.assertTrue('user' in g, 'User should be in Flask globals') - # user = UserService.current_user(allow_admin_impersonate=True) - # self.assertEqual(uid, user.uid, 'Logged in user should match given user uid') - return dict(Authorization="Bearer " + user.encode_auth_token()) def get_test_data_file_contents( diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index 2d79b652..9a923b97 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -25,7 +25,6 @@ from spiffworkflow_backend.models.process_instance_report import ( ) from spiffworkflow_backend.models.process_model import NotificationType from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema -from spiffworkflow_backend.models.task_event import TaskEventModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.file_system_service import FileSystemService @@ -1088,16 +1087,7 @@ class TestProcessApi(BaseTest): f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run", headers=self.logged_in_headers(with_super_admin_user), ) - assert response.json is not None - task_events = ( - db.session.query(TaskEventModel) - .filter(TaskEventModel.process_instance_id == process_instance_id) - .all() - ) - assert len(task_events) == 1 - task_event = task_events[0] - assert task_event.user_id == with_super_admin_user.id delete_response = client.delete( f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}", @@ -1105,40 +1095,6 @@ class TestProcessApi(BaseTest): ) assert delete_response.status_code == 200 - def test_process_instance_run_user_task_creates_task_event( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, - ) -> None: - """Test_process_instance_run_user_task.""" - process_group_id = "my_process_group" - process_model_id = "user_task" - - headers = self.logged_in_headers(with_super_admin_user) - response = self.create_process_instance( - client, process_group_id, process_model_id, headers - ) - assert response.json is not None - process_instance_id = response.json["id"] - - response = client.post( - f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run", - headers=self.logged_in_headers(with_super_admin_user), - ) - - assert response.json is not None - task_events = ( - db.session.query(TaskEventModel) - .filter(TaskEventModel.process_instance_id == process_instance_id) - .all() - ) - assert len(task_events) == 1 - task_event = task_events[0] - assert task_event.user_id == with_super_admin_user.id - # TODO: When user tasks work, we need to add some more assertions for action, task_state, etc. - def test_task_show( self, app: Flask,