Merge commit '39f9dcba4d497b121ef7f24e58e735b62b0e03d4' into main

This commit is contained in:
Dan 2022-11-07 14:35:45 -05:00
commit 3a9b7661e0
6 changed files with 118 additions and 26 deletions

View File

@ -4,6 +4,7 @@ from dataclasses import field
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
import marshmallow
from marshmallow import INCLUDE from marshmallow import INCLUDE
from marshmallow import Schema 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) @dataclass(order=True)
class File: class File:
"""File.""" """File."""
@ -70,17 +85,12 @@ class File:
content_type: str content_type: str
name: str name: str
type: str type: str
document: dict
last_modified: datetime last_modified: datetime
size: int size: int
process_instance_id: Optional[int] = None references: Optional[list[FileReference]] = None
irb_doc_code: Optional[str] = None
data_store: Optional[dict] = field(default_factory=dict)
user_uid: Optional[str] = None
file_contents: Optional[bytes] = None file_contents: Optional[bytes] = None
process_model_id: Optional[str] = None process_model_id: Optional[str] = None
process_group_id: Optional[str] = None process_group_id: Optional[str] = None
archived: bool = False
def __post_init__(self) -> None: def __post_init__(self) -> None:
"""__post_init__.""" """__post_init__."""
@ -100,7 +110,6 @@ class File:
name=file_name, name=file_name,
content_type=content_type, content_type=content_type,
type=file_type.value, type=file_type.value,
document={},
last_modified=last_modified, last_modified=last_modified,
size=file_size, size=file_size,
) )
@ -118,32 +127,29 @@ class FileSchema(Schema):
"id", "id",
"name", "name",
"content_type", "content_type",
"process_instance_id",
"irb_doc_code",
"last_modified", "last_modified",
"type", "type",
"archived",
"size", "size",
"data_store", "data_store",
"document",
"user_uid", "user_uid",
"url", "url",
"file_contents", "file_contents",
"process_model_id", "references",
"process_group_id", "process_group_id",
"process_model_id",
] ]
unknown = INCLUDE unknown = INCLUDE
references = marshmallow.fields.List(
marshmallow.fields.Nested("FileReferenceSchema")
)
# url = Method("get_url")
# class FileReferenceSchema(Schema):
# def get_url(self, obj): """FileSchema."""
# token = 'not_available'
# if hasattr(obj, 'id') and obj.id is not None: class Meta:
# file_url = url_for("/v1_0.crc_api_file_get_file_data_link", file_id=obj.id, _external=True) """Meta."""
# if hasattr(flask.g, 'user'):
# token = flask.g.user.encode_auth_token() model = FileReference
# url = file_url + '?auth_token=' + urllib.parse.quote_plus(token) fields = ["id", "name", "type"]
# return url unknown = INCLUDE
# else:
# return ""
#

View File

@ -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.file_system_service import FileSystemService
from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.git_service import GitService
from spiffworkflow_backend.services.message_service import MessageService 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 ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, 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) process_model = get_process_model(process_model_id, process_group_id)
files = sorted(SpecFileService.get_files(process_model)) files = sorted(SpecFileService.get_files(process_model))
process_model.files = files 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) process_model_json = ProcessModelInfoSchema().dump(process_model)
return process_model_json return process_model_json

View File

@ -2,6 +2,7 @@
import os import os
import shutil import shutil
from datetime import datetime from datetime import datetime
from typing import Any
from typing import List from typing import List
from typing import Optional 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.bpmn_process_id_lookup import BpmnProcessIdLookup
from spiffworkflow_backend.models.file import File 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.file import FileType
from spiffworkflow_backend.models.message_correlation_property import ( from spiffworkflow_backend.models.message_correlation_property import (
MessageCorrelationPropertyModel, MessageCorrelationPropertyModel,
@ -54,6 +56,41 @@ class SpecFileService(FileSystemService):
) )
return files 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 @staticmethod
def add_file( def add_file(
process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes

View File

@ -21,7 +21,6 @@ def create_test_file(type: str, name: str) -> File:
type=type, type=type,
name=name, name=name,
content_type=type, content_type=type,
document={},
last_modified=datetime.now(), last_modified=datetime.now(),
size=1, size=1,
) )

View File

@ -5,10 +5,13 @@ import pytest
from flask import Flask from flask import Flask
from flask_bpmn.api.api_error import ApiError from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db 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.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup 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): class TestSpecFileService(BaseTest):
@ -95,3 +98,44 @@ class TestSpecFileService(BaseTest):
bpmn_process_id_lookups[0].bpmn_file_relative_path bpmn_process_id_lookups[0].bpmn_file_relative_path
== self.call_activity_nested_relative_file_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"