diff --git a/spiffworkflow-backend/bin/get_bpmn_json_for_process_instance b/spiffworkflow-backend/bin/get_bpmn_json_for_process_instance old mode 100755 new mode 100644 index 9b6b4c757..dbce01ecc --- a/spiffworkflow-backend/bin/get_bpmn_json_for_process_instance +++ b/spiffworkflow-backend/bin/get_bpmn_json_for_process_instance @@ -1,5 +1,4 @@ """Get the bpmn process json for a given process instance id and store it in /tmp.""" -#!/usr/bin/env python import os import sys @@ -18,15 +17,17 @@ def main(process_instance_id: str): id=process_instance_id ).first() + file_path = f"/tmp/{process_instance_id}_bpmn_json.json" if not process_instance: raise Exception( f"Could not find a process instance with id: {process_instance_id}" ) with open( - f"/tmp/{process_instance_id}_bpmn_json.json", "w", encoding="utf-8" + file_path, "w", encoding="utf-8" ) as f: f.write(process_instance.bpmn_json) + print(f"Saved to {file_path}") if len(sys.argv) < 2: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 12b40160e..55653fa14 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -174,7 +174,7 @@ paths: items: $ref: "#/components/schemas/ProcessModelCategory" post: - operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_add + operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_create summary: Add process group tags: - Process Groups @@ -1439,7 +1439,7 @@ paths: schema: type: string put: - operationId: spiffworkflow_backend.routes.process_api_blueprint.update_task_data + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_data_update summary: Update the task data for requested instance and task tags: - Process Instances @@ -1451,6 +1451,39 @@ paths: schema: $ref: "#/components/schemas/Workflow" + /process-data/{modified_process_model_identifier}/{process_instance_id}/{process_data_identifier}: + parameters: + - name: modified_process_model_identifier + in: path + required: true + description: The modified id of an existing process model + schema: + type: string + - name: process_instance_id + in: path + required: true + description: The unique id of an existing process instance. + schema: + type: integer + - name: process_data_identifier + in: path + required: true + description: The identifier of the process data. + schema: + type: string + get: + operationId: spiffworkflow_backend.routes.process_api_blueprint.process_data_show + summary: Fetch the process data value. + tags: + - Data Objects + responses: + "200": + description: Fetch succeeded. + content: + application/json: + schema: + $ref: "#/components/schemas/Workflow" + /service-tasks: get: tags: @@ -1689,7 +1722,7 @@ paths: schema: type: integer post: - operationId: spiffworkflow_backend.routes.process_api_blueprint.add_secret + operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_create summary: Create a secret for a key and value tags: - Secrets @@ -1739,7 +1772,7 @@ paths: schema: $ref: "#/components/schemas/Secret" delete: - operationId: spiffworkflow_backend.routes.process_api_blueprint.delete_secret + operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_delete summary: Delete an existing secret tags: - Secrets @@ -1751,7 +1784,7 @@ paths: "404": description: Secret does not exist put: - operationId: spiffworkflow_backend.routes.process_api_blueprint.update_secret + operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_update summary: Modify an existing secret tags: - Secrets 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 bfece39b8..374c90ac6 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -158,17 +158,12 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R return make_response(jsonify({"results": response_dict}), 200) -def modify_process_model_id(process_model_id: str) -> str: - """Modify_process_model_id.""" - return process_model_id.replace("/", ":") - - def un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str: """Un_modify_modified_process_model_id.""" return modified_process_model_identifier.replace(":", "/") -def process_group_add(body: dict) -> flask.wrappers.Response: +def process_group_create(body: dict) -> flask.wrappers.Response: """Add_process_group.""" process_group = ProcessGroup(**body) ProcessModelService.add_process_group(process_group) @@ -1354,7 +1349,6 @@ def process_instance_task_list_without_task_data_for_me( ) -> flask.wrappers.Response: """Process_instance_task_list_without_task_data_for_me.""" process_instance = _find_process_instance_for_me_or_raise(process_instance_id) - print(f"process_instance: {process_instance}") return process_instance_task_list( modified_process_model_identifier, process_instance, @@ -1540,6 +1534,30 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response return make_response(jsonify(task), 200) +def process_data_show( + process_instance_id: int, + process_data_identifier: str, + modified_process_model_identifier: str, +) -> flask.wrappers.Response: + """Process_data_show.""" + process_instance = find_process_instance_by_id_or_raise(process_instance_id) + processor = ProcessInstanceProcessor(process_instance) + all_process_data = processor.get_data() + process_data_value = None + if process_data_identifier in all_process_data: + process_data_value = all_process_data[process_data_identifier] + + return make_response( + jsonify( + { + "process_data_identifier": process_data_identifier, + "process_data_value": process_data_value, + } + ), + 200, + ) + + def task_submit( process_instance_id: int, task_id: str, @@ -1907,7 +1925,7 @@ def secret_list( return make_response(jsonify(response_json), 200) -def add_secret(body: Dict) -> Response: +def secret_create(body: Dict) -> Response: """Add secret.""" secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id) return Response( @@ -1917,20 +1935,20 @@ def add_secret(body: Dict) -> Response: ) -def update_secret(key: str, body: dict) -> Response: +def secret_update(key: str, body: dict) -> Response: """Update secret.""" SecretService().update_secret(key, body["value"], g.user.id) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") -def delete_secret(key: str) -> Response: +def secret_delete(key: str) -> Response: """Delete secret.""" current_user = UserService.current_user() SecretService.delete_secret(key, current_user.id) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") -def update_task_data( +def task_data_update( process_instance_id: str, modified_process_model_identifier: str, task_id: str, diff --git a/spiffworkflow-backend/tests/data/data_object_test/data_object.bpmn b/spiffworkflow-backend/tests/data/data_object_test/data_object.bpmn new file mode 100644 index 000000000..c112339e2 --- /dev/null +++ b/spiffworkflow-backend/tests/data/data_object_test/data_object.bpmn @@ -0,0 +1,75 @@ + + + + + Flow_0hnphp9 + + + + Flow_0hnphp9 + Flow_0amajxh + + DataObjectReference_10g8dit + + the_data_object_var = 'hey' + + + + Flow_1ifqo6o + + + + Flow_0amajxh + Flow_1ifqo6o + + + DataObjectReference_10g8dit + Property_0a8w16m + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index adc21c29f..273f6151f 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -2928,3 +2928,31 @@ class TestProcessApi(BaseTest): assert len(response.json["results"]) == 2 assert response.json["results"][1]["id"] == process_instance_one.id assert response.json["results"][0]["id"] == process_instance_two.id + + def test_process_data_show( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_process_data_show.""" + process_model = load_test_spec( + "test_group/data_object_test", + process_model_source_directory="data_object_test", + ) + process_instance_one = self.create_process_instance_from_process_model( + process_model + ) + processor = ProcessInstanceProcessor(process_instance_one) + processor.do_engine_steps(save=True) + assert process_instance_one.status == "user_input_required" + + response = client.get( + f"/v1.0/process-data/{self.modify_process_identifier_for_path_param(process_model.id)}/{process_instance_one.id}/the_data_object_var", + headers=self.logged_in_headers(with_super_admin_user), + ) + + assert response.status_code == 200 + assert response.json is not None + assert response.json["process_data_value"] == "hey" diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 7805249be..ca40940ca 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -5,6 +5,11 @@ export interface Secret { creator_user_id: string; } +export interface ProcessData { + process_data_identifier: string; + process_data_value: any; +} + export interface RecentProcessModel { processGroupIdentifier?: string; processModelIdentifier: string; diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index e4e1ffa23..38b26729c 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -41,6 +41,7 @@ import ErrorContext from '../contexts/ErrorContext'; import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { PermissionsToCheck, + ProcessData, ProcessInstance, ProcessInstanceTask, } from '../interfaces'; @@ -62,6 +63,8 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { const [tasksCallHadError, setTasksCallHadError] = useState(false); const [taskToDisplay, setTaskToDisplay] = useState(null); const [taskDataToDisplay, setTaskDataToDisplay] = useState(''); + const [processDataToDisplay, setProcessDataToDisplay] = + useState(null); const [editingTaskData, setEditingTaskData] = useState(false); const setErrorMessage = (useContext as any)(ErrorContext)[1]; @@ -78,15 +81,15 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { : targetUris.processInstanceTaskListForMePath; const permissionRequestData: PermissionsToCheck = { - [targetUris.messageInstanceListPath]: ['GET'], - [taskListPath]: ['GET'], - [targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'], - [targetUris.processInstanceActionPath]: ['DELETE'], - [targetUris.processInstanceLogListPath]: ['GET'], - [targetUris.processModelShowPath]: ['PUT'], [`${targetUris.processInstanceResumePath}`]: ['POST'], [`${targetUris.processInstanceSuspendPath}`]: ['POST'], [`${targetUris.processInstanceTerminatePath}`]: ['POST'], + [targetUris.messageInstanceListPath]: ['GET'], + [targetUris.processInstanceActionPath]: ['DELETE'], + [targetUris.processInstanceLogListPath]: ['GET'], + [targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'], + [targetUris.processModelShowPath]: ['PUT'], + [taskListPath]: ['GET'], }; const { ability, permissionsLoaded } = usePermissionFetcher( permissionRequestData @@ -411,16 +414,50 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { } }; + const handleProcessDataDisplayClose = () => { + setProcessDataToDisplay(null); + }; + + const processDataDisplayArea = () => { + if (processDataToDisplay) { + return ( + +

Data Object: {processDataToDisplay.process_data_identifier}

+
+

Value:

+
{JSON.stringify(processDataToDisplay.process_data_value)}
+
+ ); + } + return null; + }; + + const handleProcessDataShowResponse = (processData: ProcessData) => { + setProcessDataToDisplay(processData); + }; + const handleClickedDiagramTask = ( shapeElement: any, bpmnProcessIdentifiers: any ) => { - if (tasks) { - const matchingTask: any = tasks.find( - (task: any) => + if (shapeElement.type === 'bpmn:DataObjectReference') { + const dataObjectIdentifer = shapeElement.businessObject.dataObjectRef.id; + HttpService.makeCallToBackend({ + path: `/process-data/${params.process_model_id}/${params.process_instance_id}/${dataObjectIdentifer}`, + httpMethod: 'GET', + successCallback: handleProcessDataShowResponse, + }); + } else if (tasks) { + const matchingTask: any = tasks.find((task: any) => { + return ( task.name === shapeElement.id && bpmnProcessIdentifiers.includes(task.process_identifier) - ); + ); + }); if (matchingTask) { setTaskToDisplay(matchingTask); initializeTaskDataToDisplay(matchingTask); @@ -503,7 +540,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { // taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay }; HttpService.makeCallToBackend({ - path: `/task-data/${modifiedProcessModelId}/${params.process_instance_id}/${taskToUse.id}`, + path: `${targetUris.processInstanceTaskListDataPath}/${taskToUse.id}`, httpMethod: 'PUT', successCallback: saveTaskDataResult, failureCallback: saveTaskDataFailure, @@ -687,6 +724,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { {getInfoTag()}
{taskDataDisplayArea()} + {processDataDisplayArea()} {stepsElement()}