diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index c49eda58..0aa0fa7b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -218,8 +218,9 @@ def task_data_update( task_model, new_task_data_dict, "json_data_hash" ) if json_data_dict is not None: - json_data = JsonDataModel(**json_data_dict) - db.session.add(json_data) + TaskService.insert_or_update_json_data_records({json_data_dict['hash']: json_data_dict}) + # json_data = JsonDataModel(**json_data_dict) + # db.session.add(json_data) ProcessInstanceProcessor.add_event_to_process_instance( process_instance, ProcessInstanceEventType.task_data_edited.value, task_guid=task_guid ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 535a2be4..5791be7e 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1232,6 +1232,7 @@ class ProcessInstanceProcessor: def manual_complete_task(self, task_id: str, execute: bool) -> None: """Mark the task complete optionally executing it.""" spiff_tasks_updated = {} + start_in_seconds = time.time() spiff_task = self.bpmn_process_instance.get_task(UUID(task_id)) event_type = ProcessInstanceEventType.task_skipped.value if execute: @@ -1264,6 +1265,8 @@ class ProcessInstanceProcessor: spiff_task.workflow.last_task = spiff_task spiff_tasks_updated[spiff_task.id] = spiff_task + end_in_seconds = time.time() + if isinstance(spiff_task.task_spec, EndEvent): for task in self.bpmn_process_instance.get_tasks(TaskState.DEFINITE_MASK, workflow=spiff_task.workflow): task.complete() @@ -1300,6 +1303,11 @@ class ProcessInstanceProcessor: if bpmn_process_json_data is not None: new_json_data_dicts[bpmn_process_json_data["hash"]] = bpmn_process_json_data + # spiff_task should be the main task we are completing and only it should get the timestamps + if task_model.guid == str(spiff_task.id): + task_model.start_in_seconds = start_in_seconds + task_model.end_in_seconds = end_in_seconds + new_task_models[task_model.guid] = task_model db.session.bulk_save_objects(new_task_models.values()) TaskService.insert_or_update_json_data_records(new_json_data_dicts) diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index 308f4bd1..26231282 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -68,6 +68,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { const [tasks, setTasks] = useState(null); const [tasksCallHadError, setTasksCallHadError] = useState(false); const [taskToDisplay, setTaskToDisplay] = useState(null); + const [taskToTimeTravelTo, setTaskToTimeTravelTo] = useState( + null + ); const [taskDataToDisplay, setTaskDataToDisplay] = useState(''); const [showTaskDataLoading, setShowTaskDataLoading] = useState(false); @@ -127,45 +130,58 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { } useEffect(() => { - if (permissionsLoaded) { - const processTaskFailure = () => { - setTasksCallHadError(true); - }; - let queryParams = ''; - const processIdentifier = searchParams.get('process_identifier'); - if (processIdentifier) { - queryParams = `?process_identifier=${processIdentifier}`; - } - let apiPath = '/process-instances/for-me'; - if (variant === 'all') { - apiPath = '/process-instances'; - } - HttpService.makeCallToBackend({ - path: `${apiPath}/${modifiedProcessModelId}/${params.process_instance_id}${queryParams}`, - successCallback: setProcessInstance, - }); - let taskParams = '?most_recent_tasks_only=true'; - if (typeof params.to_task_guid !== 'undefined') { - taskParams = `${taskParams}&to_task_guid=${params.to_task_guid}`; - } - const bpmnProcessGuid = searchParams.get('bpmn_process_guid'); - if (bpmnProcessGuid) { - taskParams = `${taskParams}&bpmn_process_guid=${bpmnProcessGuid}`; - } - let taskPath = ''; - if (ability.can('GET', taskListPath)) { - taskPath = `${taskListPath}${taskParams}`; - } - if (taskPath) { - HttpService.makeCallToBackend({ - path: taskPath, - successCallback: setTasks, - failureCallback: processTaskFailure, - }); - } else { - setTasksCallHadError(true); - } + if (!permissionsLoaded) { + return undefined; } + const processTaskFailure = () => { + setTasksCallHadError(true); + }; + const processTasksSuccess = (results: Task[]) => { + if (params.to_task_guid) { + const matchingTask = results.find( + (task: Task) => task.guid === params.to_task_guid + ); + if (matchingTask) { + setTaskToTimeTravelTo(matchingTask); + } + } + setTasks(results); + }; + let queryParams = ''; + const processIdentifier = searchParams.get('process_identifier'); + if (processIdentifier) { + queryParams = `?process_identifier=${processIdentifier}`; + } + let apiPath = '/process-instances/for-me'; + if (variant === 'all') { + apiPath = '/process-instances'; + } + HttpService.makeCallToBackend({ + path: `${apiPath}/${modifiedProcessModelId}/${params.process_instance_id}${queryParams}`, + successCallback: setProcessInstance, + }); + let taskParams = '?most_recent_tasks_only=true'; + if (typeof params.to_task_guid !== 'undefined') { + taskParams = `${taskParams}&to_task_guid=${params.to_task_guid}`; + } + const bpmnProcessGuid = searchParams.get('bpmn_process_guid'); + if (bpmnProcessGuid) { + taskParams = `${taskParams}&bpmn_process_guid=${bpmnProcessGuid}`; + } + let taskPath = ''; + if (ability.can('GET', taskListPath)) { + taskPath = `${taskListPath}${taskParams}`; + } + if (taskPath) { + HttpService.makeCallToBackend({ + path: taskPath, + successCallback: processTasksSuccess, + failureCallback: processTaskFailure, + }); + } else { + setTasksCallHadError(true); + } + return undefined; }, [ targetUris, params, @@ -231,14 +247,17 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { }; const currentToTaskGuid = () => { - return params.to_task_guid; + if (taskToTimeTravelTo) { + return taskToTimeTravelTo.guid; + } + return null; }; - // right now this just assume if to_task_guid was passed in then + // right now this just assume if taskToTimeTravelTo was passed in then // this cannot be the active task. // we may need a better way to figure this out. const showingActiveTask = () => { - return !params.to_task_guid; + return !taskToTimeTravelTo; }; const completionViewLink = (label: any, taskGuid: string) => { @@ -983,7 +1002,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
{completionViewLink( - 'View state at task completion', + 'View process instance at the time when this task was active.', taskToUse.guid )} @@ -1035,21 +1054,31 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { }; const viewMostRecentStateComponent = () => { - if (showingActiveTask()) { + if (!taskToTimeTravelTo) { return null; } - + const title = `${taskToTimeTravelTo.id}: ${taskToTimeTravelTo.guid}: ${taskToTimeTravelTo.bpmn_identifier}`; return ( <> - - - View at most recent state - + +

+ Viewing process instance at the time when{' '} + + + {taskToTimeTravelTo.bpmn_name || + taskToTimeTravelTo.bpmn_identifier} + + {' '} + was active.{' '} + + View current process instance state. + +