Merge remote-tracking branch 'origin/main' into new_report

This commit is contained in:
jasquat 2022-11-29 16:39:45 -05:00
commit 57ddaff26c
14 changed files with 334 additions and 102 deletions

View File

@ -20,6 +20,12 @@ function get_python_dirs() {
(git ls-tree -r HEAD --name-only | grep -E '\.py$' | awk -F '/' '{print $1}' | sort | uniq | grep -v '\.' | grep -Ev '^(bin|migrations)$') || echo ''
}
function run_fix_docstrings() {
if command -v fix_python_docstrings >/dev/null ; then
fix_python_docstrings $(get_top_level_directories_containing_python_files)
fi
}
function run_autoflake() {
if ! command -v autoflake8 >/dev/null ; then
pip install autoflake8
@ -51,6 +57,7 @@ done
for python_project in "${python_projects[@]}" ; do
pushd "$python_project"
run_fix_docstrings || run_fix_docstrings
run_autoflake || run_autoflake
popd
done

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: 568f5fe2c9f8
Revision ID: ff1c1628337c
Revises:
Create Date: 2022-11-24 12:11:46.669020
Create Date: 2022-11-28 15:08:52.014254
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '568f5fe2c9f8'
revision = 'ff1c1628337c'
down_revision = None
branch_labels = None
depends_on = None
@ -166,17 +166,6 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('key')
)
op.create_table('spiff_step_details',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
sa.Column('spiff_step', sa.Integer(), nullable=False),
sa.Column('task_json', sa.JSON(), nullable=False),
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user_group_assignment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
@ -249,6 +238,29 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq')
)
op.create_table('process_instance_metadata',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(length=255), nullable=False),
sa.Column('value', sa.String(length=255), nullable=False),
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=False),
sa.Column('created_at_in_seconds', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
)
op.create_table('spiff_step_details',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
sa.Column('spiff_step', sa.Integer(), nullable=False),
sa.Column('task_json', sa.JSON(), nullable=False),
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.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),
@ -282,6 +294,8 @@ def downgrade():
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('spiff_step_details')
op.drop_table('process_instance_metadata')
op.drop_table('permission_assignment')
op.drop_table('message_instance')
op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation')
@ -291,7 +305,6 @@ def downgrade():
op.drop_table('message_correlation')
op.drop_table('active_task')
op.drop_table('user_group_assignment')
op.drop_table('spiff_step_details')
op.drop_table('secret')
op.drop_table('refresh_token')
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')

View File

@ -136,47 +136,38 @@ permissions:
allowed_permissions: [create, read, update, delete]
uri: /v1.0/process-groups/manage-procurement:procurement:*
demo-models-instantiate-vendor-block:
groups: ["demo"]
manage-revenue-streams-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-block/process-instances
demo-models-instantiate-vendor-change:
groups: ["demo"]
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
manage-revenue-streams-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-change/process-instances
demo-models-instantiate-vendor-creation:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-creation/process-instances
demo-models-instantiate-vendor-core-invoice_appoval:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:cc-invoice-approval/process-instances
demo-models-customer-contracts:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/process-instances
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
core-admin-models-instantiate:
groups: ["core-contributor"]
manage-procurement-invoice-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:cc-invoice-approval/process-instances
core-admin-instances:
groups: ["core-contributor"]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
manage-procurement-invoice-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
core-admin-instances-slash:
groups: ["core-contributor"]
manage-procurement-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
manage-procurement-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management/*
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
core1-admin-models-instantiate:
groups: ["core-contributor"]

View File

@ -136,44 +136,35 @@ permissions:
allowed_permissions: [create, read, update, delete]
uri: /v1.0/process-groups/manage-procurement:procurement:*
demo-models-instantiate-vendor-block:
groups: ["demo"]
manage-revenue-streams-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-block/process-instances
demo-models-instantiate-vendor-change:
groups: ["demo"]
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
manage-revenue-streams-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-change/process-instances
demo-models-instantiate-vendor-creation:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:vendor-md-maintenance:vendor-md-creation/process-instances
demo-models-instantiate-vendor-core-invoice_appoval:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:cc-invoice-approval/process-instances
demo-models-customer-contracts:
groups: ["demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/process-instances
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
core-admin-models-instantiate:
groups: ["core-contributor"]
manage-procurement-invoice-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:cc-invoice-approval/process-instances
core-admin-instances:
groups: ["core-contributor"]
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
manage-procurement-invoice-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
core-admin-instances-slash:
groups: ["core-contributor"]
manage-procurement-instantiate:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create]
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
manage-procurement-instances:
groups: ["core-contributor", "demo"]
users: []
allowed_permissions: [create, read]
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management/*
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*

View File

@ -51,5 +51,8 @@ from spiffworkflow_backend.models.spiff_step_details import (
) # noqa: F401
from spiffworkflow_backend.models.user import UserModel # noqa: F401
from spiffworkflow_backend.models.group import GroupModel # noqa: F401
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
) # noqa: F401
add_listeners()

View File

@ -22,6 +22,10 @@ from spiffworkflow_backend.models.task import TaskSchema
from spiffworkflow_backend.models.user import UserModel
class ProcessInstanceNotFoundError(Exception):
"""ProcessInstanceNotFoundError."""
class NavigationItemSchema(Schema):
"""NavigationItemSchema."""

View File

@ -0,0 +1,30 @@
"""Spiff_step_details."""
from dataclasses import dataclass
from flask_bpmn.models.db import db
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
from sqlalchemy import ForeignKey
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
@dataclass
class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
"""ProcessInstanceMetadataModel."""
__tablename__ = "process_instance_metadata"
__table_args__ = (
db.UniqueConstraint(
"process_instance_id", "key", name="process_instance_metadata_unique"
),
)
id: int = db.Column(db.Integer, primary_key=True)
process_instance_id: int = db.Column(
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
)
key: str = db.Column(db.String(255), nullable=False)
value: str = db.Column(db.String(255), nullable=False)
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
created_at_in_seconds: int = db.Column(db.Integer, nullable=False)

View File

@ -8,6 +8,7 @@ from sqlalchemy import ForeignKey
from sqlalchemy.orm import deferred
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
@dataclass
@ -16,7 +17,9 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
__tablename__ = "spiff_step_details"
id: int = db.Column(db.Integer, primary_key=True)
process_instance_id: int = db.Column(db.Integer, nullable=False)
process_instance_id: int = db.Column(
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
)
spiff_step: int = db.Column(db.Integer, nullable=False)
task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore
timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False)

View File

@ -0,0 +1,42 @@
"""Get_env."""
from typing import Any
from flask_bpmn.models.db import db
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext,
)
from spiffworkflow_backend.scripts.script import Script
class SaveProcessInstanceMetadata(Script):
"""SaveProcessInstanceMetadata."""
def get_description(self) -> str:
"""Get_description."""
return """Save a given dict as process instance metadata (useful for creating reports)."""
def run(
self,
script_attributes_context: ScriptAttributesContext,
*args: Any,
**kwargs: Any,
) -> Any:
"""Run."""
metadata_dict = args[0]
for key, value in metadata_dict.items():
pim = ProcessInstanceMetadataModel.query.filter_by(
process_instance_id=script_attributes_context.process_instance_id,
key=key,
).first()
if pim is None:
pim = ProcessInstanceMetadataModel(
process_instance_id=script_attributes_context.process_instance_id,
key=key,
)
pim.value = value
db.session.add(pim)
db.session.commit()

View File

@ -590,16 +590,12 @@ class ProcessInstanceProcessor:
if self.bpmn_process_instance.is_completed():
self.process_instance_model.end_in_seconds = round(time.time())
active_tasks = ActiveTaskModel.query.filter_by(
process_instance_id=self.process_instance_model.id
).all()
if len(active_tasks) > 0:
for at in active_tasks:
db.session.delete(at)
db.session.add(self.process_instance_model)
db.session.commit()
active_tasks = ActiveTaskModel.query.filter_by(
process_instance_id=self.process_instance_model.id
).all()
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
for ready_or_waiting_task in ready_or_waiting_tasks:
# filter out non-usertasks
@ -626,6 +622,13 @@ class ProcessInstanceProcessor:
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):
active_task = at
active_tasks.remove(at)
if active_task is None:
active_task = ActiveTaskModel(
process_instance_id=self.process_instance_model.id,
process_model_display_name=process_model_display_name,
@ -641,13 +644,20 @@ class ProcessInstanceProcessor:
db.session.add(active_task)
db.session.commit()
for potential_owner_id in potential_owner_hash["potential_owner_ids"]:
for potential_owner_id in potential_owner_hash[
"potential_owner_ids"
]:
active_task_user = ActiveTaskUserModel(
user_id=potential_owner_id, active_task_id=active_task.id
)
db.session.add(active_task_user)
db.session.commit()
if len(active_tasks) > 0:
for at in active_tasks:
db.session.delete(at)
db.session.commit()
@staticmethod
def get_parser() -> MyCustomParser:
"""Get_parser."""

View File

@ -0,0 +1,52 @@
<?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="test_save_process_instance_metadata" isExecutable="true">
<bpmn:startEvent id="Event_0r6oru6">
<bpmn:outgoing>Flow_1j4jzft</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1j4jzft" sourceRef="Event_0r6oru6" targetRef="save_key1" />
<bpmn:endEvent id="Event_1s123jg">
<bpmn:incoming>Flow_01xr2ac</bpmn:incoming>
</bpmn:endEvent>
<bpmn:scriptTask id="save_key1">
<bpmn:incoming>Flow_1j4jzft</bpmn:incoming>
<bpmn:outgoing>Flow_10xyk22</bpmn:outgoing>
<bpmn:script>save_process_instance_metadata({"key1": "value1"})</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_10xyk22" sourceRef="save_key1" targetRef="save_key2" />
<bpmn:scriptTask id="save_key2">
<bpmn:incoming>Flow_10xyk22</bpmn:incoming>
<bpmn:outgoing>Flow_01xr2ac</bpmn:outgoing>
<bpmn:script>save_process_instance_metadata({"key2": "value2", "key3": "value3"})</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_01xr2ac" sourceRef="save_key2" targetRef="Event_1s123jg" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="test_save_process_instance_metadata">
<bpmndi:BPMNShape id="Event_0r6oru6_di" bpmnElement="Event_0r6oru6">
<dc:Bounds x="162" y="162" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0zfzev2_di" bpmnElement="save_key1">
<dc:Bounds x="250" y="140" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0d1q8x4_di" bpmnElement="save_key2">
<dc:Bounds x="410" y="140" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1s123jg_di" bpmnElement="Event_1s123jg">
<dc:Bounds x="582" y="162" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1j4jzft_di" bpmnElement="Flow_1j4jzft">
<di:waypoint x="198" y="180" />
<di:waypoint x="250" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10xyk22_di" bpmnElement="Flow_10xyk22">
<di:waypoint x="350" y="180" />
<di:waypoint x="410" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_01xr2ac_di" bpmnElement="Flow_01xr2ac">
<di:waypoint x="510" y="180" />
<di:waypoint x="582" y="180" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,45 @@
"""Test_get_localtime."""
from flask.app import Flask
from flask.testing import FlaskClient
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.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
class TestSaveProcessInstanceMetadata(BaseTest):
"""TestSaveProcessInstanceMetadata."""
def test_can_save_process_instance_metadata(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
"""Test_can_save_process_instance_metadata."""
initiator_user = self.find_or_create_user("initiator_user")
self.create_process_group(
client, with_super_admin_user, "test_group", "test_group"
)
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=initiator_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

View File

@ -161,6 +161,7 @@ class TestProcessInstanceProcessor(BaseTest):
)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
processor.save()
assert len(process_instance.active_tasks) == 1
active_task = process_instance.active_tasks[0]
@ -241,3 +242,42 @@ class TestProcessInstanceProcessor(BaseTest):
)
assert process_instance.status == ProcessInstanceStatus.complete.value
def test_does_not_recreate_active_tasks_on_multiple_saves(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
"""Test_sets_permission_correctly_on_active_task_when_using_dict."""
self.create_process_group(
client, with_super_admin_user, "test_group", "test_group"
)
initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3")
assert initiator_user.principal is not None
assert finance_user_three.principal is not None
AuthorizationService.import_permissions_from_yaml_file()
finance_group = GroupModel.query.filter_by(identifier="Finance Team").first()
assert finance_group is not None
process_model = load_test_spec(
process_model_id="test_group/model_with_lanes",
bpmn_file_name="lanes_with_owner_dict.bpmn",
process_model_source_directory="model_with_lanes",
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user
)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
assert len(process_instance.active_tasks) == 1
initial_active_task_id = process_instance.active_tasks[0].id
# save again to ensure we go attempt to process the active tasks again
processor.save()
assert len(process_instance.active_tasks) == 1
assert initial_active_task_id == process_instance.active_tasks[0].id

View File

@ -81,6 +81,7 @@ export default function ProcessModelListTiles({
<div className="tile-process-group-content-container">
<div className="tile-title-top">
<a
title={row.id}
data-qa="process-model-show-link"
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
row.id