diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 764ba543f..84ada2341 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -699,6 +699,12 @@ paths: description: The unique id of an existing process instance. schema: type: integer + - name: process_identifier + in: query + required: false + description: The identifier of the process to use for the diagram. Useful for displaying the diagram for a call activity. + schema: + type: string get: tags: - Process Instances diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/spec_reference.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/spec_reference.py index 1e85f7229..ac2e3200b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/spec_reference.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/spec_reference.py @@ -8,6 +8,10 @@ from marshmallow import INCLUDE from sqlalchemy import UniqueConstraint +class SpecReferenceNotFoundError(Exception): + pass + + @dataclass() class SpecReference: """File Reference Information. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py index 52bb11715..019f4b648 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py @@ -118,6 +118,7 @@ class Task: form_schema: Union[str, None] = None, form_ui_schema: Union[str, None] = None, parent: Optional[str] = None, + call_activity_process_identifier: Optional[str] = None, ): """__init__.""" self.id = id @@ -129,6 +130,7 @@ class Task: self.documentation = documentation self.lane = lane self.parent = parent + self.call_activity_process_identifier = call_activity_process_identifier self.data = data if self.data is None: @@ -187,6 +189,7 @@ class Task: "form_schema": self.form_schema, "form_ui_schema": self.form_ui_schema, "parent": self.parent, + "call_activity_process_identifier": self.call_activity_process_identifier, } @classmethod 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 c29cf2148..3a73098fd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -65,7 +65,7 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema from spiffworkflow_backend.models.secret_model import SecretModel from spiffworkflow_backend.models.secret_model import SecretModelSchema -from spiffworkflow_backend.models.spec_reference import SpecReferenceCache +from spiffworkflow_backend.models.spec_reference import SpecReferenceCache, SpecReferenceNotFoundError from spiffworkflow_backend.models.spec_reference import SpecReferenceSchema from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsModel @@ -1073,25 +1073,38 @@ def process_instance_report_column_list() -> flask.wrappers.Response: def process_instance_show( - modified_process_model_identifier: str, process_instance_id: int + modified_process_model_identifier: str, process_instance_id: int, process_identifier: Optional[str] = None ) -> flask.wrappers.Response: """Create_process_instance.""" process_model_identifier = modified_process_model_identifier.replace(":", "/") process_instance = find_process_instance_by_id_or_raise(process_instance_id) current_version_control_revision = GitService.get_current_revision() - process_model = get_process_model(process_model_identifier) - if process_model.primary_file_name: + process_model_with_diagram = None + name_of_file_with_diagram = None + if process_identifier: + spec_reference = SpecReferenceCache.query.filter_by(identifier=process_identifier).first() + if spec_reference is None: + raise SpecReferenceNotFoundError(f"Could not find given process identifier in the cache: {process_identifier}") + + process_model_with_diagram = ProcessModelService.get_process_model(spec_reference.process_model_id) + name_of_file_with_diagram = spec_reference.file_name + else: + process_model_with_diagram = get_process_model(process_model_identifier) + if process_model_with_diagram.primary_file_name: + name_of_file_with_diagram = process_model_with_diagram.primary_file_name + + if process_model_with_diagram and name_of_file_with_diagram: if ( process_instance.bpmn_version_control_identifier == current_version_control_revision ): bpmn_xml_file_contents = SpecFileService.get_data( - process_model, process_model.primary_file_name + process_model_with_diagram, name_of_file_with_diagram ).decode("utf-8") else: bpmn_xml_file_contents = GitService.get_instance_file_contents_for_revision( - process_model, process_instance.bpmn_version_control_identifier + process_model_with_diagram, process_instance.bpmn_version_control_identifier, file_name=name_of_file_with_diagram ) process_instance.bpmn_xml_file_contents = bpmn_xml_file_contents diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py index f972b672b..519510e72 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py @@ -46,18 +46,21 @@ class GitService: @classmethod def get_instance_file_contents_for_revision( - cls, process_model: ProcessModelInfo, revision: str + cls, process_model: ProcessModelInfo, revision: str, file_name: Optional[str] = None ) -> str: """Get_instance_file_contents_for_revision.""" bpmn_spec_absolute_dir = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"] process_model_relative_path = FileSystemService.process_model_relative_path( process_model ) + file_name_to_use = file_name + if file_name_to_use is None: + file_name_to_use = process_model.primary_file_name with FileSystemService.cd(bpmn_spec_absolute_dir): shell_command = [ "git", "show", - f"{revision}:{process_model_relative_path}/{process_model.primary_file_name}", + f"{revision}:{process_model_relative_path}/{file_name_to_use}", ] return cls.run_shell_command_to_get_stdout(shell_command) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index 46bd252b9..38c71715d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -302,6 +302,11 @@ class ProcessInstanceService: else: lane = None + if hasattr(spiff_task.task_spec, "spec"): + call_activity_process_identifier = spiff_task.task_spec.spec + else: + call_activity_process_identifier = None + parent_id = None if spiff_task.parent: parent_id = spiff_task.parent.id @@ -319,6 +324,7 @@ class ProcessInstanceService: process_name=spiff_task.task_spec._wf_spec.description, properties=props, parent=parent_id, + call_activity_process_identifier=call_activity_process_identifier, ) return task diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index 9a0495d1d..a575968bd 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -1,6 +1,11 @@ import { useContext, useEffect, useState } from 'react'; import Editor from '@monaco-editor/react'; -import { useParams, useNavigate, Link } from 'react-router-dom'; +import { + useParams, + useNavigate, + Link, + useSearchParams, +} from 'react-router-dom'; import { TrashCan, StopOutline, @@ -40,6 +45,7 @@ import { usePermissionFetcher } from '../hooks/PermissionService'; export default function ProcessInstanceShow() { const navigate = useNavigate(); const params = useParams(); + const [searchParams] = useSearchParams(); const [processInstance, setProcessInstance] = useState(null); const [tasks, setTasks] = useState | null>(null); @@ -80,8 +86,13 @@ export default function ProcessInstanceShow() { const processTaskFailure = () => { setTasksCallHadError(true); }; + let queryParams = ''; + const processIdentifier = searchParams.get('process_identifier'); + if (processIdentifier) { + queryParams = `?process_identifier=${processIdentifier}`; + } HttpService.makeCallToBackend({ - path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}`, + path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}${queryParams}`, successCallback: setProcessInstance, }); let taskParams = '?all_tasks=true'; @@ -98,7 +109,14 @@ export default function ProcessInstanceShow() { setTasksCallHadError(true); } } - }, [params, modifiedProcessModelId, permissionsLoaded, ability, targetUris]); + }, [ + params, + modifiedProcessModelId, + permissionsLoaded, + ability, + targetUris, + searchParams, + ]); const deleteProcessInstance = () => { HttpService.makeCallToBackend({ @@ -179,11 +197,9 @@ export default function ProcessInstanceShow() { {label} @@ -471,19 +487,28 @@ export default function ProcessInstanceShow() { ); } + if (task.type === 'Call Activity') { + buttons.push( + + View Call Activity Diagram + + ); + } + if (canEditTaskData(task)) { if (editingTaskData) { buttons.push( - ); buttons.push(