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 - name: Upload coverage data
# pin to upload coverage from only one matrix entry, otherwise coverage gets confused later # 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" uses: "actions/upload-artifact@v3.0.0"
with: with:
name: coverage-data name: coverage-data

View File

@ -39,10 +39,13 @@ class MyJSONEncoder(DefaultJSONProvider):
return_dict = {} return_dict = {}
for row_key in obj.keys(): for row_key in obj.keys():
row_value = obj[row_key] 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__) return_dict.update(row_value.__dict__)
else: else:
return_dict.update({row_key: row_value}) return_dict.update({row_key: row_value})
if "_sa_instance_state" in return_dict:
return_dict.pop("_sa_instance_state") return_dict.pop("_sa_instance_state")
return return_dict return return_dict
return super().default(obj) return super().default(obj)

View File

@ -872,6 +872,64 @@ paths:
items: items:
$ref: "#/components/schemas/Task" $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: /process-instance/{process_instance_id}/tasks:
parameters: parameters:
- name: process_instance_id - name: process_instance_id

View File

@ -100,17 +100,17 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
local_bpmn_xml_file_contents = "" local_bpmn_xml_file_contents = ""
if self.bpmn_xml_file_contents: if self.bpmn_xml_file_contents:
local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8") local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8")
return { return {
"id": self.id, "id": self.id,
"process_model_identifier": self.process_model_identifier, "process_model_identifier": self.process_model_identifier,
"process_group_identifier": self.process_group_identifier, "process_group_identifier": self.process_group_identifier,
"status": self.status, "status": self.status,
"bpmn_json": self.bpmn_json,
"start_in_seconds": self.start_in_seconds, "start_in_seconds": self.start_in_seconds,
"end_in_seconds": self.end_in_seconds, "end_in_seconds": self.end_in_seconds,
"process_initiator_id": self.process_initiator_id, "process_initiator_id": self.process_initiator_id,
"bpmn_xml_file_contents": local_bpmn_xml_file_contents, "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, "spiff_step": self.spiff_step,
} }

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import enum import enum
import os
from dataclasses import dataclass from dataclasses import dataclass
from dataclasses import field from dataclasses import field
from typing import Any from typing import Any
@ -50,6 +51,11 @@ class ProcessModelInfo:
return True return True
return False 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): class ProcessModelInfoSchema(Schema):
"""ProcessModelInfoSchema.""" """ProcessModelInfoSchema."""

View File

@ -28,6 +28,7 @@ from lxml import etree # type: ignore
from lxml.builder import ElementMaker # type: ignore from lxml.builder import ElementMaker # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from SpiffWorkflow.task import TaskState from SpiffWorkflow.task import TaskState
from sqlalchemy import and_
from sqlalchemy import asc from sqlalchemy import asc
from sqlalchemy import desc 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 import ActiveTaskModel
from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel
from spiffworkflow_backend.models.file import FileSchema 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_instance import MessageInstanceModel
from spiffworkflow_backend.models.message_model import MessageModel from spiffworkflow_backend.models.message_model import MessageModel
from spiffworkflow_backend.models.message_triggerable_process_model import ( 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) 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( def process_instance_task_list(
process_instance_id: int, all_tasks: bool = False, spiff_step: int = 0 process_instance_id: int, all_tasks: bool = False, spiff_step: int = 0
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
@ -1354,9 +1417,18 @@ def find_process_instance_by_id_or_raise(
process_instance_id: int, process_instance_id: int,
) -> ProcessInstanceModel: ) -> ProcessInstanceModel:
"""Find_process_instance_by_id_or_raise.""" """Find_process_instance_by_id_or_raise."""
process_instance = ProcessInstanceModel.query.filter_by( process_instance_query = ProcessInstanceModel.query.filter_by(
id=process_instance_id 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: if process_instance is None:
raise ( raise (
ApiError( ApiError(

View File

@ -218,7 +218,7 @@ class ProcessModelService(FileSystemService):
def __get_all_nested_models(self, group_path: str) -> list: def __get_all_nested_models(self, group_path: str) -> list:
"""__get_all_nested_models.""" """__get_all_nested_models."""
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: for dir in dirs:
model_dir = os.path.join(group_path, dir) model_dir = os.path.join(group_path, dir)
if ProcessModelService().is_model(model_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 process_model_info: ProcessModelInfo, bpmn_file_name: str, et_root: _Element
) -> None: ) -> None:
"""Store_bpmn_process_identifiers.""" """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_bpmn_file_path = os.path.join(
relative_process_model_path, bpmn_file_name relative_process_model_path, bpmn_file_name

View File

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