added ability to view data objects from the process instance show page w/ burnettk

This commit is contained in:
jasquat 2022-12-27 11:45:42 -05:00
parent f6c5c005d9
commit 866346f47b
7 changed files with 227 additions and 29 deletions

View File

@ -1,5 +1,4 @@
"""Get the bpmn process json for a given process instance id and store it in /tmp.""" """Get the bpmn process json for a given process instance id and store it in /tmp."""
#!/usr/bin/env python
import os import os
import sys import sys
@ -18,15 +17,17 @@ def main(process_instance_id: str):
id=process_instance_id id=process_instance_id
).first() ).first()
file_path = f"/tmp/{process_instance_id}_bpmn_json.json"
if not process_instance: if not process_instance:
raise Exception( raise Exception(
f"Could not find a process instance with id: {process_instance_id}" f"Could not find a process instance with id: {process_instance_id}"
) )
with open( with open(
f"/tmp/{process_instance_id}_bpmn_json.json", "w", encoding="utf-8" file_path, "w", encoding="utf-8"
) as f: ) as f:
f.write(process_instance.bpmn_json) f.write(process_instance.bpmn_json)
print(f"Saved to {file_path}")
if len(sys.argv) < 2: if len(sys.argv) < 2:

View File

@ -174,7 +174,7 @@ paths:
items: items:
$ref: "#/components/schemas/ProcessModelCategory" $ref: "#/components/schemas/ProcessModelCategory"
post: 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 summary: Add process group
tags: tags:
- Process Groups - Process Groups
@ -1439,7 +1439,7 @@ paths:
schema: schema:
type: string type: string
put: 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 summary: Update the task data for requested instance and task
tags: tags:
- Process Instances - Process Instances
@ -1451,6 +1451,39 @@ paths:
schema: schema:
$ref: "#/components/schemas/Workflow" $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: /service-tasks:
get: get:
tags: tags:
@ -1689,7 +1722,7 @@ paths:
schema: schema:
type: integer type: integer
post: 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 summary: Create a secret for a key and value
tags: tags:
- Secrets - Secrets
@ -1739,7 +1772,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/Secret" $ref: "#/components/schemas/Secret"
delete: delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.delete_secret operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_delete
summary: Delete an existing secret summary: Delete an existing secret
tags: tags:
- Secrets - Secrets
@ -1751,7 +1784,7 @@ paths:
"404": "404":
description: Secret does not exist description: Secret does not exist
put: put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_secret operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_update
summary: Modify an existing secret summary: Modify an existing secret
tags: tags:
- Secrets - Secrets

View File

@ -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) 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: def un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str:
"""Un_modify_modified_process_model_id.""" """Un_modify_modified_process_model_id."""
return modified_process_model_identifier.replace(":", "/") 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.""" """Add_process_group."""
process_group = ProcessGroup(**body) process_group = ProcessGroup(**body)
ProcessModelService.add_process_group(process_group) ProcessModelService.add_process_group(process_group)
@ -1354,7 +1349,6 @@ def process_instance_task_list_without_task_data_for_me(
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_instance_task_list_without_task_data_for_me.""" """Process_instance_task_list_without_task_data_for_me."""
process_instance = _find_process_instance_for_me_or_raise(process_instance_id) process_instance = _find_process_instance_for_me_or_raise(process_instance_id)
print(f"process_instance: {process_instance}")
return process_instance_task_list( return process_instance_task_list(
modified_process_model_identifier, modified_process_model_identifier,
process_instance, 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) 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( def task_submit(
process_instance_id: int, process_instance_id: int,
task_id: str, task_id: str,
@ -1907,7 +1925,7 @@ def secret_list(
return make_response(jsonify(response_json), 200) return make_response(jsonify(response_json), 200)
def add_secret(body: Dict) -> Response: def secret_create(body: Dict) -> Response:
"""Add secret.""" """Add secret."""
secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id) secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id)
return Response( 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.""" """Update secret."""
SecretService().update_secret(key, body["value"], g.user.id) SecretService().update_secret(key, body["value"], g.user.id)
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") 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.""" """Delete secret."""
current_user = UserService.current_user() current_user = UserService.current_user()
SecretService.delete_secret(key, current_user.id) SecretService.delete_secret(key, current_user.id)
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def update_task_data( def task_data_update(
process_instance_id: str, process_instance_id: str,
modified_process_model_identifier: str, modified_process_model_identifier: str,
task_id: str, task_id: str,

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_hjecbuk" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0hnphp9</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0hnphp9" sourceRef="StartEvent_1" targetRef="Activity_16lbvwu" />
<bpmn:scriptTask id="Activity_16lbvwu">
<bpmn:incoming>Flow_0hnphp9</bpmn:incoming>
<bpmn:outgoing>Flow_0amajxh</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_15x55ya">
<bpmn:targetRef>DataObjectReference_10g8dit</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:script>the_data_object_var = 'hey'</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0amajxh" sourceRef="Activity_16lbvwu" targetRef="manual_task" />
<bpmn:endEvent id="Event_0ik0i72">
<bpmn:incoming>Flow_1ifqo6o</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1ifqo6o" sourceRef="manual_task" targetRef="Event_0ik0i72" />
<bpmn:manualTask id="manual_task">
<bpmn:incoming>Flow_0amajxh</bpmn:incoming>
<bpmn:outgoing>Flow_1ifqo6o</bpmn:outgoing>
<bpmn:property id="Property_0a8w16m" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataInputAssociation_0iqtpwy">
<bpmn:sourceRef>DataObjectReference_10g8dit</bpmn:sourceRef>
<bpmn:targetRef>Property_0a8w16m</bpmn:targetRef>
</bpmn:dataInputAssociation>
</bpmn:manualTask>
<bpmn:dataObjectReference id="DataObjectReference_10g8dit" name="The Data Object Var" dataObjectRef="the_data_object_var" />
<bpmn:dataObject id="the_data_object_var" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_hjecbuk">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0wqvy5h_di" bpmnElement="Activity_16lbvwu">
<dc:Bounds x="290" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ik0i72_di" bpmnElement="Event_0ik0i72">
<dc:Bounds x="652" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0keslpp_di" bpmnElement="manual_task">
<dc:Bounds x="470" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0hnphp9_di" bpmnElement="Flow_0hnphp9">
<di:waypoint x="215" y="177" />
<di:waypoint x="290" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0amajxh_di" bpmnElement="Flow_0amajxh">
<di:waypoint x="390" y="177" />
<di:waypoint x="470" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ifqo6o_di" bpmnElement="Flow_1ifqo6o">
<di:waypoint x="570" y="177" />
<di:waypoint x="652" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="DataObjectReference_10g8dit_di" bpmnElement="DataObjectReference_10g8dit">
<dc:Bounds x="412" y="275" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="390" y="332" width="81" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="DataInputAssociation_0iqtpwy_di" bpmnElement="DataInputAssociation_0iqtpwy">
<di:waypoint x="448" y="275" />
<di:waypoint x="491" y="217" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_15x55ya_di" bpmnElement="DataOutputAssociation_15x55ya">
<di:waypoint x="371" y="217" />
<di:waypoint x="416" y="275" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -2928,3 +2928,31 @@ class TestProcessApi(BaseTest):
assert len(response.json["results"]) == 2 assert len(response.json["results"]) == 2
assert response.json["results"][1]["id"] == process_instance_one.id assert response.json["results"][1]["id"] == process_instance_one.id
assert response.json["results"][0]["id"] == process_instance_two.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"

View File

@ -5,6 +5,11 @@ export interface Secret {
creator_user_id: string; creator_user_id: string;
} }
export interface ProcessData {
process_data_identifier: string;
process_data_value: any;
}
export interface RecentProcessModel { export interface RecentProcessModel {
processGroupIdentifier?: string; processGroupIdentifier?: string;
processModelIdentifier: string; processModelIdentifier: string;

View File

@ -41,6 +41,7 @@ import ErrorContext from '../contexts/ErrorContext';
import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import { import {
PermissionsToCheck, PermissionsToCheck,
ProcessData,
ProcessInstance, ProcessInstance,
ProcessInstanceTask, ProcessInstanceTask,
} from '../interfaces'; } from '../interfaces';
@ -62,6 +63,8 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false); const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false);
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null); const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>(''); const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
const [processDataToDisplay, setProcessDataToDisplay] =
useState<ProcessData | null>(null);
const [editingTaskData, setEditingTaskData] = useState<boolean>(false); const [editingTaskData, setEditingTaskData] = useState<boolean>(false);
const setErrorMessage = (useContext as any)(ErrorContext)[1]; const setErrorMessage = (useContext as any)(ErrorContext)[1];
@ -78,15 +81,15 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
: targetUris.processInstanceTaskListForMePath; : targetUris.processInstanceTaskListForMePath;
const permissionRequestData: PermissionsToCheck = { 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.processInstanceResumePath}`]: ['POST'],
[`${targetUris.processInstanceSuspendPath}`]: ['POST'], [`${targetUris.processInstanceSuspendPath}`]: ['POST'],
[`${targetUris.processInstanceTerminatePath}`]: ['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( const { ability, permissionsLoaded } = usePermissionFetcher(
permissionRequestData permissionRequestData
@ -411,16 +414,50 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
} }
}; };
const handleProcessDataDisplayClose = () => {
setProcessDataToDisplay(null);
};
const processDataDisplayArea = () => {
if (processDataToDisplay) {
return (
<Modal
open={!!processDataToDisplay}
passiveModal
onRequestClose={handleProcessDataDisplayClose}
>
<h2>Data Object: {processDataToDisplay.process_data_identifier}</h2>
<br />
<p>Value:</p>
<pre>{JSON.stringify(processDataToDisplay.process_data_value)}</pre>
</Modal>
);
}
return null;
};
const handleProcessDataShowResponse = (processData: ProcessData) => {
setProcessDataToDisplay(processData);
};
const handleClickedDiagramTask = ( const handleClickedDiagramTask = (
shapeElement: any, shapeElement: any,
bpmnProcessIdentifiers: any bpmnProcessIdentifiers: any
) => { ) => {
if (tasks) { if (shapeElement.type === 'bpmn:DataObjectReference') {
const matchingTask: any = tasks.find( const dataObjectIdentifer = shapeElement.businessObject.dataObjectRef.id;
(task: any) => 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 && task.name === shapeElement.id &&
bpmnProcessIdentifiers.includes(task.process_identifier) bpmnProcessIdentifiers.includes(task.process_identifier)
); );
});
if (matchingTask) { if (matchingTask) {
setTaskToDisplay(matchingTask); setTaskToDisplay(matchingTask);
initializeTaskDataToDisplay(matchingTask); initializeTaskDataToDisplay(matchingTask);
@ -503,7 +540,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
// taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute // taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay }; const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/task-data/${modifiedProcessModelId}/${params.process_instance_id}/${taskToUse.id}`, path: `${targetUris.processInstanceTaskListDataPath}/${taskToUse.id}`,
httpMethod: 'PUT', httpMethod: 'PUT',
successCallback: saveTaskDataResult, successCallback: saveTaskDataResult,
failureCallback: saveTaskDataFailure, failureCallback: saveTaskDataFailure,
@ -687,6 +724,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
{getInfoTag()} {getInfoTag()}
<br /> <br />
{taskDataDisplayArea()} {taskDataDisplayArea()}
{processDataDisplayArea()}
{stepsElement()} {stepsElement()}
<br /> <br />
<ReactDiagramEditor <ReactDiagramEditor