Squashed 'spiffworkflow-backend/' changes from 2cb3fb27e2..55f5c8113e

55f5c8113e arena github actions
a06427482b reduce matrix
f5f88926a6 debug
23fbe1b2e9 lint and mypy
30ee07700f Merge remote-tracking branch 'origin/main' into feature/home_page_redesign
bd38d90600 lint
201655fc0f add the username to the task list w/ burnettk
e069906394 merged in main and resolved conflicts w/ burnettk
9135f74a03 added more task tables w/ burnettk
352353e48b store bpmn_file_relative_path using correct slashes w/ burnettk
d02a6610bb underscore unused vars
a9ecaa0431 added tasks for my open processes page w/ burnettk
74af6a4ad1 Merge remote-tracking branch 'origin/main' into feature/home_page_redesign
d5330d6031 Merge remote-tracking branch 'origin/main' into feature/home_page_redesign
37d9ca8dea added home page routes and some tab stuff w/ burnettk

git-subtree-dir: spiffworkflow-backend
git-subtree-split: 55f5c8113e1189888672992d109e80a3d51dfa1a
This commit is contained in:
burnettk 2022-11-13 18:42:04 -05:00
parent a1d7c2b6cb
commit ac6706197c
9 changed files with 164 additions and 16 deletions

View File

@ -156,7 +156,7 @@ jobs:
- name: Upload coverage data
# pin to upload coverage from only one matrix entry, otherwise coverage gets confused later
if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest'
if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest' && matrix.database == 'mysql'
uses: "actions/upload-artifact@v3.0.0"
with:
name: coverage-data

View File

@ -39,11 +39,14 @@ class MyJSONEncoder(DefaultJSONProvider):
return_dict = {}
for row_key in obj.keys():
row_value = obj[row_key]
if hasattr(row_value, "__dict__"):
if hasattr(row_value, "serialized"):
return_dict.update(row_value.serialized)
elif hasattr(row_value, "__dict__"):
return_dict.update(row_value.__dict__)
else:
return_dict.update({row_key: row_value})
return_dict.pop("_sa_instance_state")
if "_sa_instance_state" in return_dict:
return_dict.pop("_sa_instance_state")
return return_dict
return super().default(obj)

View File

@ -872,6 +872,64 @@ paths:
items:
$ref: "#/components/schemas/Task"
/tasks/for-my-open-processes:
parameters:
- name: page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
- name: per_page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
get:
tags:
- Process Instances
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_my_open_processes
summary: returns the list of tasks for given user's open process instances
responses:
"200":
description: list of tasks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Task"
/tasks/for-processes-started-by-others:
parameters:
- name: page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
- name: per_page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
get:
tags:
- Process Instances
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_processes_started_by_others
summary: returns the list of tasks for given user's open process instances
responses:
"200":
description: list of tasks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Task"
/process-instance/{process_instance_id}/tasks:
parameters:
- name: process_instance_id

View File

@ -100,17 +100,17 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
local_bpmn_xml_file_contents = ""
if self.bpmn_xml_file_contents:
local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8")
return {
"id": self.id,
"process_model_identifier": self.process_model_identifier,
"process_group_identifier": self.process_group_identifier,
"status": self.status,
"bpmn_json": self.bpmn_json,
"start_in_seconds": self.start_in_seconds,
"end_in_seconds": self.end_in_seconds,
"process_initiator_id": self.process_initiator_id,
"bpmn_xml_file_contents": local_bpmn_xml_file_contents,
"bpmn_version_control_identifier": self.bpmn_version_control_identifier,
"bpmn_version_control_type": self.bpmn_version_control_type,
"spiff_step": self.spiff_step,
}

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import enum
import os
from dataclasses import dataclass
from dataclasses import field
from typing import Any
@ -50,6 +51,11 @@ class ProcessModelInfo:
return True
return False
# for use with os.path.join so it can work on windows
def id_for_file_path(self) -> str:
"""Id_for_file_path."""
return self.id.replace("/", os.sep)
class ProcessModelInfoSchema(Schema):
"""ProcessModelInfoSchema."""

View File

@ -28,6 +28,7 @@ from lxml import etree # type: ignore
from lxml.builder import ElementMaker # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from SpiffWorkflow.task import TaskState
from sqlalchemy import and_
from sqlalchemy import asc
from sqlalchemy import desc
@ -37,6 +38,7 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
from spiffworkflow_backend.models.active_task import ActiveTaskModel
from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel
from spiffworkflow_backend.models.file import FileSchema
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.message_model import MessageModel
from spiffworkflow_backend.models.message_triggerable_process_model import (
@ -1000,6 +1002,67 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
return make_response(jsonify(response_json), 200)
def task_list_for_my_open_processes(
page: int = 1, per_page: int = 100
) -> flask.wrappers.Response:
"""Task_list_for_my_open_processes."""
return get_tasks(page=page, per_page=per_page)
def task_list_for_processes_started_by_others(
page: int = 1, per_page: int = 100
) -> flask.wrappers.Response:
"""Task_list_for_processes_started_by_others."""
return get_tasks(processes_started_by_user=False, page=page, per_page=per_page)
def get_tasks(
processes_started_by_user: bool = True, page: int = 1, per_page: int = 100
) -> flask.wrappers.Response:
"""Get_tasks."""
user_id = g.user.id
active_tasks_query = (
ActiveTaskModel.query.outerjoin(
GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id
)
.join(ProcessInstanceModel)
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
)
if processes_started_by_user:
active_tasks_query = active_tasks_query.filter(
ProcessInstanceModel.process_initiator_id == user_id
).outerjoin(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id))
else:
active_tasks_query = active_tasks_query.filter(
ProcessInstanceModel.process_initiator_id != user_id
).join(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id))
active_tasks = active_tasks_query.add_columns(
ProcessInstanceModel.process_model_identifier,
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
ProcessInstanceModel.updated_at_in_seconds,
ProcessInstanceModel.created_at_in_seconds,
UserModel.username,
GroupModel.identifier.label("group_identifier"),
ActiveTaskModel.task_name,
ActiveTaskModel.task_title,
ActiveTaskModel.process_model_display_name,
ActiveTaskModel.process_instance_id,
ActiveTaskUserModel.user_id.label("current_user_is_potential_owner"),
).paginate(page=page, per_page=per_page, error_out=False)
response_json = {
"results": active_tasks.items,
"pagination": {
"count": len(active_tasks.items),
"total": active_tasks.total,
"pages": active_tasks.pages,
},
}
return make_response(jsonify(response_json), 200)
def process_instance_task_list(
process_instance_id: int, all_tasks: bool = False, spiff_step: int = 0
) -> flask.wrappers.Response:
@ -1354,9 +1417,18 @@ def find_process_instance_by_id_or_raise(
process_instance_id: int,
) -> ProcessInstanceModel:
"""Find_process_instance_by_id_or_raise."""
process_instance = ProcessInstanceModel.query.filter_by(
process_instance_query = ProcessInstanceModel.query.filter_by(
id=process_instance_id
).first()
)
# we had a frustrating session trying to do joins and access columns from two tables. here's some notes for our future selves:
# this returns an object that allows you to do: process_instance.UserModel.username
# process_instance = db.session.query(ProcessInstanceModel, UserModel).filter_by(id=process_instance_id).first()
# you can also use splat with add_columns, but it still didn't ultimately give us access to the process instance
# attributes or username like we wanted:
# process_instance_query.join(UserModel).add_columns(*ProcessInstanceModel.__table__.columns, UserModel.username)
process_instance = process_instance_query.first()
if process_instance is None:
raise (
ApiError(

View File

@ -218,7 +218,7 @@ class ProcessModelService(FileSystemService):
def __get_all_nested_models(self, group_path: str) -> list:
"""__get_all_nested_models."""
all_nested_models = []
for root, dirs, files in os.walk(group_path):
for _root, dirs, _files in os.walk(group_path):
for dir in dirs:
model_dir = os.path.join(group_path, dir)
if ProcessModelService().is_model(model_dir):

View File

@ -375,7 +375,7 @@ class SpecFileService(FileSystemService):
process_model_info: ProcessModelInfo, bpmn_file_name: str, et_root: _Element
) -> None:
"""Store_bpmn_process_identifiers."""
relative_process_model_path = process_model_info.id
relative_process_model_path = process_model_info.id_for_file_path()
relative_bpmn_file_path = os.path.join(
relative_process_model_path, bpmn_file_name

View File

@ -1441,7 +1441,7 @@ class TestProcessApi(BaseTest):
updated_at_in_seconds=round(time.time()),
start_in_seconds=(1000 * i) + 1000,
end_in_seconds=(1000 * i) + 2000,
bpmn_json=json.dumps({"i": i}),
bpmn_version_control_identifier=i,
)
db.session.add(process_instance)
db.session.commit()
@ -1487,7 +1487,12 @@ class TestProcessApi(BaseTest):
results = response.json["results"]
assert len(results) == 4
for i in range(4):
assert json.loads(results[i]["bpmn_json"])["i"] in (1, 2, 3, 4)
assert json.loads(results[i]["bpmn_version_control_identifier"]) in (
1,
2,
3,
4,
)
# start > 2000, end < 5000 - this should eliminate the first 2 and the last
response = client.get(
@ -1497,8 +1502,8 @@ class TestProcessApi(BaseTest):
assert response.json is not None
results = response.json["results"]
assert len(results) == 2
assert json.loads(results[0]["bpmn_json"])["i"] in (2, 3)
assert json.loads(results[1]["bpmn_json"])["i"] in (2, 3)
assert json.loads(results[0]["bpmn_version_control_identifier"]) in (2, 3)
assert json.loads(results[1]["bpmn_version_control_identifier"]) in (2, 3)
# start > 1000, start < 4000 - this should eliminate the first and the last 2
response = client.get(
@ -1508,8 +1513,8 @@ class TestProcessApi(BaseTest):
assert response.json is not None
results = response.json["results"]
assert len(results) == 2
assert json.loads(results[0]["bpmn_json"])["i"] in (1, 2)
assert json.loads(results[1]["bpmn_json"])["i"] in (1, 2)
assert json.loads(results[0]["bpmn_version_control_identifier"]) in (1, 2)
assert json.loads(results[1]["bpmn_version_control_identifier"]) in (1, 2)
# end > 2000, end < 6000 - this should eliminate the first and the last
response = client.get(
@ -1520,7 +1525,11 @@ class TestProcessApi(BaseTest):
results = response.json["results"]
assert len(results) == 3
for i in range(3):
assert json.loads(results[i]["bpmn_json"])["i"] in (1, 2, 3)
assert json.loads(results[i]["bpmn_version_control_identifier"]) in (
1,
2,
3,
)
def test_process_instance_report_list(
self,