diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/human_task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/human_task.py index 6a26e5c5..bca4cb81 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/human_task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/human_task.py @@ -4,6 +4,7 @@ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING +from flask import g from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship @@ -68,12 +69,19 @@ class HumanTaskModel(SpiffworkflowBaseDBModel): @classmethod def to_task(cls, task: HumanTaskModel) -> Task: """To_task.""" + can_complete = False + for user in task.human_task_users: + if user.user_id == g.user.id: + can_complete = True + break + new_task = Task( task.task_id, task.task_name, task.task_title, task.task_type, task.task_status, + can_complete, process_instance_id=task.process_instance_id, ) if hasattr(task, "process_model_display_name"): diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py index bc2fcff3..c9bf311b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py @@ -88,6 +88,7 @@ class Task: title: str, type: str, state: str, + can_complete: bool, lane: Union[str, None] = None, form: None = None, documentation: str = "", @@ -116,6 +117,7 @@ class Task: self.title = title self.type = type self.state = state + self.can_complete = can_complete self.form = form self.documentation = documentation self.lane = lane @@ -160,6 +162,7 @@ class Task: "type": self.type, "state": self.state, "lane": self.lane, + "can_complete": self.can_complete, "form": self.form, "documentation": self.documentation, "data": self.data, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index 01ccfdde..e0be60c3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -10,7 +10,7 @@ from typing import Tuple from urllib.parse import unquote import sentry_sdk -from flask import current_app +from flask import current_app, g from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import _BoundaryEventParent # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore @@ -26,7 +26,8 @@ from spiffworkflow_backend.models.process_instance_file_data import ( from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.task import Task from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.authorization_service import AuthorizationService, HumanTaskNotFoundError, \ + UserDoesNotHaveAccessToTaskError from spiffworkflow_backend.services.git_service import GitCommandError from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.process_instance_processor import ( @@ -422,6 +423,18 @@ class ProcessInstanceService: else: lane = None + # Check for a human task, and if it exists, check to see if the current user + # can complete it. + can_complete = False + try: + AuthorizationService.assert_user_can_complete_spiff_task(processor.process_instance_model.id, spiff_task, + g.user) + can_complete = True + except HumanTaskNotFoundError as e: + can_complete = False + except UserDoesNotHaveAccessToTaskError as ude: + can_complete = False + if hasattr(spiff_task.task_spec, "spec"): call_activity_process_identifier = spiff_task.task_spec.spec else: @@ -439,6 +452,7 @@ class ProcessInstanceService: spiff_task.task_spec.description, task_type, spiff_task.get_state_name(), + can_complete=can_complete, lane=lane, process_identifier=spiff_task.task_spec._wf_spec.name, process_instance_id=processor.process_instance_model.id, diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 802c48c7..7c46081c 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -41,9 +41,7 @@ export interface Task { id: number; guid: string; bpmn_identifier: string; - bpmn_name?: string; - bpmn_process_direct_parent_guid: string; bpmn_process_definition_identifier: string; data: any; @@ -64,7 +62,7 @@ export interface TaskIds { export interface ProcessInstanceTask { id: string; task_id: string; - + can_complete: boolean; calling_subprocess_task_id: string; created_at_in_seconds: number; current_user_is_potential_owner: number; diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index 3e7311a5..b36e638c 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -9,6 +9,7 @@ import { getBasicHeaders } from '../services/HttpService'; // @ts-ignore import InstructionsForEndUser from '../components/InstructionsForEndUser'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import { ProcessInstanceTask } from '../interfaces'; export default function ProcessInterstitial() { const [data, setData] = useState([]); @@ -39,7 +40,11 @@ export default function ProcessInterstitial() { useEffect(() => { // Added this seperate use effect so that the timer interval will be cleared if // we end up redirecting back to the TaskShow page. - if (lastTask && ['User Task', 'Manual Task'].includes(lastTask.type)) { + if ( + lastTask && + lastTask.can_complete && + ['User Task', 'Manual Task'].includes(lastTask.type) + ) { const timerId = setInterval(() => { navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`); }, 1000); @@ -52,6 +57,12 @@ export default function ProcessInterstitial() { if (status !== 'running') { setStatus(lastTask.state); } + if ( + !lastTask.can_complete && + ['User Task', 'Manual Task'].includes(lastTask.type) + ) { + setStatus('LOCKED'); + } console.log(`Status is : ${status}}`); console.log('last task is : ', lastTask); switch (status) { @@ -63,10 +74,25 @@ export default function ProcessInterstitial() { return Waiting ....; case 'COMPLETED': return Completed; + case 'LOCKED': + return Locked, Waiting on someone else.; default: return null; } }; + + const userMessage = (myTask: ProcessInstanceTask) => { + if ( + !myTask.can_complete && + ['User Task', 'Manual Task'].includes(myTask.type) + ) { + return ( +
This task is assigned to another user or group to complete.
+ ); + } + return ; + }; + if (lastTask) { return ( <> @@ -81,7 +107,7 @@ export default function ProcessInterstitial() { [`Process Instance Id: ${lastTask.process_instance_id}`], ]} /> -

+

{processStatusImage()} {lastTask.process_model_display_name}: {lastTask.process_instance_id}

@@ -89,18 +115,20 @@ export default function ProcessInterstitial() { - - {data && - data.map((d) => ( - - - - - ))} - -

{d.title}

- -
+ + {data && + data.map((d) => ( + + +

{d.title}

+ + +

{userMessage(d)}

+ + + ))} + +
diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 231f80f8..4afb2ec0 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -91,8 +91,6 @@ function TypeAheadWidget({ ); } - - class UnexpectedHumanTaskType extends Error { constructor(message: string) { super(message); @@ -121,11 +119,24 @@ export default function TaskShow() { // eslint-disable-next-line sonarjs/no-duplicate-string const supportedHumanTaskTypes = ['User Task', 'Manual Task']; + const navigateToInterstitial = (myTask: ProcessInstanceTask) => { + navigate( + `/process/${modifyProcessIdentifierForPathParam( + myTask.process_model_identifier + )}/${myTask.process_instance_id}/interstitial` + ); + }; + useEffect(() => { const processResult = (result: ProcessInstanceTask) => { setTask(result); setDisabled(false); + + if (!result.can_complete) { + navigateToInterstitial(result); + } + /* Disable call to load previous tasks -- do not display menu. const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam( result.process_model_identifier @@ -162,14 +173,10 @@ export default function TaskShow() { if (result.ok) { navigate(`/tasks`); } else if (result.process_instance_id) { - if (result.type in supportedHumanTaskTypes) { + if (result.can_complete) { navigate(`/tasks/${result.process_instance_id}/${result.id}`); } else { - navigate( - `/process/${modifyProcessIdentifierForPathParam( - result.process_model_identifier - )}/${result.process_instance_id}/interstitial` - ); + navigateToInterstitial(result); } } else { addError(result);