make sure we can properly display lanes.

This commit is contained in:
Dan 2023-04-18 19:36:31 -04:00
parent b6480fb9fe
commit 18f3b34c4a
6 changed files with 85 additions and 27 deletions

View File

@ -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"):

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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<any[]>([]);
@ -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 <img src="/interstitial/clock.png" alt="Waiting ...." />;
case 'COMPLETED':
return <img src="/interstitial/checkmark.png" alt="Completed" />;
case 'LOCKED':
return <img src="/interstitial/lock.png" alt="Locked, Waiting on someone else." />;
default:
return null;
}
};
const userMessage = (myTask: ProcessInstanceTask) => {
if (
!myTask.can_complete &&
['User Task', 'Manual Task'].includes(myTask.type)
) {
return (
<div>This task is assigned to another user or group to complete. </div>
);
}
return <InstructionsForEndUser task={myTask} />;
};
if (lastTask) {
return (
<>
@ -81,7 +107,7 @@ export default function ProcessInterstitial() {
[`Process Instance Id: ${lastTask.process_instance_id}`],
]}
/>
<h1 style={{display: 'inline-flex', alignItems: 'center'}}>
<h1 style={{ display: 'inline-flex', alignItems: 'center' }}>
{processStatusImage()}
{lastTask.process_model_display_name}: {lastTask.process_instance_id}
</h1>
@ -89,18 +115,20 @@ export default function ProcessInterstitial() {
<Grid condensed fullWidth>
<Column md={6} lg={8} sm={4}>
<table className="table table-bordered">
<tbody>
{data &&
data.map((d) => (
<tr key={d.id}>
<td><h3>{d.title}</h3></td>
<td>
<InstructionsForEndUser task={d} />
</td>
</tr>
))}
</tbody>
</table>
<tbody>
{data &&
data.map((d) => (
<tr key={d.id}>
<td>
<h3>{d.title}</h3>
</td>
<td>
<p>{userMessage(d)}</p>
</td>
</tr>
))}
</tbody>
</table>
</Column>
</Grid>
</>

View File

@ -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);