From 8b562b0bf2b561b95affeecbb40477203d8ead86 Mon Sep 17 00:00:00 2001
From: jbirddog <100367399+jbirddog@users.noreply.github.com>
Date: Tue, 22 Nov 2022 17:05:58 -0500
Subject: [PATCH 1/2] Update oauth redirect url (#58)
---
.../src/spiffworkflow_backend/routes/process_api_blueprint.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
index 9026abb0e..f11358438 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
@@ -1005,7 +1005,7 @@ def authentication_callback(
f"{service}/{auth_method}", response, g.user.id, create_if_not_exists=True
)
return redirect(
- f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/authentications"
+ f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/configuration"
)
From ceefa9a2f7711f40f4c7126c151a710c33d9a7b0 Mon Sep 17 00:00:00 2001
From: jbirddog <100367399+jbirddog@users.noreply.github.com>
Date: Tue, 22 Nov 2022 17:14:51 -0500
Subject: [PATCH 2/2] Start of system report filters (#57)
---
.../src/spiffworkflow_backend/api.yml | 18 ++++
.../routes/process_api_blueprint.py | 87 ++++++++++++++++++-
.../process_instance_report_service.py | 58 +++++++++++--
.../src/components/MyCompletedInstances.tsx | 2 +
.../components/ProcessInstanceListTable.tsx | 14 ++-
.../src/routes/CompletedInstances.tsx | 31 ++++++-
6 files changed, 196 insertions(+), 14 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index e5e0db974..f85cc74fe 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -514,6 +514,24 @@ paths:
description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended
schema:
type: string
+ - name: initiated_by_me
+ in: query
+ required: false
+ description: For filtering - show instances initiated by me
+ schema:
+ type: boolean
+ - name: with_tasks_completed_by_me
+ in: query
+ required: false
+ description: For filtering - show instances with tasks completed by me
+ schema:
+ type: boolean
+ - name: with_tasks_completed_by_my_group
+ in: query
+ required: false
+ description: For filtering - show instances with tasks completed by my group
+ schema:
+ type: boolean
- name: user_filter
in: query
required: false
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
index f11358438..31eff335b 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
@@ -30,6 +30,7 @@ from SpiffWorkflow.task import TaskState
from sqlalchemy import and_
from sqlalchemy import asc
from sqlalchemy import desc
+from sqlalchemy import select
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
ProcessEntityNotFoundError,
@@ -63,6 +64,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceSchema
from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel
from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsModel
from spiffworkflow_backend.models.user import UserModel
+from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
from spiffworkflow_backend.routes.user import verify_token
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
@@ -762,6 +764,9 @@ def process_instance_list(
end_from: Optional[int] = None,
end_to: Optional[int] = None,
process_status: Optional[str] = None,
+ initiated_by_me: Optional[bool] = None,
+ with_tasks_completed_by_me: Optional[bool] = None,
+ with_tasks_completed_by_my_group: Optional[bool] = None,
user_filter: Optional[bool] = False,
report_identifier: Optional[str] = None,
) -> flask.wrappers.Response:
@@ -778,6 +783,9 @@ def process_instance_list(
end_from,
end_to,
process_status.split(",") if process_status else None,
+ initiated_by_me,
+ with_tasks_completed_by_me,
+ with_tasks_completed_by_my_group,
)
else:
report_filter = (
@@ -789,6 +797,9 @@ def process_instance_list(
end_from,
end_to,
process_status,
+ initiated_by_me,
+ with_tasks_completed_by_me,
+ with_tasks_completed_by_my_group,
)
)
@@ -837,9 +848,79 @@ def process_instance_list(
ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
)
- process_instances = process_instance_query.order_by(
- ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
- ).paginate(page=page, per_page=per_page, error_out=False)
+ if report_filter.initiated_by_me is True:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status == "complete"
+ )
+ process_instance_query = process_instance_query.filter_by(
+ process_initiator=g.user
+ )
+
+ # TODO: not sure if this is exactly what is wanted
+ if report_filter.with_tasks_completed_by_me is True:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status == "complete"
+ )
+ process_instance_query = process_instance_query.join(
+ SpiffStepDetailsModel,
+ ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
+ )
+ process_instance_query = process_instance_query.join(
+ SpiffLoggingModel,
+ ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
+ )
+ process_instance_query = process_instance_query.filter(
+ SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
+ )
+ process_instance_query = process_instance_query.filter(
+ SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
+ )
+ process_instance_query = process_instance_query.filter(
+ SpiffStepDetailsModel.completed_by_user_id == g.user.id
+ )
+
+ # TODO: not sure if this is exactly what is wanted
+ if report_filter.with_tasks_completed_by_my_group is True:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status == "complete"
+ )
+ process_instance_query = process_instance_query.join(
+ SpiffStepDetailsModel,
+ ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
+ )
+ process_instance_query = process_instance_query.join(
+ SpiffLoggingModel,
+ ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
+ )
+ process_instance_query = process_instance_query.filter(
+ SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
+ )
+ process_instance_query = process_instance_query.filter(
+ SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
+ )
+
+ my_groups = (
+ select(UserGroupAssignmentModel) # type: ignore
+ .where(UserGroupAssignmentModel.user_id == g.user.id)
+ .with_only_columns(UserGroupAssignmentModel.group_id)
+ )
+ users_in_my_groups = (
+ select(UserGroupAssignmentModel) # type: ignore
+ .where(UserGroupAssignmentModel.group_id.in_(my_groups))
+ .with_only_columns(UserGroupAssignmentModel.user_id)
+ )
+
+ process_instance_query = process_instance_query.filter(
+ SpiffStepDetailsModel.completed_by_user_id.in_(users_in_my_groups) # type: ignore
+ )
+
+ process_instances = (
+ process_instance_query.distinct()
+ .order_by(
+ ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
+ )
+ .paginate(page=page, per_page=per_page, error_out=False)
+ )
results = list(
map(
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index 9c398514b..731abcdbd 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -18,6 +18,9 @@ class ProcessInstanceReportFilter:
end_from: Optional[int] = None
end_to: Optional[int] = None
process_status: Optional[list[str]] = None
+ initiated_by_me: Optional[bool] = None
+ with_tasks_completed_by_me: Optional[bool] = None
+ with_tasks_completed_by_my_group: Optional[bool] = None
def to_dict(self) -> dict[str, str]:
"""To_dict."""
@@ -35,6 +38,16 @@ class ProcessInstanceReportFilter:
d["end_to"] = str(self.end_to)
if self.process_status is not None:
d["process_status"] = ",".join(self.process_status)
+ if self.initiated_by_me is not None:
+ d["initiated_by_me"] = str(self.initiated_by_me).lower()
+ if self.with_tasks_completed_by_me is not None:
+ d["with_tasks_completed_by_me"] = str(
+ self.with_tasks_completed_by_me
+ ).lower()
+ if self.with_tasks_completed_by_my_group is not None:
+ d["with_tasks_completed_by_my_group"] = str(
+ self.with_tasks_completed_by_my_group
+ ).lower()
return d
@@ -73,30 +86,35 @@ class ProcessInstanceReportService:
},
"system_report_instances_initiated_by_me": {
"columns": [
+ {"Header": "id", "accessor": "id"},
{
"Header": "process_model_identifier",
"accessor": "process_model_identifier",
},
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- {"Header": "id", "accessor": "id"},
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
{"Header": "status", "accessor": "status"},
],
+ "filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
},
"system_report_instances_with_tasks_completed_by_me": {
"columns": [
- {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
- {"Header": "status", "accessor": "status"},
{"Header": "id", "accessor": "id"},
{
"Header": "process_model_identifier",
"accessor": "process_model_identifier",
},
+ {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
+ {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
+ {"Header": "status", "accessor": "status"},
+ ],
+ "filter_by": [
+ {"field_name": "with_tasks_completed_by_me", "field_value": True}
],
},
"system_report_instances_with_tasks_completed_by_my_groups": {
"columns": [
+ {"Header": "id", "accessor": "id"},
{
"Header": "process_model_identifier",
"accessor": "process_model_identifier",
@@ -104,7 +122,12 @@ class ProcessInstanceReportService:
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
{"Header": "status", "accessor": "status"},
- {"Header": "id", "accessor": "id"},
+ ],
+ "filter_by": [
+ {
+ "field_name": "with_tasks_completed_by_my_group",
+ "field_value": True,
+ }
],
},
}
@@ -112,7 +135,7 @@ class ProcessInstanceReportService:
process_instance_report = ProcessInstanceReportModel(
identifier=report_identifier,
created_by_id=user.id,
- report_metadata=temp_system_metadata_map[report_identifier],
+ report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
)
return process_instance_report # type: ignore
@@ -138,6 +161,10 @@ class ProcessInstanceReportService:
"""Filter_from_metadata."""
filters = cls.filter_by_to_dict(process_instance_report)
+ def bool_value(key: str) -> Optional[bool]:
+ """Bool_value."""
+ return bool(filters[key]) if key in filters else None
+
def int_value(key: str) -> Optional[int]:
"""Int_value."""
return int(filters[key]) if key in filters else None
@@ -152,6 +179,11 @@ class ProcessInstanceReportService:
end_from = int_value("end_from")
end_to = int_value("end_to")
process_status = list_value("process_status")
+ initiated_by_me = bool_value("initiated_by_me")
+ with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me")
+ with_tasks_completed_by_my_group = bool_value(
+ "with_tasks_completed_by_my_group"
+ )
report_filter = ProcessInstanceReportFilter(
process_model_identifier,
@@ -160,6 +192,9 @@ class ProcessInstanceReportService:
end_from,
end_to,
process_status,
+ initiated_by_me,
+ with_tasks_completed_by_me,
+ with_tasks_completed_by_my_group,
)
return report_filter
@@ -174,6 +209,9 @@ class ProcessInstanceReportService:
end_from: Optional[int] = None,
end_to: Optional[int] = None,
process_status: Optional[str] = None,
+ initiated_by_me: Optional[bool] = None,
+ with_tasks_completed_by_me: Optional[bool] = None,
+ with_tasks_completed_by_my_group: Optional[bool] = None,
) -> ProcessInstanceReportFilter:
"""Filter_from_metadata_with_overrides."""
report_filter = cls.filter_from_metadata(process_instance_report)
@@ -190,5 +228,13 @@ class ProcessInstanceReportService:
report_filter.end_to = end_to
if process_status is not None:
report_filter.process_status = process_status.split(",")
+ if initiated_by_me is not None:
+ report_filter.initiated_by_me = initiated_by_me
+ if with_tasks_completed_by_me is not None:
+ report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me
+ if with_tasks_completed_by_my_group is not None:
+ report_filter.with_tasks_completed_by_my_group = (
+ with_tasks_completed_by_my_group
+ )
return report_filter
diff --git a/spiffworkflow-frontend/src/components/MyCompletedInstances.tsx b/spiffworkflow-frontend/src/components/MyCompletedInstances.tsx
index fe6652951..2d0fe26a7 100644
--- a/spiffworkflow-frontend/src/components/MyCompletedInstances.tsx
+++ b/spiffworkflow-frontend/src/components/MyCompletedInstances.tsx
@@ -8,6 +8,8 @@ export default function MyCompletedInstances() {
filtersEnabled={false}
paginationQueryParamPrefix={paginationQueryParamPrefix}
perPageOptions={[2, 5, 25]}
+ reportIdentifier="system_report_instances_initiated_by_me"
+ showReports={false}
/>
);
}
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 5b60f2178..20a2b2bb4 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -57,6 +57,7 @@ type OwnProps = {
paginationQueryParamPrefix?: string;
perPageOptions?: number[];
showReports?: boolean;
+ reportIdentifier?: string;
};
interface dateParameters {
@@ -69,6 +70,7 @@ export default function ProcessInstanceListTable({
paginationQueryParamPrefix,
perPageOptions,
showReports = true,
+ reportIdentifier,
}: OwnProps) {
const params = useParams();
const [searchParams] = useSearchParams();
@@ -173,9 +175,14 @@ export default function ProcessInstanceListTable({
queryParamString += `&user_filter=${userAppliedFilter}`;
}
- const reportIdentifier = searchParams.get('report_identifier');
- if (reportIdentifier) {
- queryParamString += `&report_identifier=${reportIdentifier}`;
+ let reportIdentifierToUse: any = reportIdentifier;
+
+ if (!reportIdentifierToUse) {
+ reportIdentifierToUse = searchParams.get('report_identifier');
+ }
+
+ if (reportIdentifierToUse) {
+ queryParamString += `&report_identifier=${reportIdentifierToUse}`;
}
Object.keys(dateParametersToAlwaysFilterBy).forEach(
@@ -273,6 +280,7 @@ export default function ProcessInstanceListTable({
paginationQueryParamPrefix,
processModelFullIdentifier,
perPageOptions,
+ reportIdentifier,
]);
// This sets the filter data using the saved reports returned from the initial instance_list query.
diff --git a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
index 237c21f3b..de5036776 100644
--- a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
+++ b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
@@ -1,5 +1,32 @@
-import MyCompletedInstances from '../components/MyCompletedInstances';
+import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
export default function CompletedInstances() {
- return