Merge branch 'main' into feature/git-integration
# Conflicts: # spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
This commit is contained in:
commit
f580cadb21
|
@ -1,5 +1,5 @@
|
|||
pip==22.2.2
|
||||
nox==2022.8.7
|
||||
nox-poetry==1.0.1
|
||||
nox==2022.11.21
|
||||
nox-poetry==1.0.2
|
||||
poetry==1.2.2
|
||||
virtualenv==20.16.5
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: ff1c1628337c
|
||||
Revision ID: 40a2ed63cc5a
|
||||
Revises:
|
||||
Create Date: 2022-11-28 15:08:52.014254
|
||||
Create Date: 2022-11-29 16:59:02.980181
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ff1c1628337c'
|
||||
revision = '40a2ed63cc5a'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -249,6 +249,7 @@ def upgrade():
|
|||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
||||
)
|
||||
op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
|
@ -295,6 +296,7 @@ def downgrade():
|
|||
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('spiff_step_details')
|
||||
op.drop_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata')
|
||||
op.drop_table('process_instance_metadata')
|
||||
op.drop_table('permission_assignment')
|
||||
op.drop_table('message_instance')
|
|
@ -1851,7 +1851,7 @@ lxml = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/sartography/SpiffWorkflow"
|
||||
reference = "main"
|
||||
resolved_reference = "062eaf15d28c66f8cf07f68409429560251b12c7"
|
||||
resolved_reference = "ffb1686757f944065580dd2db8def73d6c1f0134"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
|
@ -2989,7 +2989,18 @@ psycopg2 = [
|
|||
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
||||
]
|
||||
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 = [
|
||||
|
|
|
@ -338,9 +338,9 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/ProcessModel"
|
||||
|
||||
/process-models/{modified_process_model_id}/files:
|
||||
/process-models/{modified_process_model_identifier}/files:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The process_model_id, modified to replace slashes (/)
|
||||
|
@ -570,6 +570,12 @@ paths:
|
|||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: string
|
||||
- name: report_id
|
||||
in: query
|
||||
required: false
|
||||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
|
||||
summary: Returns a list of process instances for a given process model
|
||||
|
@ -585,33 +591,6 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/task/{task_id}/update:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the process instance
|
||||
schema:
|
||||
type: string
|
||||
- name: task_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the task
|
||||
schema:
|
||||
type: string
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_task_data
|
||||
summary: Update the task data for requested instance and task
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Task Updated Successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-models/{process_group_id}/{process_model_id}/script-unit-tests:
|
||||
parameters:
|
||||
- name: process_group_id
|
||||
|
@ -666,15 +645,14 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-models/{modified_process_model_id}/process-instances:
|
||||
/process-instances/{modified_process_model_identifier}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process model.
|
||||
schema:
|
||||
type: string
|
||||
# process_instance_create
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_create
|
||||
summary: Creates an process instance from a process model and returns the instance
|
||||
|
@ -688,28 +666,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
delete:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
|
||||
summary: Deletes a single process instance
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: The process instance was deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-models/{modified_process_model_identifier}/process-instances/{process_instance_id}:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
|
@ -735,6 +692,18 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
delete:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
|
||||
summary: Deletes a single process instance
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: The process instance was deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/run:
|
||||
parameters:
|
||||
|
@ -763,7 +732,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/terminate:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/terminate:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -784,7 +753,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{process_instance_id}/suspend:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/suspend:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -805,7 +774,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{process_instance_id}/resume:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/resume:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -867,14 +836,30 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/reports/{report_identifier}:
|
||||
/process-instances/reports/columns:
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_column_list
|
||||
summary: Returns all available columns for a process instance report.
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Workflow.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/reports/{report_id}:
|
||||
parameters:
|
||||
- name: report_identifier
|
||||
- name: report_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing report
|
||||
schema:
|
||||
type: string
|
||||
type: integer
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
|
@ -926,9 +911,9 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-models/{modified_process_model_id}/files/{file_name}:
|
||||
/process-models/{modified_process_model_identifier}/files/{file_name}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified process model id
|
||||
|
@ -1105,9 +1090,9 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/process-instances/{modified_process_model_id}/{process_instance_id}/tasks:
|
||||
/task-data/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified id of an existing process model
|
||||
|
@ -1146,11 +1131,44 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/service_tasks:
|
||||
/task-data/{modified_process_model_identifier}/{process_instance_id}/{task_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified id of an existing process model
|
||||
schema:
|
||||
type: string
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
- name: task_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the task.
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_task_data
|
||||
summary: Update the task data for requested instance and task
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Task Updated Successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/service-tasks:
|
||||
get:
|
||||
tags:
|
||||
- Service Tasks
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.service_tasks_show
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.service_task_list
|
||||
summary: Gets all available service task connectors
|
||||
responses:
|
||||
"200":
|
||||
|
@ -1330,7 +1348,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/logs:
|
||||
/logs/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -1350,6 +1368,12 @@ paths:
|
|||
description: The number of items to show per page. Defaults to page 10.
|
||||
schema:
|
||||
type: integer
|
||||
- name: detailed
|
||||
in: query
|
||||
required: false
|
||||
description: Show the detailed view, which includes all log entries
|
||||
schema:
|
||||
type: boolean
|
||||
get:
|
||||
tags:
|
||||
- Process Instances
|
||||
|
|
|
@ -12,7 +12,6 @@ groups:
|
|||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
|
@ -70,6 +69,12 @@ permissions:
|
|||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
service-tasks:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/service-tasks
|
||||
|
||||
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
|
@ -98,6 +103,12 @@ permissions:
|
|||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
task-data-read:
|
||||
groups: [demo]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/task-data/*
|
||||
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
|
@ -170,17 +181,17 @@ permissions:
|
|||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
||||
core1-admin-models-instantiate:
|
||||
groups: ["core-contributor"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/misc:category_number_one:process-model-with-form/process-instances
|
||||
core1-admin-instances:
|
||||
groups: ["core-contributor"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form:*
|
||||
core1-admin-instances-slash:
|
||||
groups: ["core-contributor"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/*
|
||||
|
|
|
@ -12,7 +12,6 @@ groups:
|
|||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
|
@ -98,6 +97,12 @@ permissions:
|
|||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
task-data-read:
|
||||
groups: [demo]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/task-data/*
|
||||
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
|
|
|
@ -23,7 +23,7 @@ class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
|||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
key: str = db.Column(db.String(255), nullable=False)
|
||||
key: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
value: str = db.Column(db.String(255), nullable=False)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
||||
|
|
|
@ -26,6 +26,10 @@ from spiffworkflow_backend.services.process_instance_processor import (
|
|||
ReportMetadata = dict[str, Any]
|
||||
|
||||
|
||||
class ProcessInstanceReportAlreadyExistsError(Exception):
|
||||
"""ProcessInstanceReportAlreadyExistsError."""
|
||||
|
||||
|
||||
class ProcessInstanceReportResult(TypedDict):
|
||||
"""ProcessInstanceReportResult."""
|
||||
|
||||
|
@ -63,7 +67,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
),
|
||||
)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
report_metadata: dict = deferred(db.Column(db.JSON)) # type: ignore
|
||||
created_by_id = db.Column(ForeignKey(UserModel.id), nullable=False, index=True)
|
||||
|
@ -71,6 +75,11 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
created_at_in_seconds = db.Column(db.Integer)
|
||||
updated_at_in_seconds = db.Column(db.Integer)
|
||||
|
||||
@classmethod
|
||||
def default_order_by(cls) -> list[str]:
|
||||
"""Default_order_by."""
|
||||
return ["-start_in_seconds", "-id"]
|
||||
|
||||
@classmethod
|
||||
def add_fixtures(cls) -> None:
|
||||
"""Add_fixtures."""
|
||||
|
@ -120,14 +129,18 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
identifier: str,
|
||||
user: UserModel,
|
||||
report_metadata: ReportMetadata,
|
||||
) -> None:
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Make_fixture_report."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
).first()
|
||||
|
||||
if process_instance_report is None:
|
||||
if process_instance_report is not None:
|
||||
raise ProcessInstanceReportAlreadyExistsError(
|
||||
f"Process instance report with identifier already exists: {identifier}"
|
||||
)
|
||||
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
|
@ -136,6 +149,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
db.session.add(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
@classmethod
|
||||
def ticket_for_month_report(cls) -> dict:
|
||||
"""Ticket_for_month_report."""
|
||||
|
@ -204,18 +219,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
user: UserModel,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Create_with_attributes."""
|
||||
# <<<<<<< HEAD
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id=f"{process_model_identifier}"
|
||||
# )
|
||||
# process_instance_report = cls(
|
||||
# identifier=identifier,
|
||||
# process_group_identifier="process_model.process_group_id",
|
||||
# process_model_identifier=process_model.id,
|
||||
# =======
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
# >>>>>>> main
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
)
|
||||
|
|
|
@ -38,6 +38,7 @@ class ProcessModelInfo:
|
|||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||
parent_groups: list[dict] | None = None
|
||||
metadata_extraction_paths: list[dict[str, str]] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -76,6 +77,13 @@ class ProcessModelInfoSchema(Schema):
|
|||
exception_notification_addresses = marshmallow.fields.List(
|
||||
marshmallow.fields.String
|
||||
)
|
||||
metadata_extraction_paths = marshmallow.fields.List(
|
||||
marshmallow.fields.Dict(
|
||||
keys=marshmallow.fields.Str(required=False),
|
||||
values=marshmallow.fields.Str(required=False),
|
||||
required=False,
|
||||
)
|
||||
)
|
||||
|
||||
@post_load
|
||||
def make_spec(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""APIs for dealing with process groups, process models, and process instances."""
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
@ -30,6 +31,8 @@ from SpiffWorkflow.task import TaskState
|
|||
from sqlalchemy import and_
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
|
@ -52,6 +55,9 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSche
|
|||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -152,9 +158,9 @@ def modify_process_model_id(process_model_id: str) -> str:
|
|||
return process_model_id.replace("/", ":")
|
||||
|
||||
|
||||
def un_modify_modified_process_model_id(modified_process_model_id: str) -> str:
|
||||
def un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str:
|
||||
"""Un_modify_modified_process_model_id."""
|
||||
return modified_process_model_id.replace(":", "/")
|
||||
return modified_process_model_identifier.replace(":", "/")
|
||||
|
||||
|
||||
def process_group_add(body: dict) -> flask.wrappers.Response:
|
||||
|
@ -256,19 +262,26 @@ def process_model_create(
|
|||
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_create."""
|
||||
process_model_info = ProcessModelInfoSchema().load(body)
|
||||
body_include_list = [
|
||||
"id",
|
||||
"display_name",
|
||||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
for include_item in body_include_list
|
||||
if include_item in body
|
||||
}
|
||||
|
||||
if modified_process_group_id is None:
|
||||
raise ApiError(
|
||||
error_code="process_group_id_not_specified",
|
||||
message="Process Model could not be created when process_group_id path param is unspecified",
|
||||
status_code=400,
|
||||
)
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_id
|
||||
|
@ -281,6 +294,14 @@ def process_model_create(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
process_model_info = ProcessModelInfo(**body_filtered) # type: ignore
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
ProcessModelService.add_process_model(process_model_info)
|
||||
return Response(
|
||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||
|
@ -294,7 +315,6 @@ def process_model_delete(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Process_model_delete."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
ProcessModelService().process_model_delete(process_model_identifier)
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
@ -309,6 +329,7 @@ def process_model_update(
|
|||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
|
@ -316,7 +337,6 @@ def process_model_update(
|
|||
if include_item in body
|
||||
}
|
||||
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(process_model, body_filtered)
|
||||
return ProcessModelInfoSchema().dump(process_model)
|
||||
|
@ -325,10 +345,7 @@ def process_model_update(
|
|||
def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||
"""Process_model_show."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
# TODO: Temporary. Should not need the next line once models have correct ids
|
||||
# process_model.id = process_model_identifier
|
||||
files = sorted(SpecFileService.get_files(process_model))
|
||||
process_model.files = files
|
||||
for file in process_model.files:
|
||||
|
@ -405,9 +422,9 @@ def process_list() -> Any:
|
|||
return SpecReferenceSchema(many=True).dump(references)
|
||||
|
||||
|
||||
def get_file(modified_process_model_id: str, file_name: str) -> Any:
|
||||
def get_file(modified_process_model_identifier: str, file_name: str) -> Any:
|
||||
"""Get_file."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
files = SpecFileService.get_files(process_model, file_name)
|
||||
if len(files) == 0:
|
||||
|
@ -427,11 +444,10 @@ def get_file(modified_process_model_id: str, file_name: str) -> Any:
|
|||
|
||||
|
||||
def process_model_file_update(
|
||||
modified_process_model_id: str, file_name: str
|
||||
modified_process_model_identifier: str, file_name: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_file_update."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
|
||||
request_file = get_file_from_request()
|
||||
|
@ -457,10 +473,10 @@ def process_model_file_update(
|
|||
|
||||
|
||||
def process_model_file_delete(
|
||||
modified_process_model_id: str, file_name: str
|
||||
modified_process_model_identifier: str, file_name: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_file_delete."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
try:
|
||||
SpecFileService.delete_file(process_model, file_name)
|
||||
|
@ -476,9 +492,9 @@ def process_model_file_delete(
|
|||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
def add_file(modified_process_model_id: str) -> flask.wrappers.Response:
|
||||
def add_file(modified_process_model_identifier: str) -> flask.wrappers.Response:
|
||||
"""Add_file."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
request_file = get_file_from_request()
|
||||
if not request_file.filename:
|
||||
|
@ -499,10 +515,12 @@ def add_file(modified_process_model_id: str) -> flask.wrappers.Response:
|
|||
)
|
||||
|
||||
|
||||
def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Response:
|
||||
def process_instance_create(
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Create_process_instance."""
|
||||
process_model_identifier = un_modify_modified_process_model_id(
|
||||
modified_process_model_id
|
||||
modified_process_model_identifier
|
||||
)
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
|
@ -560,6 +578,7 @@ def process_instance_run(
|
|||
|
||||
def process_instance_terminate(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_run."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -572,6 +591,7 @@ def process_instance_terminate(
|
|||
|
||||
def process_instance_suspend(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_suspend."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -584,6 +604,7 @@ def process_instance_suspend(
|
|||
|
||||
def process_instance_resume(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_resume."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -595,19 +616,24 @@ def process_instance_resume(
|
|||
|
||||
|
||||
def process_instance_log_list(
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
detailed: bool = False,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_log_list."""
|
||||
# to make sure the process instance exists
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
|
||||
logs = (
|
||||
SpiffLoggingModel.query.filter(
|
||||
log_query = SpiffLoggingModel.query.filter(
|
||||
SpiffLoggingModel.process_instance_id == process_instance.id
|
||||
)
|
||||
.order_by(SpiffLoggingModel.timestamp.desc()) # type: ignore
|
||||
if not detailed:
|
||||
log_query = log_query.filter(SpiffLoggingModel.message.in_(["State change to COMPLETED"])) # type: ignore
|
||||
|
||||
logs = (
|
||||
log_query.order_by(SpiffLoggingModel.timestamp.desc()) # type: ignore
|
||||
.join(
|
||||
UserModel, UserModel.id == SpiffLoggingModel.current_user_id, isouter=True
|
||||
) # isouter since if we don't have a user, we still want the log
|
||||
|
@ -653,6 +679,7 @@ def message_instance_list(
|
|||
.add_columns(
|
||||
MessageModel.identifier.label("message_identifier"),
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
ProcessInstanceModel.process_model_display_name,
|
||||
)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
@ -787,10 +814,11 @@ def process_instance_list(
|
|||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
user_filter: Optional[bool] = False,
|
||||
report_identifier: Optional[str] = None,
|
||||
report_id: Optional[int] = None,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instance_report = ProcessInstanceReportService.report_with_identifier(
|
||||
g.user, report_identifier
|
||||
g.user, report_id, report_identifier
|
||||
)
|
||||
|
||||
if user_filter:
|
||||
|
@ -821,7 +849,6 @@ def process_instance_list(
|
|||
)
|
||||
)
|
||||
|
||||
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
|
||||
process_instance_query = ProcessInstanceModel.query
|
||||
# Always join that hot user table for good performance at serialization time.
|
||||
process_instance_query = process_instance_query.options(
|
||||
|
@ -939,25 +966,78 @@ def process_instance_list(
|
|||
UserGroupAssignmentModel.user_id == g.user.id
|
||||
)
|
||||
|
||||
instance_metadata_aliases = {}
|
||||
stock_columns = ProcessInstanceReportService.get_column_names_for_model(
|
||||
ProcessInstanceModel
|
||||
)
|
||||
for column in process_instance_report.report_metadata["columns"]:
|
||||
if column["accessor"] in stock_columns:
|
||||
continue
|
||||
instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
|
||||
instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
|
||||
|
||||
filter_for_column = None
|
||||
if "filter_by" in process_instance_report.report_metadata:
|
||||
filter_for_column = next(
|
||||
(
|
||||
f
|
||||
for f in process_instance_report.report_metadata["filter_by"]
|
||||
if f["field_name"] == column["accessor"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
isouter = True
|
||||
conditions = [
|
||||
ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
|
||||
instance_metadata_alias.key == column["accessor"],
|
||||
]
|
||||
if filter_for_column:
|
||||
isouter = False
|
||||
conditions.append(
|
||||
instance_metadata_alias.value == filter_for_column["field_value"]
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
instance_metadata_alias, and_(*conditions), isouter=isouter
|
||||
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
|
||||
|
||||
order_by_query_array = []
|
||||
order_by_array = process_instance_report.report_metadata["order_by"]
|
||||
if len(order_by_array) < 1:
|
||||
order_by_array = ProcessInstanceReportModel.default_order_by()
|
||||
for order_by_option in order_by_array:
|
||||
attribute = re.sub("^-", "", order_by_option)
|
||||
if attribute in stock_columns:
|
||||
if order_by_option.startswith("-"):
|
||||
order_by_query_array.append(
|
||||
getattr(ProcessInstanceModel, attribute).desc()
|
||||
)
|
||||
else:
|
||||
order_by_query_array.append(
|
||||
getattr(ProcessInstanceModel, attribute).asc()
|
||||
)
|
||||
elif attribute in instance_metadata_aliases:
|
||||
if order_by_option.startswith("-"):
|
||||
order_by_query_array.append(
|
||||
instance_metadata_aliases[attribute].value.desc()
|
||||
)
|
||||
else:
|
||||
order_by_query_array.append(
|
||||
instance_metadata_aliases[attribute].value.asc()
|
||||
)
|
||||
|
||||
process_instances = (
|
||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||
.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
)
|
||||
.add_columns(ProcessInstanceModel.id)
|
||||
.order_by(*order_by_query_array)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
||||
results = list(
|
||||
map(
|
||||
ProcessInstanceService.serialize_flat_with_task_data,
|
||||
process_instances.items,
|
||||
results = ProcessInstanceReportService.add_metadata_columns_to_process_instance(
|
||||
process_instances.items, process_instance_report.report_metadata["columns"]
|
||||
)
|
||||
)
|
||||
report_metadata = process_instance_report.report_metadata
|
||||
|
||||
response_json = {
|
||||
"report_identifier": process_instance_report.identifier,
|
||||
"report_metadata": report_metadata,
|
||||
"report": process_instance_report,
|
||||
"results": results,
|
||||
"filters": report_filter.to_dict(),
|
||||
"pagination": {
|
||||
|
@ -970,6 +1050,22 @@ def process_instance_list(
|
|||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def process_instance_report_column_list() -> flask.wrappers.Response:
|
||||
"""Process_instance_report_column_list."""
|
||||
table_columns = ProcessInstanceReportService.builtin_column_options()
|
||||
columns_for_metadata = (
|
||||
db.session.query(ProcessInstanceMetadataModel.key)
|
||||
.order_by(ProcessInstanceMetadataModel.key)
|
||||
.distinct() # type: ignore
|
||||
.all()
|
||||
)
|
||||
columns_for_metadata_strings = [
|
||||
{"Header": i[0], "accessor": i[0], "filterable": True}
|
||||
for i in columns_for_metadata
|
||||
]
|
||||
return make_response(jsonify(table_columns + columns_for_metadata_strings), 200)
|
||||
|
||||
|
||||
def process_instance_show(
|
||||
modified_process_model_identifier: str, process_instance_id: int
|
||||
) -> flask.wrappers.Response:
|
||||
|
@ -996,7 +1092,9 @@ def process_instance_show(
|
|||
return make_response(jsonify(process_instance), 200)
|
||||
|
||||
|
||||
def process_instance_delete(process_instance_id: int) -> flask.wrappers.Response:
|
||||
def process_instance_delete(
|
||||
process_instance_id: int, modified_process_model_identifier: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Create_process_instance."""
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
|
||||
|
@ -1026,22 +1124,22 @@ def process_instance_report_list(
|
|||
|
||||
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
ProcessInstanceReportModel.create_report(
|
||||
process_instance_report = ProcessInstanceReportModel.create_report(
|
||||
identifier=body["identifier"],
|
||||
user=g.user,
|
||||
report_metadata=body["report_metadata"],
|
||||
)
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_update(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
body: Dict[str, Any],
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1054,15 +1152,15 @@ def process_instance_report_update(
|
|||
process_instance_report.report_metadata = body["report_metadata"]
|
||||
db.session.commit()
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_delete(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1078,11 +1176,9 @@ def process_instance_report_delete(
|
|||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
def service_tasks_show() -> flask.wrappers.Response:
|
||||
"""Service_tasks_show."""
|
||||
def service_task_list() -> flask.wrappers.Response:
|
||||
"""Service_task_list."""
|
||||
available_connectors = ServiceTaskService.available_connectors()
|
||||
print(available_connectors)
|
||||
|
||||
return Response(
|
||||
json.dumps(available_connectors), status=200, mimetype="application/json"
|
||||
)
|
||||
|
@ -1116,19 +1212,17 @@ def authentication_callback(
|
|||
|
||||
|
||||
def process_instance_report_show(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id)
|
||||
"""Process_instance_report_show."""
|
||||
process_instances = ProcessInstanceModel.query.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
).paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1290,7 +1384,7 @@ def get_tasks(
|
|||
|
||||
|
||||
def process_instance_task_list(
|
||||
modified_process_model_id: str,
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
all_tasks: bool = False,
|
||||
spiff_step: int = 0,
|
||||
|
@ -1405,9 +1499,6 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
|||
task.form_ui_schema = ui_form_contents
|
||||
|
||||
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
||||
print(
|
||||
f"task.properties['instructionsForEndUser']: {task.properties['instructionsForEndUser']}"
|
||||
)
|
||||
if task.properties["instructionsForEndUser"]:
|
||||
task.properties["instructionsForEndUser"] = render_jinja_template(
|
||||
task.properties["instructionsForEndUser"], task.data
|
||||
|
@ -1854,7 +1945,12 @@ def _update_form_schema_with_task_data_as_needed(
|
|||
_update_form_schema_with_task_data_as_needed(o, task_data)
|
||||
|
||||
|
||||
def update_task_data(process_instance_id: str, task_id: str, body: Dict) -> Response:
|
||||
def update_task_data(
|
||||
process_instance_id: str,
|
||||
modified_process_model_identifier: str,
|
||||
task_id: str,
|
||||
body: Dict,
|
||||
) -> Response:
|
||||
"""Update task data."""
|
||||
process_instance = ProcessInstanceModel.query.filter(
|
||||
ProcessInstanceModel.id == int(process_instance_id)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Get_env."""
|
||||
"""Save process instance metadata."""
|
||||
from typing import Any
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
|
|
|
@ -235,8 +235,9 @@ class AuthenticationService:
|
|||
refresh_token_object: RefreshTokenModel = RefreshTokenModel.query.filter(
|
||||
RefreshTokenModel.user_id == user_id
|
||||
).first()
|
||||
assert refresh_token_object # noqa: S101
|
||||
if refresh_token_object:
|
||||
return refresh_token_object.token
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_auth_token_from_refresh_token(cls, refresh_token: str) -> dict:
|
||||
|
|
|
@ -81,6 +81,9 @@ from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
|||
from spiffworkflow_backend.models.message_instance import MessageModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.script_attributes_context import (
|
||||
ScriptAttributesContext,
|
||||
|
@ -178,7 +181,12 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
|
|||
)
|
||||
return Script.generate_augmented_list(script_attributes_context)
|
||||
|
||||
def evaluate(self, task: SpiffTask, expression: str, external_methods=None) -> Any:
|
||||
def evaluate(
|
||||
self,
|
||||
task: SpiffTask,
|
||||
expression: str,
|
||||
external_methods: Optional[dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
"""Evaluate."""
|
||||
return self._evaluate(expression, task.data, task, external_methods)
|
||||
|
||||
|
@ -571,6 +579,36 @@ class ProcessInstanceProcessor:
|
|||
db.session.add(details_model)
|
||||
db.session.commit()
|
||||
|
||||
def extract_metadata(self, process_model_info: ProcessModelInfo) -> None:
|
||||
"""Extract_metadata."""
|
||||
metadata_extraction_paths = process_model_info.metadata_extraction_paths
|
||||
if metadata_extraction_paths is None:
|
||||
return
|
||||
if len(metadata_extraction_paths) <= 0:
|
||||
return
|
||||
|
||||
current_data = self.get_current_data()
|
||||
for metadata_extraction_path in metadata_extraction_paths:
|
||||
key = metadata_extraction_path["key"]
|
||||
path = metadata_extraction_path["path"]
|
||||
path_segments = path.split(".")
|
||||
data_for_key = current_data
|
||||
for path_segment in path_segments:
|
||||
data_for_key = data_for_key[path_segment]
|
||||
|
||||
pim = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
).first()
|
||||
if pim is None:
|
||||
pim = ProcessInstanceMetadataModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
)
|
||||
pim.value = data_for_key
|
||||
db.session.add(pim)
|
||||
db.session.commit()
|
||||
|
||||
def save(self) -> None:
|
||||
"""Saves the current state of this processor to the database."""
|
||||
self.process_instance_model.bpmn_json = self.serialize()
|
||||
|
@ -597,6 +635,15 @@ class ProcessInstanceProcessor:
|
|||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
self.extract_metadata(process_model_info)
|
||||
|
||||
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||
# filter out non-usertasks
|
||||
task_spec = ready_or_waiting_task.task_spec
|
||||
|
@ -615,13 +662,6 @@ class ProcessInstanceProcessor:
|
|||
if "formUiSchemaFilename" in properties:
|
||||
ui_form_file_name = properties["formUiSchemaFilename"]
|
||||
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
active_task = None
|
||||
for at in active_tasks:
|
||||
if at.task_id == str(ready_or_waiting_task.id):
|
||||
|
@ -1146,8 +1186,8 @@ class ProcessInstanceProcessor:
|
|||
def get_current_data(self) -> dict[str, Any]:
|
||||
"""Get the current data for the process.
|
||||
|
||||
Return either most recent task data or the process data
|
||||
if the process instance is complete
|
||||
Return either the most recent task data or--if the process instance is complete--
|
||||
the process data.
|
||||
"""
|
||||
if self.process_instance_model.status == "complete":
|
||||
return self.get_data()
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import sqlalchemy
|
||||
from flask_bpmn.models.db import db
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -57,12 +60,21 @@ class ProcessInstanceReportService:
|
|||
|
||||
@classmethod
|
||||
def report_with_identifier(
|
||||
cls, user: UserModel, report_identifier: Optional[str] = None
|
||||
cls,
|
||||
user: UserModel,
|
||||
report_id: Optional[int] = None,
|
||||
report_identifier: Optional[str] = None,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Report_with_filter."""
|
||||
if report_id is not None:
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
id=report_id, created_by_id=user.id
|
||||
).first()
|
||||
if process_instance_report is not None:
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
if report_identifier is None:
|
||||
report_identifier = "default"
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier, created_by_id=user.id
|
||||
).first()
|
||||
|
@ -73,17 +85,9 @@ class ProcessInstanceReportService:
|
|||
# TODO replace with system reports that are loaded on launch (or similar)
|
||||
temp_system_metadata_map = {
|
||||
"default": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_initiated_by_me": {
|
||||
"columns": [
|
||||
|
@ -97,48 +101,31 @@ class ProcessInstanceReportService:
|
|||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_me": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{
|
||||
"field_name": "with_tasks_completed_by_my_group",
|
||||
"field_value": True,
|
||||
}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
}
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel(
|
||||
identifier=report_identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
|
||||
report_metadata=temp_system_metadata_map[report_identifier],
|
||||
)
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
@ -241,3 +228,43 @@ class ProcessInstanceReportService:
|
|||
)
|
||||
|
||||
return report_filter
|
||||
|
||||
@classmethod
|
||||
def add_metadata_columns_to_process_instance(
|
||||
cls,
|
||||
process_instance_sqlalchemy_rows: list[sqlalchemy.engine.row.Row], # type: ignore
|
||||
metadata_columns: list[dict],
|
||||
) -> list[dict]:
|
||||
"""Add_metadata_columns_to_process_instance."""
|
||||
results = []
|
||||
for process_instance in process_instance_sqlalchemy_rows:
|
||||
process_instance_dict = process_instance["ProcessInstanceModel"].serialized
|
||||
for metadata_column in metadata_columns:
|
||||
if metadata_column["accessor"] not in process_instance_dict:
|
||||
process_instance_dict[
|
||||
metadata_column["accessor"]
|
||||
] = process_instance[metadata_column["accessor"]]
|
||||
|
||||
results.append(process_instance_dict)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_column_names_for_model(cls, model: db.Model) -> list[str]: # type: ignore
|
||||
"""Get_column_names_for_model."""
|
||||
return [i.name for i in model.__table__.columns]
|
||||
|
||||
@classmethod
|
||||
def builtin_column_options(cls) -> list[dict]:
|
||||
"""Builtin_column_options."""
|
||||
return [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
]
|
||||
|
|
|
@ -322,18 +322,3 @@ class ProcessInstanceService:
|
|||
)
|
||||
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def serialize_flat_with_task_data(
|
||||
process_instance: ProcessInstanceModel,
|
||||
) -> dict[str, Any]:
|
||||
"""NOTE: This is crazy slow. Put the latest task data in the database."""
|
||||
"""Serialize_flat_with_task_data."""
|
||||
# results = {}
|
||||
# try:
|
||||
# processor = ProcessInstanceProcessor(process_instance)
|
||||
# process_instance.data = processor.get_current_data()
|
||||
# results = process_instance.serialized_flat
|
||||
# except ApiError:
|
||||
results = process_instance.serialized
|
||||
return results
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
<bpmn:scriptTask id="hot_script_task_OH_YEEEEEEEEEEEEEEEEEEEEAH" name="OHHHHHHHHHHYEEEESSSSSSSSSS">
|
||||
<bpmn:incoming>Flow_0bazl8x</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1mcaszp</bpmn:outgoing>
|
||||
<bpmn:script>a = 1</bpmn:script>
|
||||
<bpmn:script>a = 1
|
||||
b = 2
|
||||
outer = {}
|
||||
outer["inner"] = 'sweet1'
|
||||
</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_1vch1y0">
|
||||
<bpmn:incoming>Flow_1mcaszp</bpmn:incoming>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="Process_hk6nsfl" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1ohrjz9</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1ohrjz9" sourceRef="StartEvent_1" targetRef="Activity_0fah9rm" />
|
||||
<bpmn:endEvent id="Event_1tk4dsv">
|
||||
<bpmn:incoming>Flow_1flxgry</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_18gs4jt" sourceRef="Activity_0fah9rm" targetRef="Activity_1bvyv67" />
|
||||
<bpmn:scriptTask id="Activity_0fah9rm" name="First setting of data">
|
||||
<bpmn:incoming>Flow_1ohrjz9</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_18gs4jt</bpmn:outgoing>
|
||||
<bpmn:script>outer = {}
|
||||
invoice_number = 123
|
||||
outer["inner"] = 'sweet1'
|
||||
outer['time'] = time.time_ns()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1flxgry" sourceRef="Activity_1bvyv67" targetRef="Event_1tk4dsv" />
|
||||
<bpmn:scriptTask id="Activity_1bvyv67" name="First setting of data">
|
||||
<bpmn:incoming>Flow_18gs4jt</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1flxgry</bpmn:outgoing>
|
||||
<bpmn:script>outer["inner"] = 'sweet2'</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_hk6nsfl">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1c5bi8c_di" bpmnElement="Activity_0fah9rm">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1tk4dsv_di" bpmnElement="Event_1tk4dsv">
|
||||
<dc:Bounds x="612" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1ay4o3w_di" bpmnElement="Activity_1bvyv67">
|
||||
<dc:Bounds x="430" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1ohrjz9_di" bpmnElement="Flow_1ohrjz9">
|
||||
<di:waypoint x="215" y="177" />
|
||||
<di:waypoint x="270" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_18gs4jt_di" bpmnElement="Flow_18gs4jt">
|
||||
<di:waypoint x="370" y="177" />
|
||||
<di:waypoint x="430" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1flxgry_di" bpmnElement="Flow_1flxgry">
|
||||
<di:waypoint x="530" y="177" />
|
||||
<di:waypoint x="612" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -265,7 +265,7 @@ class BaseTest:
|
|||
)
|
||||
modified_process_model_id = test_process_model_id.replace("/", ":")
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_id}/process-instances",
|
||||
f"/v1.0/process-instances/{modified_process_model_id}",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
|
|
@ -57,7 +57,7 @@ class TestLoggingService(BaseTest):
|
|||
assert response.status_code == 200
|
||||
|
||||
log_response = client.get(
|
||||
f"/v1.0/process-instances/{process_instance_id}/logs",
|
||||
f"/v1.0/logs/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
|
||||
headers=headers,
|
||||
)
|
||||
assert log_response.status_code == 200
|
||||
|
|
|
@ -20,6 +20,9 @@ from spiffworkflow_backend.models.group import GroupModel
|
|||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -330,6 +333,9 @@ class TestProcessApi(BaseTest):
|
|||
process_model.display_name = "Updated Display Name"
|
||||
process_model.primary_file_name = "superduper.bpmn"
|
||||
process_model.primary_process_id = "superduper"
|
||||
process_model.metadata_extraction_paths = [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
response = client.put(
|
||||
|
@ -343,6 +349,9 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json["display_name"] == "Updated Display Name"
|
||||
assert response.json["primary_file_name"] == "superduper.bpmn"
|
||||
assert response.json["primary_process_id"] == "superduper"
|
||||
assert response.json["metadata_extraction_paths"] == [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
def test_process_model_list_all(
|
||||
self,
|
||||
|
@ -903,7 +912,7 @@ class TestProcessApi(BaseTest):
|
|||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_identifier}/process-instances",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
@ -1145,10 +1154,11 @@ class TestProcessApi(BaseTest):
|
|||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
show_response = client.get(
|
||||
f"/v1.0/process-models/{modified_process_model_identifier}/process-instances/{process_instance_id}",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert show_response.json is not None
|
||||
assert show_response.status_code == 200
|
||||
file_system_root = FileSystemService.root_path()
|
||||
file_path = (
|
||||
f"{file_system_root}/{process_model_identifier}/{process_model_id}.bpmn"
|
||||
|
@ -1311,7 +1321,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/terminate",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/terminate",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1358,7 +1368,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
|
||||
delete_response = client.delete(
|
||||
f"/v1.0/process-instances/{process_instance_id}",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert delete_response.status_code == 200
|
||||
|
@ -1723,14 +1733,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure",
|
||||
f"/v1.0/process-instances/reports/{report.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1769,14 +1779,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
f"/v1.0/process-instances/reports/{report.id}?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1791,9 +1801,9 @@ class TestProcessApi(BaseTest):
|
|||
with_super_admin_user: UserModel,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_process_instance_report_show_with_default_list."""
|
||||
"""Test_process_instance_report_show_with_bad_identifier."""
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
"/v1.0/process-instances/reports/13000000?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
@ -2357,7 +2367,7 @@ class TestProcessApi(BaseTest):
|
|||
assert process_instance.status == "user_input_required"
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/suspend",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/suspend",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -2661,3 +2671,191 @@ class TestProcessApi(BaseTest):
|
|||
# )
|
||||
|
||||
print("test_process_model_publish")
|
||||
|
||||
def test_can_get_process_instance_list_with_report_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=with_super_admin_user
|
||||
)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "ID", "accessor": "id"},
|
||||
{"Header": "Status", "accessor": "status"},
|
||||
{"Header": "Key One", "accessor": "key1"},
|
||||
{"Header": "Key Two", "accessor": "key2"},
|
||||
],
|
||||
"order_by": ["status"],
|
||||
"filter_by": [],
|
||||
}
|
||||
process_instance_report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_identifier={process_instance_report.identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
|
||||
assert len(response.json["results"]) == 1
|
||||
assert response.json["results"][0]["status"] == "complete"
|
||||
assert response.json["results"][0]["id"] == process_instance.id
|
||||
assert response.json["results"][0]["key1"] == "value1"
|
||||
assert response.json["results"][0]["key2"] == "value2"
|
||||
assert response.json["pagination"]["count"] == 1
|
||||
assert response.json["pagination"]["pages"] == 1
|
||||
assert response.json["pagination"]["total"] == 1
|
||||
|
||||
def test_can_get_process_instance_report_column_list(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=with_super_admin_user
|
||||
)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/columns",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
assert response.json == [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
{"Header": "key1", "accessor": "key1", "filterable": True},
|
||||
{"Header": "key2", "accessor": "key2", "filterable": True},
|
||||
{"Header": "key3", "accessor": "key3", "filterable": True},
|
||||
]
|
||||
|
||||
def test_process_instance_list_can_order_by_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_process_instance_list_can_order_by_metadata."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "time_ns", "path": "outer.time"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance_one = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_one)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_one.status == "complete"
|
||||
process_instance_two = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_two)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_two.status == "complete"
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["time_ns"],
|
||||
}
|
||||
report_one = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_one",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_one.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][0]["id"] == process_instance_one.id
|
||||
assert response.json["results"][1]["id"] == process_instance_two.id
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["-time_ns"],
|
||||
}
|
||||
report_two = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_two",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_two.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][1]["id"] == process_instance_one.id
|
||||
assert response.json["results"][0]["id"] == process_instance_two.id
|
||||
|
|
|
@ -37,7 +37,7 @@ def test_generate_report_with_filter_by_with_variable_substitution(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_filter_by_with_variable_substitution."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"filter_by": [
|
||||
|
@ -61,7 +61,7 @@ def test_generate_report_with_order_by_and_one_field(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_one_field."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -75,7 +75,7 @@ def test_generate_report_with_order_by_and_two_fields(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_two_fields."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -89,7 +89,7 @@ def test_generate_report_with_order_by_desc(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_desc."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "-test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -103,7 +103,7 @@ def test_generate_report_with_columns(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_columns."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
|
|
|
@ -5,12 +5,16 @@ from flask_bpmn.models.db import db
|
|||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
class TestProcessModel(BaseTest):
|
||||
|
@ -122,6 +126,53 @@ class TestProcessModel(BaseTest):
|
|||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
def test_extract_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_run_process_model_with_call_activities."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "awesome_var", "path": "outer.inner"},
|
||||
{"key": "invoice_number", "path": "invoice_number"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="awesome_var"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "sweet2"
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="invoice_number"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "123"
|
||||
|
||||
def create_test_process_model(self, id: str, display_name: str) -> ProcessModelInfo:
|
||||
"""Create_test_process_model."""
|
||||
return ProcessModelInfo(
|
||||
|
|
Loading…
Reference in New Issue