From 7f9422ce2cf368b9f955f270f42534e1c8079ac3 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 3 Jan 2023 16:39:32 -0500 Subject: [PATCH] added method to create process models based on english string w/ burnettk --- .../routes/process_instances_controller.py | 2 - .../routes/process_models_controller.py | 71 +++++++++++++------ .../services/file_system_service.py | 5 +- .../process_instance_report_service.py | 53 +++++++------- .../basic_with_user_task_template.bpmn | 45 ++++++++++++ .../form-identifier-id-template-schema.json | 6 ++ .../form-identifier-id-template-uischema.json | 3 + .../integration/test_process_api.py | 19 +++++ 8 files changed, 155 insertions(+), 49 deletions(-) create mode 100644 src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn create mode 100644 src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json create mode 100644 src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json diff --git a/src/spiffworkflow_backend/routes/process_instances_controller.py b/src/spiffworkflow_backend/routes/process_instances_controller.py index 50f1fc64..c1c4c1b0 100644 --- a/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -396,7 +396,6 @@ def process_instance_report_update( report_id: int, body: Dict[str, Any], ) -> flask.wrappers.Response: - """Process_instance_report_create.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, @@ -417,7 +416,6 @@ def process_instance_report_update( def process_instance_report_delete( report_id: int, ) -> flask.wrappers.Response: - """Process_instance_report_create.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, diff --git a/src/spiffworkflow_backend/routes/process_models_controller.py b/src/spiffworkflow_backend/routes/process_models_controller.py index a1c750cd..3c3a7382 100644 --- a/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/src/spiffworkflow_backend/routes/process_models_controller.py @@ -1,5 +1,8 @@ """APIs for dealing with process groups, process models, and process instances.""" import json +import os +from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel +from spiffworkflow_backend.services.file_system_service import FileSystemService import re from typing import Any from typing import Dict @@ -26,6 +29,7 @@ from spiffworkflow_backend.routes.process_api_blueprint import ( ) from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.git_service import MissingGitConfigsError +from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.spec_file_service import SpecFileService @@ -302,20 +306,6 @@ def process_model_create_with_natural_language( modified_process_group_id: str, body: Dict[str, str] ) -> flask.wrappers.Response: """Process_model_create_with_natural_language.""" - # body_include_list = [ - # "id", - # "display_name", - # "primary_file_name", - # "primary_process_id", - # "description", - # "metadata_extraction_paths", - # ] - # body_filtered = { - # include_item: body[include_item] - # for include_item in body_include_list - # if include_item in body - # } - pattern = re.compile( r"Create a (?P.*?) process model with a (?P.*?) form that" r" collects (?P.*)" @@ -333,17 +323,13 @@ def process_model_create_with_natural_language( process_model_display_name = match.group("pm_name") process_model_identifier = re.sub(r"[ _]", "-", process_model_display_name) process_model_identifier = re.sub(r"-{2,}", "-", process_model_identifier).lower() - print(f"process_model_identifier: {process_model_identifier}") form_name = match.group("form_name") form_identifier = re.sub(r"[ _]", "-", form_name) form_identifier = re.sub(r"-{2,}", "-", form_identifier).lower() - print(f"form_identifier: {form_identifier}") column_names = match.group("columns") - print(f"column_names: {column_names}") columns = re.sub(r"(, (and )?)", ",", column_names).split(",") - print(f"columns: {columns}") process_group = _get_process_group_from_modified_identifier( modified_process_group_id @@ -371,14 +357,59 @@ def process_model_create_with_natural_language( status_code=400, ) + bpmn_template_file = os.path.join(current_app.root_path, 'templates', 'basic_with_user_task_template.bpmn') + if not os.path.exists(bpmn_template_file): + raise ApiError( + error_code="bpmn_template_file_does_not_exist", + message=f"Could not find the bpmn template file to create process model.", + status_code=500, + ) + ProcessModelService.add_process_model(process_model_info) + bpmn_process_identifier = f"{process_model_info.id}_process" + bpmn_template_contents = '' + with open(bpmn_template_file, encoding="utf-8") as f: + bpmn_template_contents = f.read() + + bpmn_template_contents = bpmn_template_contents.replace('natural_language_process_id_template', bpmn_process_identifier) + bpmn_template_contents = bpmn_template_contents.replace('form-identifier-id-template-', form_identifier) + + form_uischema_json: dict = { + "ui:order": [] + } + + form_properties: dict = {} + for column in columns: + form_properties[column] = { + "type": "string", + "title": column, + } + form_schema_json = { + "title": form_identifier, + "description": "", + "properties": form_properties, + "required": [] + } + + SpecFileService.add_file(process_model_info, f"{process_model_identifier}.bpmn", str.encode(bpmn_template_contents)) + SpecFileService.add_file(process_model_info, f"{form_identifier}-schema.json", str.encode(json.dumps(form_schema_json))) + SpecFileService.add_file(process_model_info, f"{form_identifier}-uischema.json", str.encode(json.dumps(form_uischema_json))) + _commit_and_push_to_git( f"User: {g.user.username} created process model via natural language:" f" {process_model_info.id}" ) - # TODO: Create a form json schema and UI schema - # TODO: Add report + default_report_metadata = ProcessInstanceReportService.system_metadata_map('default') + for column in columns: + default_report_metadata['columns'].append({ + "Header": column, "accessor": column + }) + ProcessInstanceReportModel.create_report( + identifier=process_model_identifier, + user=g.user, + report_metadata=default_report_metadata, + ) return Response( json.dumps(ProcessModelInfoSchema().dump(process_model_info)), diff --git a/src/spiffworkflow_backend/services/file_system_service.py b/src/spiffworkflow_backend/services/file_system_service.py index a2a9181d..06bfd421 100644 --- a/src/spiffworkflow_backend/services/file_system_service.py +++ b/src/spiffworkflow_backend/services/file_system_service.py @@ -40,10 +40,9 @@ class FileSystemService: @staticmethod def root_path() -> str: """Root_path.""" - # fixme: allow absolute files dir_name = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"] - app_root = current_app.root_path - return os.path.abspath(os.path.join(app_root, "..", dir_name)) + # ensure this is a string - thanks mypy... + return os.path.abspath(os.path.join(dir_name, '')) @staticmethod def id_string_to_relative_path(id_string: str) -> str: diff --git a/src/spiffworkflow_backend/services/process_instance_report_service.py b/src/spiffworkflow_backend/services/process_instance_report_service.py index 7d725fa2..2ae12769 100644 --- a/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -1,5 +1,6 @@ """Process_instance_report_service.""" import re +from typing import Any from dataclasses import dataclass from typing import Optional @@ -84,29 +85,7 @@ class ProcessInstanceReportService: """ProcessInstanceReportService.""" @classmethod - def report_with_identifier( - cls, - user: UserModel, - report_id: Optional[int] = None, - report_identifier: Optional[str] = None, - ) -> ProcessInstanceReportModel: - """Report_with_filter.""" - if report_id is not None: - process_instance_report = ProcessInstanceReportModel.query.filter_by( - id=report_id, created_by_id=user.id - ).first() - if process_instance_report is not None: - return process_instance_report # type: ignore - - if report_identifier is None: - report_identifier = "default" - process_instance_report = ProcessInstanceReportModel.query.filter_by( - identifier=report_identifier, created_by_id=user.id - ).first() - - if process_instance_report is not None: - return process_instance_report # type: ignore - + def system_metadata_map(cls, metadata_key: str) -> dict[str, Any]: # TODO replace with system reports that are loaded on launch (or similar) temp_system_metadata_map = { "default": { @@ -151,10 +130,36 @@ class ProcessInstanceReportService: "order_by": ["-start_in_seconds", "-id"], }, } + return temp_system_metadata_map[metadata_key] + + @classmethod + def report_with_identifier( + cls, + user: UserModel, + report_id: Optional[int] = None, + report_identifier: Optional[str] = None, + ) -> ProcessInstanceReportModel: + """Report_with_filter.""" + if report_id is not None: + process_instance_report = ProcessInstanceReportModel.query.filter_by( + id=report_id, created_by_id=user.id + ).first() + if process_instance_report is not None: + return process_instance_report # type: ignore + + if report_identifier is None: + report_identifier = "default" + process_instance_report = ProcessInstanceReportModel.query.filter_by( + identifier=report_identifier, created_by_id=user.id + ).first() + + if process_instance_report is not None: + return process_instance_report # type: ignore + process_instance_report = ProcessInstanceReportModel( identifier=report_identifier, created_by_id=user.id, - report_metadata=temp_system_metadata_map[report_identifier], + report_metadata=cls.system_metadata_map(report_identifier), ) return process_instance_report # type: ignore diff --git a/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn b/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn new file mode 100644 index 00000000..2e33d429 --- /dev/null +++ b/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn @@ -0,0 +1,45 @@ + + + + + Flow_0gixxkm + + + + + + + + + + Flow_0gixxkm + Flow_1oi9nsn + + + Flow_1oi9nsn + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json b/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json new file mode 100644 index 00000000..6c2c075d --- /dev/null +++ b/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json @@ -0,0 +1,6 @@ +{ + "title": "{FORM_IDENTIFIER}", + "description": "", + "properties": {}, + "required": [] +} \ No newline at end of file diff --git a/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json b/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json new file mode 100644 index 00000000..e8e87ab4 --- /dev/null +++ b/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json @@ -0,0 +1,3 @@ +{ + "ui:order": [] +} \ No newline at end of file diff --git a/tests/spiffworkflow_backend/integration/test_process_api.py b/tests/spiffworkflow_backend/integration/test_process_api.py index 461cdcd4..7096499e 100644 --- a/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/tests/spiffworkflow_backend/integration/test_process_api.py @@ -205,6 +205,25 @@ class TestProcessApi(BaseTest): {"key": "priority", "path": "priority"}, ] + process_model = ProcessModelService.get_process_model(response.json['id']) + process_model_path = os.path.join( + FileSystemService.root_path(), + FileSystemService.id_string_to_relative_path(process_model.id) + ) + + process_model_diagram = os.path.join(process_model_path, "bug-tracker.bpmn") + assert os.path.exists(process_model_diagram) + form_schema_json = os.path.join(process_model_path, "bug-details-schema.json") + assert os.path.exists(form_schema_json) + form_uischema_json = os.path.join(process_model_path, "bug-details-uischema.json") + assert os.path.exists(form_uischema_json) + + process_instance_report = ProcessInstanceReportModel.query.filter_by(identifier='bug-tracker').first() + assert process_instance_report is not None + report_column_accessors = [i['accessor'] for i in process_instance_report.report_metadata['columns']] + expected_column_accessors = ['id', 'process_model_display_name', 'start_in_seconds', 'end_in_seconds', 'username', 'status', 'summary', 'description', 'priority'] + assert report_column_accessors == expected_column_accessors + def test_primary_process_id_updates_via_xml( self, app: Flask,