Merge pull request #243 from sartography/feature/interstitial_do_not_update_pi_status
Feature/interstitial do not update pi status
This commit is contained in:
commit
ca8ddc55d7
|
@ -172,7 +172,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def active_statuses(cls) -> list[str]:
|
def active_statuses(cls) -> list[str]:
|
||||||
return ["user_input_required", "waiting"]
|
return ["not_started", "user_input_required", "waiting"]
|
||||||
|
|
||||||
|
|
||||||
class ProcessInstanceModelSchema(Schema):
|
class ProcessInstanceModelSchema(Schema):
|
||||||
|
|
|
@ -45,7 +45,8 @@ from spiffworkflow_backend.models.process_instance import (
|
||||||
)
|
)
|
||||||
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
|
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
|
||||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||||
from spiffworkflow_backend.models.task import TaskModel # noqa: F401
|
from spiffworkflow_backend.models.task import Task
|
||||||
|
from spiffworkflow_backend.models.task import TaskModel
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.routes.process_api_blueprint import (
|
from spiffworkflow_backend.routes.process_api_blueprint import (
|
||||||
_find_principal_or_raise,
|
_find_principal_or_raise,
|
||||||
|
@ -400,6 +401,11 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
extensions = TaskService.get_extensions_from_task_model(task_model)
|
extensions = TaskService.get_extensions_from_task_model(task_model)
|
||||||
return _render_instructions_for_end_user(task_model, extensions)
|
return _render_instructions_for_end_user(task_model, extensions)
|
||||||
|
|
||||||
|
def render_data(return_type: str, entity: Union[ApiError, Task, ProcessInstanceModel]) -> str:
|
||||||
|
return_hash: dict = {"type": return_type}
|
||||||
|
return_hash[return_type] = entity
|
||||||
|
return f"data: {current_app.json.dumps(return_hash)} \n\n"
|
||||||
|
|
||||||
tasks = get_reportable_tasks()
|
tasks = get_reportable_tasks()
|
||||||
while True:
|
while True:
|
||||||
for spiff_task in tasks:
|
for spiff_task in tasks:
|
||||||
|
@ -411,14 +417,18 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
yield render_data("error", api_error)
|
||||||
raise e
|
raise e
|
||||||
if instructions and spiff_task.id not in reported_ids:
|
if instructions and spiff_task.id not in reported_ids:
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
task.properties = {"instructionsForEndUser": instructions}
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
yield render_data("task", task)
|
||||||
reported_ids.append(spiff_task.id)
|
reported_ids.append(spiff_task.id)
|
||||||
if spiff_task.state == TaskState.READY:
|
if spiff_task.state == TaskState.READY:
|
||||||
|
# do not do any processing if the instance is not currently active
|
||||||
|
if process_instance.status not in ProcessInstanceModel.active_statuses():
|
||||||
|
yield render_data("unrunnable_instance", process_instance)
|
||||||
|
break
|
||||||
try:
|
try:
|
||||||
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
||||||
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
||||||
|
@ -427,7 +437,7 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
api_error = ApiError.from_workflow_exception(
|
api_error = ApiError.from_workflow_exception(
|
||||||
"engine_steps_error", "Failed complete an automated task.", exp=wfe
|
"engine_steps_error", "Failed complete an automated task.", exp=wfe
|
||||||
)
|
)
|
||||||
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
yield render_data("error", api_error)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
api_error = ApiError(
|
api_error = ApiError(
|
||||||
|
@ -435,7 +445,7 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
yield render_data("error", api_error)
|
||||||
return
|
return
|
||||||
processor.refresh_waiting_tasks()
|
processor.refresh_waiting_tasks()
|
||||||
ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance)
|
ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance)
|
||||||
|
@ -454,10 +464,10 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
yield render_data("error", api_error)
|
||||||
raise e
|
raise e
|
||||||
task.properties = {"instructionsForEndUser": instructions}
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
yield render_data("task", task)
|
||||||
|
|
||||||
|
|
||||||
def get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
def get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
||||||
|
@ -472,8 +482,9 @@ def get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _dequeued_interstitial_stream(process_instance_id: int) -> Generator[str, Optional[str], None]:
|
def _dequeued_interstitial_stream(process_instance_id: int) -> Generator[Optional[str], Optional[str], None]:
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
|
||||||
with ProcessInstanceQueueService.dequeued(process_instance):
|
with ProcessInstanceQueueService.dequeued(process_instance):
|
||||||
yield from _interstitial_stream(process_instance)
|
yield from _interstitial_stream(process_instance)
|
||||||
|
|
||||||
|
|
|
@ -1688,14 +1688,15 @@ class TestProcessApi(BaseTest):
|
||||||
# The second script task should produce rendered jinja text
|
# The second script task should produce rendered jinja text
|
||||||
# The Manual Task should then return a message as well.
|
# The Manual Task should then return a message as well.
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
assert json_results[0]["state"] == "READY"
|
# import pdb; pdb.set_trace()
|
||||||
assert json_results[0]["title"] == "Script Task #2"
|
assert json_results[0]["task"]["state"] == "READY"
|
||||||
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am Script Task 2"
|
assert json_results[0]["task"]["title"] == "Script Task #2"
|
||||||
assert json_results[1]["state"] == "READY"
|
assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am Script Task 2"
|
||||||
assert json_results[1]["title"] == "Manual Task"
|
assert json_results[1]["task"]["state"] == "READY"
|
||||||
|
assert json_results[1]["task"]["title"] == "Manual Task"
|
||||||
|
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/v1.0/tasks/{process_instance_id}/{json_results[1]['id']}",
|
f"/v1.0/tasks/{process_instance_id}/{json_results[1]['task']['id']}",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1705,14 +1706,14 @@ class TestProcessApi(BaseTest):
|
||||||
results = list(_dequeued_interstitial_stream(process_instance_id))
|
results = list(_dequeued_interstitial_stream(process_instance_id))
|
||||||
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
|
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert json_results[0]["state"] == "READY"
|
assert json_results[0]["task"]["state"] == "READY"
|
||||||
assert json_results[0]["can_complete"] is False
|
assert json_results[0]["task"]["can_complete"] is False
|
||||||
assert json_results[0]["title"] == "Please Approve"
|
assert json_results[0]["task"]["title"] == "Please Approve"
|
||||||
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane"
|
assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane"
|
||||||
|
|
||||||
# Complete task as the finance user.
|
# Complete task as the finance user.
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/v1.0/tasks/{process_instance_id}/{json_results[0]['id']}",
|
f"/v1.0/tasks/{process_instance_id}/{json_results[0]['task']['id']}",
|
||||||
headers=self.logged_in_headers(finance_user),
|
headers=self.logged_in_headers(finance_user),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1722,8 +1723,8 @@ class TestProcessApi(BaseTest):
|
||||||
results = list(_dequeued_interstitial_stream(process_instance_id))
|
results = list(_dequeued_interstitial_stream(process_instance_id))
|
||||||
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
|
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
|
||||||
assert len(json_results) == 1
|
assert len(json_results) == 1
|
||||||
assert json_results[0]["state"] == "COMPLETED"
|
assert json_results[0]["task"]["state"] == "COMPLETED"
|
||||||
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am the end task"
|
assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am the end task"
|
||||||
|
|
||||||
def test_process_instance_list_with_default_list(
|
def test_process_instance_list_with_default_list(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -351,3 +351,15 @@ export interface ProcessModelCaller {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserGroup {}
|
export interface UserGroup {}
|
||||||
|
|
||||||
|
type InterstitialPageResponseType =
|
||||||
|
| 'task_update'
|
||||||
|
| 'error'
|
||||||
|
| 'unrunnable_instance';
|
||||||
|
|
||||||
|
export interface InterstitialPageResponse {
|
||||||
|
type: InterstitialPageResponseType;
|
||||||
|
error?: any;
|
||||||
|
task?: ProcessInstanceTask;
|
||||||
|
process_instance?: ProcessInstance;
|
||||||
|
}
|
||||||
|
|
|
@ -9,12 +9,14 @@ import { getBasicHeaders } from '../services/HttpService';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import { ProcessInstanceTask } from '../interfaces';
|
import { ProcessInstance, ProcessInstanceTask } from '../interfaces';
|
||||||
import useAPIError from '../hooks/UseApiError';
|
import useAPIError from '../hooks/UseApiError';
|
||||||
|
|
||||||
export default function ProcessInterstitial() {
|
export default function ProcessInterstitial() {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [lastTask, setLastTask] = useState<any>(null);
|
const [lastTask, setLastTask] = useState<any>(null);
|
||||||
|
const [processInstance, setProcessInstance] =
|
||||||
|
useState<ProcessInstance | null>(null);
|
||||||
const [state, setState] = useState<string>('RUNNING');
|
const [state, setState] = useState<string>('RUNNING');
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -32,11 +34,13 @@ export default function ProcessInterstitial() {
|
||||||
headers: getBasicHeaders(),
|
headers: getBasicHeaders(),
|
||||||
onmessage(ev) {
|
onmessage(ev) {
|
||||||
const retValue = JSON.parse(ev.data);
|
const retValue = JSON.parse(ev.data);
|
||||||
if ('error_code' in retValue) {
|
if (retValue.type === 'error') {
|
||||||
addError(retValue);
|
addError(retValue.error);
|
||||||
} else {
|
} else if (retValue.type === 'task') {
|
||||||
setData((prevData) => [retValue, ...prevData]);
|
setData((prevData) => [retValue.task, ...prevData]);
|
||||||
setLastTask(retValue);
|
setLastTask(retValue.task);
|
||||||
|
} else if (retValue.type === 'unrunnable_instance') {
|
||||||
|
setProcessInstance(retValue.unrunnable_instance);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onclose() {
|
onclose() {
|
||||||
|
@ -49,9 +53,14 @@ export default function ProcessInterstitial() {
|
||||||
|
|
||||||
const shouldRedirect = useCallback(
|
const shouldRedirect = useCallback(
|
||||||
(myTask: ProcessInstanceTask): boolean => {
|
(myTask: ProcessInstanceTask): boolean => {
|
||||||
return myTask && myTask.can_complete && userTasks.includes(myTask.type);
|
return (
|
||||||
|
!processInstance &&
|
||||||
|
myTask &&
|
||||||
|
myTask.can_complete &&
|
||||||
|
userTasks.includes(myTask.type)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[userTasks]
|
[userTasks, processInstance]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -68,6 +77,9 @@ export default function ProcessInterstitial() {
|
||||||
}, [lastTask, navigate, userTasks, shouldRedirect]);
|
}, [lastTask, navigate, userTasks, shouldRedirect]);
|
||||||
|
|
||||||
const getStatus = (): string => {
|
const getStatus = (): string => {
|
||||||
|
if (processInstance) {
|
||||||
|
return 'LOCKED';
|
||||||
|
}
|
||||||
if (!lastTask.can_complete && userTasks.includes(lastTask.type)) {
|
if (!lastTask.can_complete && userTasks.includes(lastTask.type)) {
|
||||||
return 'LOCKED';
|
return 'LOCKED';
|
||||||
}
|
}
|
||||||
|
@ -153,12 +165,15 @@ export default function ProcessInterstitial() {
|
||||||
return <div>{myTask.error_message}</div>;
|
return <div>{myTask.error_message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let message =
|
||||||
|
'There are no additional instructions or information for this task.';
|
||||||
|
if (processInstance && processInstance.status !== 'completed') {
|
||||||
|
message = `The tasks cannot be completed on this instance because its status is "${processInstance.status}".`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InstructionsForEndUser
|
<InstructionsForEndUser task={myTask} defaultMessage={message} />
|
||||||
task={myTask}
|
|
||||||
defaultMessage="There are no additional instructions or information for this task."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue