From 39f9dcba4d497b121ef7f24e58e735b62b0e03d4 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 7 Nov 2022 14:35:45 -0500 Subject: [PATCH] Squashed 'spiffworkflow-backend/' changes from c354b846c..b8924100d b8924100d Merge pull request #165 from sartography/feature/more_launch_buttons_and_dropdowns eae9d4e37 Merge remote-tracking branch 'origin/main' into feature/more_launch_buttons_and_dropdowns 704ce5709 pyl is passing 98e1776ed When returning the list of files in a ProcessModel, include all the ways they can be referenced, for instance, json files, can be referened by file name, bpmn files can be referened by one more process ids, and DMN's files can be referenced by one or more decision ids. This information is now included in the reference. git-subtree-dir: spiffworkflow-backend git-subtree-split: b8924100d01276e3f282dbe939df74ebabdad625 --- src/spiffworkflow_backend/models/file.py | 56 ++++++++++--------- .../routes/process_api_blueprint.py | 5 ++ .../services/spec_file_service.py | 37 ++++++++++++ tests/data/call_activity_nested/schema.json | 1 + tests/spiffworkflow_backend/unit/test_file.py | 1 - .../unit/test_spec_file_service.py | 44 +++++++++++++++ 6 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 tests/data/call_activity_nested/schema.json diff --git a/src/spiffworkflow_backend/models/file.py b/src/spiffworkflow_backend/models/file.py index 0c844766..02ad5fc1 100644 --- a/src/spiffworkflow_backend/models/file.py +++ b/src/spiffworkflow_backend/models/file.py @@ -4,6 +4,7 @@ from dataclasses import field from datetime import datetime from typing import Optional +import marshmallow from marshmallow import INCLUDE from marshmallow import Schema @@ -61,6 +62,20 @@ CONTENT_TYPES = { } +@dataclass() +class FileReference: + """File Reference Information. + + Includes items such as the process id and name for a BPMN, + or the Decision id and Decision name for a DMN file. There may be more than + one reference that points to a particular file. + """ + + id: str + name: str + type: str # can be 'process', 'decision', or just 'file' + + @dataclass(order=True) class File: """File.""" @@ -70,17 +85,12 @@ class File: content_type: str name: str type: str - document: dict last_modified: datetime size: int - process_instance_id: Optional[int] = None - irb_doc_code: Optional[str] = None - data_store: Optional[dict] = field(default_factory=dict) - user_uid: Optional[str] = None + references: Optional[list[FileReference]] = None file_contents: Optional[bytes] = None process_model_id: Optional[str] = None process_group_id: Optional[str] = None - archived: bool = False def __post_init__(self) -> None: """__post_init__.""" @@ -100,7 +110,6 @@ class File: name=file_name, content_type=content_type, type=file_type.value, - document={}, last_modified=last_modified, size=file_size, ) @@ -118,32 +127,29 @@ class FileSchema(Schema): "id", "name", "content_type", - "process_instance_id", - "irb_doc_code", "last_modified", "type", - "archived", "size", "data_store", - "document", "user_uid", "url", "file_contents", - "process_model_id", + "references", "process_group_id", + "process_model_id", ] unknown = INCLUDE + references = marshmallow.fields.List( + marshmallow.fields.Nested("FileReferenceSchema") + ) - # url = Method("get_url") - # - # def get_url(self, obj): - # token = 'not_available' - # if hasattr(obj, 'id') and obj.id is not None: - # file_url = url_for("/v1_0.crc_api_file_get_file_data_link", file_id=obj.id, _external=True) - # if hasattr(flask.g, 'user'): - # token = flask.g.user.encode_auth_token() - # url = file_url + '?auth_token=' + urllib.parse.quote_plus(token) - # return url - # else: - # return "" - # + +class FileReferenceSchema(Schema): + """FileSchema.""" + + class Meta: + """Meta.""" + + model = FileReference + fields = ["id", "name", "type"] + unknown = INCLUDE diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index 4ff25a75..f0b9065c 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -65,6 +65,7 @@ from spiffworkflow_backend.services.error_handling_service import ErrorHandlingS from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.message_service import MessageService +from spiffworkflow_backend.services.process_instance_processor import MyCustomParser from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) @@ -263,6 +264,10 @@ def process_model_show(process_group_id: str, process_model_id: str) -> Any: process_model = get_process_model(process_model_id, process_group_id) files = sorted(SpecFileService.get_files(process_model)) process_model.files = files + for file in process_model.files: + file.references = SpecFileService.get_references_for_file( + file, process_model, MyCustomParser + ) process_model_json = ProcessModelInfoSchema().dump(process_model) return process_model_json diff --git a/src/spiffworkflow_backend/services/spec_file_service.py b/src/spiffworkflow_backend/services/spec_file_service.py index eb2a322f..c6b86d3e 100644 --- a/src/spiffworkflow_backend/services/spec_file_service.py +++ b/src/spiffworkflow_backend/services/spec_file_service.py @@ -2,6 +2,7 @@ import os import shutil from datetime import datetime +from typing import Any from typing import List from typing import Optional @@ -14,6 +15,7 @@ from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup from spiffworkflow_backend.models.file import File +from spiffworkflow_backend.models.file import FileReference from spiffworkflow_backend.models.file import FileType from spiffworkflow_backend.models.message_correlation_property import ( MessageCorrelationPropertyModel, @@ -54,6 +56,41 @@ class SpecFileService(FileSystemService): ) return files + @staticmethod + def get_references_for_file( + file: File, process_model_info: ProcessModelInfo, parser_class: Any + ) -> list[FileReference]: + """Uses spiffworkflow to parse BPMN and DMN files to determine how they can be externally referenced. + + Returns a list of Reference objects that contain the type of reference, the id, the name. + Ex. + id = {str} 'Level3' + name = {str} 'Level 3' + type = {str} 'process' + """ + references: list[FileReference] = [] + file_path = SpecFileService.file_path(process_model_info, file.name) + parser = parser_class() + parser_type = None + sub_parser = None + if file.type == FileType.bpmn.value: + parser.add_bpmn_file(file_path) + parser_type = "process" + sub_parsers = list(parser.process_parsers.values()) + elif file.type == FileType.dmn.value: + parser.add_dmn_file(file_path) + sub_parsers = list(parser.dmn_parsers.values()) + parser_type = "decision" + else: + return references + for sub_parser in sub_parsers: + references.append( + FileReference( + id=sub_parser.get_id(), name=sub_parser.get_name(), type=parser_type + ) + ) + return references + @staticmethod def add_file( process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes diff --git a/tests/data/call_activity_nested/schema.json b/tests/data/call_activity_nested/schema.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/data/call_activity_nested/schema.json @@ -0,0 +1 @@ +{} diff --git a/tests/spiffworkflow_backend/unit/test_file.py b/tests/spiffworkflow_backend/unit/test_file.py index 22f2fb15..c5819587 100644 --- a/tests/spiffworkflow_backend/unit/test_file.py +++ b/tests/spiffworkflow_backend/unit/test_file.py @@ -21,7 +21,6 @@ def create_test_file(type: str, name: str) -> File: type=type, name=name, content_type=type, - document={}, last_modified=datetime.now(), size=1, ) diff --git a/tests/spiffworkflow_backend/unit/test_spec_file_service.py b/tests/spiffworkflow_backend/unit/test_spec_file_service.py index fd882e71..85adb298 100644 --- a/tests/spiffworkflow_backend/unit/test_spec_file_service.py +++ b/tests/spiffworkflow_backend/unit/test_spec_file_service.py @@ -5,10 +5,13 @@ import pytest from flask import Flask from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db +from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup +from spiffworkflow_backend.services.process_model_service import ProcessModelService +from spiffworkflow_backend.services.spec_file_service import SpecFileService class TestSpecFileService(BaseTest): @@ -95,3 +98,44 @@ class TestSpecFileService(BaseTest): bpmn_process_id_lookups[0].bpmn_file_relative_path == self.call_activity_nested_relative_file_path ) + + def test_load_reference_information( + self, app: Flask, with_db_and_bpmn_file_cleanup: None + ) -> None: + """Test_load_reference_information. + + When getting files from the spec_file service, each file includes + details about how the file can be referenced -- for instance + it is possible to reference a DMN file with a Decision.id or + a bpmn file with a process.id. These Decisions and processes + can also have human readable display names, which should also be avaiable. + Note that a single bpmn file can contain many processes, and + a DMN file can (theoretically) contain many decisions. So this + is an array. + """ + load_test_spec( + "call_activity_nested", + process_model_source_directory="call_activity_nested", + ) + process_model_info = ProcessModelService().get_process_model( + "call_activity_nested" + ) + files = SpecFileService.get_files(process_model_info) + + file = next(filter(lambda f: f.name == "call_activity_level_3.bpmn", files)) + ca_3 = SpecFileService.get_references_for_file( + file, process_model_info, BpmnDmnParser + ) + assert len(ca_3) == 1 + assert ca_3[0].name == "Level 3" + assert ca_3[0].id == "Level3" + assert ca_3[0].type == "process" + + file = next(filter(lambda f: f.name == "level2c.dmn", files)) + dmn1 = SpecFileService.get_references_for_file( + file, process_model_info, BpmnDmnParser + ) + assert len(dmn1) == 1 + assert dmn1[0].name == "Decision 1" + assert dmn1[0].id == "Decision_0vrtcmk" + assert dmn1[0].type == "decision"