diff --git a/src/spiffworkflow_backend/routes/process_models_controller.py b/src/spiffworkflow_backend/routes/process_models_controller.py index b1666933..6276eda5 100644 --- a/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/src/spiffworkflow_backend/routes/process_models_controller.py @@ -16,6 +16,7 @@ from flask.wrappers import Response from flask_bpmn.api.api_error import ApiError from spiffworkflow_backend.models.file import FileSchema +from spiffworkflow_backend.models.process_group import ProcessGroup from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema from spiffworkflow_backend.routes.process_api_blueprint import _commit_and_push_to_git @@ -47,23 +48,7 @@ def process_model_create( if include_item in body } - if modified_process_group_id is None: - raise ApiError( - error_code="process_group_id_not_specified", - message="Process Model could not be created when process_group_id path param is unspecified", - status_code=400, - ) - - unmodified_process_group_id = _un_modify_modified_process_model_id( - modified_process_group_id - ) - process_group = ProcessModelService.get_process_group(unmodified_process_group_id) - if process_group is None: - raise ApiError( - error_code="process_model_could_not_be_created", - message=f"Process Model could not be created from given body because Process Group could not be found: {body}", - status_code=400, - ) + _get_process_group_from_modified_identifier(modified_process_group_id) process_model_info = ProcessModelInfo(**body_filtered) # type: ignore if process_model_info is None: @@ -151,7 +136,8 @@ def process_model_move( original_process_model_id, new_location ) _commit_and_push_to_git( - f"User: {g.user.username} moved process model {original_process_model_id} to {new_process_model.id}" + f"User: {g.user.username} moved process model {original_process_model_id} to" + f" {new_process_model.id}" ) return make_response(jsonify(new_process_model), 200) @@ -224,7 +210,8 @@ def process_model_file_update( SpecFileService.update_file(process_model, file_name, request_file_contents) _commit_and_push_to_git( - f"User: {g.user.username} clicked save for {process_model_identifier}/{file_name}" + f"User: {g.user.username} clicked save for" + f" {process_model_identifier}/{file_name}" ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -248,7 +235,8 @@ def process_model_file_delete( ) from exception _commit_and_push_to_git( - f"User: {g.user.username} deleted process model file {process_model_identifier}/{file_name}" + f"User: {g.user.username} deleted process model file" + f" {process_model_identifier}/{file_name}" ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -274,7 +262,8 @@ def process_model_file_create( file.file_contents = file_contents file.process_model_id = process_model.id _commit_and_push_to_git( - f"User: {g.user.username} added process model file {process_model_identifier}/{file.name}" + f"User: {g.user.username} added process model file" + f" {process_model_identifier}/{file.name}" ) return Response( json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json" @@ -291,8 +280,10 @@ def process_model_file_show( if len(files) == 0: raise ApiError( error_code="unknown file", - message=f"No information exists for file {file_name}" - f" it does not exist in workflow {process_model_identifier}.", + message=( + f"No information exists for file {file_name}" + f" it does not exist in workflow {process_model_identifier}." + ), status_code=404, ) @@ -303,12 +294,14 @@ def process_model_file_show( return FileSchema().dump(file) -# { -# "natural_language_text": "Create a bug tracker process model with a bug-details form that collects summary, description, and priority" -# } +# { +# "natural_language_text": "Create a bug tracker process model \ +# with a bug-details form that collects summary, description, and priority" +# } def process_model_create_with_natural_language( - modified_process_group_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_group_id: str, body: Dict[str, str] ) -> flask.wrappers.Response: + """Process_model_create_with_natural_language.""" # body_include_list = [ # "id", # "display_name", @@ -323,41 +316,66 @@ def process_model_create_with_natural_language( # if include_item in body # } - pattern = re.compile(r"Create a (?P.*?) process model with a (?P.*?) form that collects (?P.*)") + pattern = re.compile( + r"Create a (?P.*?) process model with a (?P.*?) form that" + r" collects (?P.*)" + ) match = pattern.match(body["natural_language_text"]) - 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() + if match is None: + raise ApiError( + error_code="natural_language_text_not_yet_supported", + message=( + "Natural language text is not yet supported. Please use the form:" + f" {pattern.pattern}" + ), + status_code=400, + ) + 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() + + 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_hey') + column_names = match.group("columns") print(f"column_names: {column_names}") - columns = re.sub(r"(, (and )?)", ',', column_names).split(',') + columns = re.sub(r"(, (and )?)", ",", column_names).split(",") print(f"columns: {columns}") - # - # if modified_process_group_id is None: - # raise ApiError( - # error_code="process_group_id_not_specified", - # message="Process Model could not be created when process_group_id path param is unspecified", - # status_code=400, - # ) - # - # unmodified_process_group_id = _un_modify_modified_process_model_id( - # modified_process_group_id - # ) - # process_group = ProcessModelService.get_process_group(unmodified_process_group_id) - # if process_group is None: - # raise ApiError( - # error_code="process_model_could_not_be_created", - # message=f"Process Model could not be created from given body because Process Group could not be found: {body}", - # status_code=400, - # ) + process_group = _get_process_group_from_modified_identifier( + modified_process_group_id + ) + qualified_process_model_identifier = ( + f"{process_group.id}/{process_model_identifier}" + ) + + process_model_attributes = { + "id": qualified_process_model_identifier, + "display_name": process_model_display_name, + "description": None, + } + + process_model_info = ProcessModelInfo(**process_model_attributes) # type: ignore + if process_model_info is None: + raise ApiError( + error_code="process_model_could_not_be_created", + message=f"Process Model could not be created from given body: {body}", + status_code=400, + ) + + ProcessModelService.add_process_model(process_model_info) + _commit_and_push_to_git( + f"User: {g.user.username} created process model via natural language:" + f" {process_model_info.id}" + ) + return Response( + json.dumps(ProcessModelInfoSchema().dump(process_model_info)), + status=201, + mimetype="application/json", + ) def _get_file_from_request() -> Any: @@ -370,3 +388,33 @@ def _get_file_from_request() -> Any: status_code=400, ) return request_file + + +def _get_process_group_from_modified_identifier( + modified_process_group_id: str, +) -> ProcessGroup: + """_get_process_group_from_modified_identifier.""" + if modified_process_group_id is None: + raise ApiError( + error_code="process_group_id_not_specified", + message=( + "Process Model could not be created when process_group_id path param is" + " unspecified" + ), + status_code=400, + ) + + unmodified_process_group_id = _un_modify_modified_process_model_id( + modified_process_group_id + ) + process_group = ProcessModelService.get_process_group(unmodified_process_group_id) + if process_group is None: + raise ApiError( + error_code="process_model_could_not_be_created", + message=( + "Process Model could not be created from given body because Process" + f" Group could not be found: {unmodified_process_group_id}" + ), + status_code=400, + ) + return process_group diff --git a/tests/spiffworkflow_backend/integration/test_process_api.py b/tests/spiffworkflow_backend/integration/test_process_api.py index 6c3d8f27..a7179966 100644 --- a/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/tests/spiffworkflow_backend/integration/test_process_api.py @@ -170,6 +170,7 @@ class TestProcessApi(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: + """Test_process_model_create_with_natural_language.""" process_group_id = "test_process_group" process_group_description = "Test Process Group" process_model_id = "sample" @@ -178,9 +179,11 @@ class TestProcessApi(BaseTest): client, with_super_admin_user, process_group_id, process_group_description ) - body = { - "natural_language_text": "Create a Bug Tracker process model with a Bug Details form that collects summary, description, and priority" - } + text = "Create a Bug Tracker process model " + text += ( + "with a Bug Details form that collects summary, description, and priority" + ) + body = {"natural_language_text": text} self.create_process_model_with_api( client, process_model_id=process_model_identifier, @@ -193,6 +196,9 @@ class TestProcessApi(BaseTest): headers=self.logged_in_headers(with_super_admin_user), ) assert response.status_code == 201 + assert response.json is not None + assert response.json["id"] == f"{process_group_id}/bug-tracker" + assert response.json["display_name"] == "Bug Tracker" def test_primary_process_id_updates_via_xml( self, @@ -281,7 +287,6 @@ class TestProcessApi(BaseTest): assert response.json is not None assert response.json["ok"] is True - def test_process_model_delete_with_instances( self, app: Flask,