From 7b85be037b0168116e0bd8c770c78bd4924c970b Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 16 Dec 2022 13:23:57 -0500 Subject: [PATCH] Squashed 'spiffworkflow-backend/' changes from ecbe9704..7600e1e5 7600e1e5 added new api endpoint to get task-info so users with access to process instances can see the tasks but not the data 643f632c fix permissions for core on dev w/ burnettk 78badf26 fix broken unit tests in backend 6a9272b3 do not set git branch info on development w/ burnettk cullerton e5b03110 commit and push to github on all changes to bpmn dir w/ burnettk cullerton e33d0030 Merge branch 'main' of github.com:sartography/spiff-arena cf9b2fcb add support to find the form for a call activity defined in another process model 494a427c Fix endpoints for script task unit tests (#77) 6eec41a7 make replacing keycloak more robust, as it now works when the container is running ec01b279 some fixes for ci w/ burnettk 7e520dbc remove assert statements from actual code w/ burnettk 511d31b9 fixed perms for readonly for staging w/ burnettk 900f4524 load the correct perm file on staging w/ burnettk 562ec8ef added permission file for staging w/ burnettk 7e39994c in postgres you cannot order by a non-grouped column without doing an aggregate 36c50953 pyl and fix test w/ burnettk f15794a2 store subprocesses for spiff steps as well and do not save file as primary if one is already set w/ burnettk 395b7e44 Merge remote-tracking branch 'origin/main' into feature/view_call_activity_diagram 4c119caa some fixes to ensure we display the correct task data for the diagram elements w/ burnettk ebbb593e lint 6ae471d0 pyl 5e54256a added test to get the diagram for a given process instance call activity 2c1d8b62 allow viewing the diagram for a specific process identifier b8a5690c strip off spaces from git service command stdout 1291c8d4 try to fix a test on windows git-subtree-dir: spiffworkflow-backend git-subtree-split: 7600e1e57917b3a1b3d07d41daaeddbb09e8242c --- bin/build_and_run_with_docker_compose | 2 +- bin/git_commit_bpmn_models_repo | 18 +- bin/start_keycloak | 14 +- docker-compose.yml | 4 +- src/spiffworkflow_backend/api.yml | 77 ++++++-- .../config/development.py | 2 - .../config/permissions/development.yml | 31 +++- .../config/permissions/staging.yml | 165 ++++++++++++++++++ .../terraform_deployed_environment.yml | 15 -- src/spiffworkflow_backend/config/staging.py | 1 + src/spiffworkflow_backend/config/testing.py | 1 + .../models/process_instance_metadata.py | 2 +- .../models/spec_reference.py | 4 + .../models/spiff_logging.py | 2 +- .../models/spiff_step_details.py | 2 +- src/spiffworkflow_backend/models/task.py | 13 +- .../routes/process_api_blueprint.py | 162 ++++++++++++++--- src/spiffworkflow_backend/routes/user.py | 9 +- .../services/authentication_service.py | 4 + .../services/git_service.py | 45 +++-- .../services/process_instance_processor.py | 2 +- .../services/process_instance_service.py | 8 +- .../services/process_model_service.py | 10 +- .../services/service_task_service.py | 1 - .../services/spec_file_service.py | 19 +- .../integration/test_process_api.py | 58 +++++- .../unit/test_git_service.py | 22 +++ 27 files changed, 575 insertions(+), 118 deletions(-) create mode 100644 src/spiffworkflow_backend/config/permissions/staging.yml create mode 100644 tests/spiffworkflow_backend/unit/test_git_service.py diff --git a/bin/build_and_run_with_docker_compose b/bin/build_and_run_with_docker_compose index 4356d974f..2dfa896e6 100755 --- a/bin/build_and_run_with_docker_compose +++ b/bin/build_and_run_with_docker_compose @@ -9,7 +9,7 @@ set -o errtrace -o errexit -o nounset -o pipefail if [[ -z "${BPMN_SPEC_ABSOLUTE_DIR:-}" ]]; then script_dir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" - export BPMN_SPEC_ABSOLUTE_DIR="$script_dir/../../sample-process-models" + export BPMN_SPEC_ABSOLUTE_DIR="$script_dir/../../../sample-process-models" fi if [[ -z "${SPIFFWORKFLOW_BACKEND_DOCKER_COMPOSE_PROFILE:-}" ]]; then diff --git a/bin/git_commit_bpmn_models_repo b/bin/git_commit_bpmn_models_repo index 13e18da9c..62fc0cab0 100755 --- a/bin/git_commit_bpmn_models_repo +++ b/bin/git_commit_bpmn_models_repo @@ -11,11 +11,12 @@ set -o errtrace -o errexit -o nounset -o pipefail bpmn_models_absolute_dir="$1" git_commit_message="$2" -git_commit_username="$3" -git_commit_email="$4" +git_branch="$3" +git_commit_username="$4" +git_commit_email="$5" -if [[ -z "${2:-}" ]]; then - >&2 echo "usage: $(basename "$0") [bpmn_models_absolute_dir] [git_commit_message]" +if [[ -z "${5:-}" ]]; then + >&2 echo "usage: $(basename "$0") [bpmn_models_absolute_dir] [git_commit_message] [git_branch] [git_commit_username] [git_commit_email]" exit 1 fi @@ -26,11 +27,8 @@ git add . if [ -z "$(git status --porcelain)" ]; then echo "No changes to commit" else - if [[ -n "$git_commit_username" ]]; then - git config --local user.name "$git_commit_username" - fi - if [[ -n "$git_commit_email" ]]; then - git config --local user.email "$git_commit_email" - fi + git config --local user.name "$git_commit_username" + git config --local user.email "$git_commit_email" git commit -m "$git_commit_message" + git push --set-upstream origin "$git_branch" fi diff --git a/bin/start_keycloak b/bin/start_keycloak index 32b502ca0..f76347da7 100755 --- a/bin/start_keycloak +++ b/bin/start_keycloak @@ -18,7 +18,19 @@ set -o errtrace -o errexit -o nounset -o pipefail if ! docker network inspect spiffworkflow > /dev/null 2>&1; then docker network create spiffworkflow fi -docker rm keycloak 2>/dev/null || echo 'no keycloak container found, safe to start new container' + +# https://stackoverflow.com/a/60579344/6090676 +container_name="keycloak" +if [[ -n "$(docker ps -qa -f name=$container_name)" ]]; then + echo ":: Found container - $container_name" + if [[ -n "$(docker ps -q -f name=$container_name)" ]]; then + echo ":: Stopping running container - $container_name" + docker stop $container_name + fi + echo ":: Removing stopped container - $container_name" + docker rm $container_name +fi + docker run \ -p 7002:8080 \ -d \ diff --git a/docker-compose.yml b/docker-compose.yml index 1cbe9dcb7..410cbb7ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,7 +68,7 @@ services: - "7000:7000" network_mode: host volumes: - - ${BPMN_SPEC_ABSOLUTE_DIR:-./../sample-process-models}:/app/process_models + - ${BPMN_SPEC_ABSOLUTE_DIR:-../../sample-process-models}:/app/process_models - ./log:/app/log healthcheck: test: curl localhost:7000/v1.0/status --fail @@ -82,7 +82,7 @@ services: profiles: - debug volumes: - - ${BPMN_SPEC_ABSOLUTE_DIR:-./../sample-process-models}:/app/process_models + - ${BPMN_SPEC_ABSOLUTE_DIR:-../../sample-process-models}:/app/process_models - ./:/app command: /app/bin/boot_in_docker_debug_mode diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index 764ba543f..a97a3b11f 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -610,15 +610,9 @@ paths: items: $ref: "#/components/schemas/Workflow" - /process-models/{process_group_id}/{process_model_id}/script-unit-tests: + /process-models/{modified_process_model_identifier}/script-unit-tests: parameters: - - name: process_group_id - in: path - required: true - description: The unique id of an existing process group - schema: - type: string - - name: process_model_id + - name: modified_process_model_identifier in: path required: true description: The unique id of an existing process model. @@ -637,15 +631,9 @@ paths: schema: $ref: "#/components/schemas/Workflow" - /process-models/{process_group_id}/{process_model_id}/script-unit-tests/run: + /process-models/{modified_process_model_identifier}/script-unit-tests/run: parameters: - - name: process_group_id - in: path - required: true - description: The unique id of an existing process group - schema: - type: string - - name: process_model_id + - name: modified_process_model_identifier in: path required: true description: The unique id of an existing process model. @@ -685,6 +673,53 @@ paths: schema: $ref: "#/components/schemas/Workflow" + /process-instances/{modified_process_model_identifier}/{process_instance_id}/task-info: + parameters: + - name: modified_process_model_identifier + in: path + required: true + description: The unique id of an existing process model + schema: + type: string + - name: process_instance_id + in: path + required: true + 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 + - name: all_tasks + in: query + required: false + description: If true, this wil return all tasks associated with the process instance and not just user tasks. + schema: + type: boolean + - name: spiff_step + in: query + required: false + description: If set will return the tasks as they were during a specific step of execution. + schema: + type: integer + get: + tags: + - Process Instances + operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_task_list_without_task_data + summary: returns the list of all user tasks associated with process instance without the task data + responses: + "200": + description: list of tasks + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Task" + /process-instances/{modified_process_model_identifier}/{process_instance_id}: parameters: - name: modified_process_model_identifier @@ -699,6 +734,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 @@ -1138,8 +1179,8 @@ paths: get: tags: - Process Instances - operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_task_list - summary: returns the list of all user tasks associated with process instance + operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_task_list_with_task_data + summary: returns the list of all user tasks associated with process instance with the task data responses: "200": description: list of tasks diff --git a/src/spiffworkflow_backend/config/development.py b/src/spiffworkflow_backend/config/development.py index 15cbead83..39e10cb58 100644 --- a/src/spiffworkflow_backend/config/development.py +++ b/src/spiffworkflow_backend/config/development.py @@ -17,5 +17,3 @@ GIT_CLONE_URL_FOR_PUBLISHING = environ.get( ) GIT_USERNAME = "sartography-automated-committer" GIT_USER_EMAIL = f"{GIT_USERNAME}@users.noreply.github.com" -GIT_BRANCH_TO_PUBLISH_TO = "main" -GIT_BRANCH = "main" diff --git a/src/spiffworkflow_backend/config/permissions/development.yml b/src/spiffworkflow_backend/config/permissions/development.yml index 419c925fa..14061da78 100644 --- a/src/spiffworkflow_backend/config/permissions/development.yml +++ b/src/spiffworkflow_backend/config/permissions/development.yml @@ -17,7 +17,6 @@ groups: dan, mike, jason, - j, jarrad, elizabeth, jon, @@ -32,7 +31,6 @@ groups: dan, mike, jason, - j, amir, jarrad, elizabeth, @@ -63,6 +61,12 @@ groups: harmeet, ] + admin-ro: + users: + [ + j, + ] + permissions: admin: groups: [admin] @@ -70,6 +74,17 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /* + admin-readonly: + groups: [admin-ro] + users: [] + allowed_permissions: [read] + uri: /* + admin-process-instances-for-readonly: + groups: [admin-ro] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /v1.0/process-instances/* + tasks-crud: groups: [everybody] users: [] @@ -108,12 +123,12 @@ permissions: users: [] allowed_permissions: [read] uri: /v1.0/processes - - task-data-read: - groups: [demo] - users: [] - allowed_permissions: [read] - uri: /v1.0/task-data/* + # + # task-data-read: + # groups: [demo] + # users: [] + # allowed_permissions: [read] + # uri: /v1.0/task-data/* manage-procurement-admin: diff --git a/src/spiffworkflow_backend/config/permissions/staging.yml b/src/spiffworkflow_backend/config/permissions/staging.yml new file mode 100644 index 000000000..982b945c6 --- /dev/null +++ b/src/spiffworkflow_backend/config/permissions/staging.yml @@ -0,0 +1,165 @@ +default_group: everybody + +groups: + admin: + users: + [ + admin, + jakub, + kb, + alex, + dan, + mike, + jason, + j, + jarrad, + elizabeth, + jon, + natalia, + ] + + Finance Team: + users: + [ + jakub, + alex, + dan, + mike, + jason, + j, + amir, + jarrad, + elizabeth, + jon, + natalia, + sasha, + fin, + fin1, + ] + + demo: + users: + [ + core, + fin, + fin1, + harmeet, + sasha, + manuchehr, + lead, + lead1 + ] + + core-contributor: + users: + [ + core, + harmeet, + ] + +permissions: + admin: + groups: [admin] + users: [] + allowed_permissions: [read] + uri: /* + admin-process-instances: + groups: [admin] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /v1.0/process-instances/* + + tasks-crud: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /v1.0/tasks/* + + service-tasks: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/service-tasks + + + # read all for everybody + read-all-process-groups: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-groups/* + read-all-process-models: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-models/* + read-all-process-instance: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-instances/* + read-process-instance-reports: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-instances/reports/* + processes-read: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /v1.0/processes + + + manage-procurement-admin-instances: + groups: ["Project Lead"] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /v1.0/process-instances/manage-procurement:* + manage-procurement-admin-instances-slash: + groups: ["Project Lead"] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /v1.0/process-instances/manage-procurement/* + manage-procurement-admin-instance-logs: + groups: ["Project Lead"] + users: [] + allowed_permissions: [read] + uri: /v1.0/logs/manage-procurement:* + manage-procurement-admin-instance-logs-slash: + groups: ["Project Lead"] + users: [] + allowed_permissions: [read] + uri: /v1.0/logs/manage-procurement/* + + manage-revenue-streams-instances: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [create, read] + uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + manage-revenue-streams-instance-logs: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [read] + uri: /v1.0/logs/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + + manage-procurement-invoice-instances: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [create, read] + uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + manage-procurement-invoice-instance-logs: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [read] + uri: /v1.0/logs/manage-procurement:procurement:core-contributor-invoice-management:* + + manage-procurement-instances: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [create, read] + uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:* + manage-procurement-instance-logs: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [read] + uri: /v1.0/logs/manage-procurement:vendor-lifecycle-management:* diff --git a/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml b/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml index 2e41e3b00..731de9ab0 100644 --- a/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml +++ b/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml @@ -148,33 +148,18 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /v1.0/process-groups/manage-procurement:procurement:* - manage-revenue-streams-instantiate: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [create] - uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* manage-revenue-streams-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* - manage-procurement-invoice-instantiate: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [create] - uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:* manage-procurement-invoice-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - manage-procurement-instantiate: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [create] - uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:* manage-procurement-instances: groups: ["core-contributor", "demo"] users: [] diff --git a/src/spiffworkflow_backend/config/staging.py b/src/spiffworkflow_backend/config/staging.py index 5f0fec4ca..9cc247056 100644 --- a/src/spiffworkflow_backend/config/staging.py +++ b/src/spiffworkflow_backend/config/staging.py @@ -4,3 +4,4 @@ from os import environ GIT_BRANCH = environ.get("GIT_BRANCH_TO_PUBLISH_TO", default="staging") GIT_BRANCH_TO_PUBLISH_TO = environ.get("GIT_BRANCH_TO_PUBLISH_TO", default="main") GIT_COMMIT_ON_SAVE = False +SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = "staging.yml" diff --git a/src/spiffworkflow_backend/config/testing.py b/src/spiffworkflow_backend/config/testing.py index bbda9db9a..605c1bccc 100644 --- a/src/spiffworkflow_backend/config/testing.py +++ b/src/spiffworkflow_backend/config/testing.py @@ -15,6 +15,7 @@ SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get( SPIFFWORKFLOW_BACKEND_LOG_LEVEL = environ.get( "SPIFFWORKFLOW_BACKEND_LOG_LEVEL", default="debug" ) +GIT_COMMIT_ON_SAVE = False # NOTE: set this here since nox shoves tests and src code to # different places and this allows us to know exactly where we are at the start diff --git a/src/spiffworkflow_backend/models/process_instance_metadata.py b/src/spiffworkflow_backend/models/process_instance_metadata.py index c9003594b..f2e4c2221 100644 --- a/src/spiffworkflow_backend/models/process_instance_metadata.py +++ b/src/spiffworkflow_backend/models/process_instance_metadata.py @@ -1,4 +1,4 @@ -"""Spiff_step_details.""" +"""Process_instance_metadata.""" from dataclasses import dataclass from flask_bpmn.models.db import db diff --git a/src/spiffworkflow_backend/models/spec_reference.py b/src/spiffworkflow_backend/models/spec_reference.py index 1e85f7229..50b73fbae 100644 --- a/src/spiffworkflow_backend/models/spec_reference.py +++ b/src/spiffworkflow_backend/models/spec_reference.py @@ -8,6 +8,10 @@ from marshmallow import INCLUDE from sqlalchemy import UniqueConstraint +class SpecReferenceNotFoundError(Exception): + """SpecReferenceNotFoundError.""" + + @dataclass() class SpecReference: """File Reference Information. diff --git a/src/spiffworkflow_backend/models/spiff_logging.py b/src/spiffworkflow_backend/models/spiff_logging.py index b0b908877..532a6c09c 100644 --- a/src/spiffworkflow_backend/models/spiff_logging.py +++ b/src/spiffworkflow_backend/models/spiff_logging.py @@ -8,7 +8,7 @@ from flask_bpmn.models.db import SpiffworkflowBaseDBModel @dataclass class SpiffLoggingModel(SpiffworkflowBaseDBModel): - """LoggingModel.""" + """SpiffLoggingModel.""" __tablename__ = "spiff_logging" id: int = db.Column(db.Integer, primary_key=True) diff --git a/src/spiffworkflow_backend/models/spiff_step_details.py b/src/spiffworkflow_backend/models/spiff_step_details.py index 91d70116a..9afb5d078 100644 --- a/src/spiffworkflow_backend/models/spiff_step_details.py +++ b/src/spiffworkflow_backend/models/spiff_step_details.py @@ -21,7 +21,7 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel): ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore ) spiff_step: int = db.Column(db.Integer, nullable=False) - task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore + task_json: dict = deferred(db.Column(db.JSON, nullable=False)) # type: ignore timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False) completed_by_user_id: int = db.Column(db.Integer, nullable=True) lane_assignment_id: Optional[int] = db.Column( diff --git a/src/spiffworkflow_backend/models/task.py b/src/spiffworkflow_backend/models/task.py index 52bb11715..60deda842 100644 --- a/src/spiffworkflow_backend/models/task.py +++ b/src/spiffworkflow_backend/models/task.py @@ -108,7 +108,7 @@ class Task: multi_instance_type: Union[MultiInstanceType, None] = None, multi_instance_count: str = "", multi_instance_index: str = "", - process_name: str = "", + process_identifier: str = "", properties: Union[dict, None] = None, process_instance_id: Union[int, None] = None, process_instance_status: Union[str, None] = None, @@ -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: @@ -151,7 +153,7 @@ class Task: self.multi_instance_index = ( multi_instance_index # And the index of the currently repeating task. ) - self.process_name = process_name + self.process_identifier = process_identifier self.properties = properties # Arbitrary extension properties from BPMN editor. if self.properties is None: @@ -177,7 +179,7 @@ class Task: "multi_instance_type": multi_instance_type, "multi_instance_count": self.multi_instance_count, "multi_instance_index": self.multi_instance_index, - "process_name": self.process_name, + "process_identifier": self.process_identifier, "properties": self.properties, "process_instance_id": self.process_instance_id, "process_instance_status": self.process_instance_status, @@ -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 @@ -282,7 +285,7 @@ class TaskSchema(Schema): "multi_instance_type", "multi_instance_count", "multi_instance_index", - "process_name", + "process_identifier", "properties", "process_instance_id", "form_schema", @@ -293,7 +296,7 @@ class TaskSchema(Schema): documentation = marshmallow.fields.String(required=False, allow_none=True) # form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True) title = marshmallow.fields.String(required=False, allow_none=True) - process_name = marshmallow.fields.String(required=False, allow_none=True) + process_identifier = marshmallow.fields.String(required=False, allow_none=True) lane = marshmallow.fields.String(required=False, allow_none=True) @marshmallow.post_load diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index 3e7eed530..616d07ca6 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1,5 +1,6 @@ """APIs for dealing with process groups, process models, and process instances.""" import json +import os import random import re import string @@ -66,6 +67,7 @@ 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 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 @@ -74,6 +76,7 @@ from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignme from spiffworkflow_backend.routes.user import verify_token from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService +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 ( @@ -167,6 +170,9 @@ def process_group_add(body: dict) -> flask.wrappers.Response: """Add_process_group.""" process_group = ProcessGroup(**body) ProcessModelService.add_process_group(process_group) + commit_and_push_to_git( + f"User: {g.user.username} added process group {process_group.id}" + ) return make_response(jsonify(process_group), 201) @@ -174,6 +180,9 @@ def process_group_delete(modified_process_group_id: str) -> flask.wrappers.Respo """Process_group_delete.""" process_group_id = un_modify_modified_process_model_id(modified_process_group_id) ProcessModelService().process_group_delete(process_group_id) + commit_and_push_to_git( + f"User: {g.user.username} deleted process group {process_group_id}" + ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -191,6 +200,9 @@ def process_group_update( process_group_id = un_modify_modified_process_model_id(modified_process_group_id) process_group = ProcessGroup(id=process_group_id, **body_filtered) ProcessModelService.update_process_group(process_group) + commit_and_push_to_git( + f"User: {g.user.username} updated process group {process_group_id}" + ) return make_response(jsonify(process_group), 200) @@ -255,7 +267,10 @@ def process_group_move( new_process_group = ProcessModelService().process_group_move( original_process_group_id, new_location ) - return make_response(jsonify(new_process_group), 201) + commit_and_push_to_git( + f"User: {g.user.username} moved process group {original_process_group_id} to {new_process_group.id}" + ) + return make_response(jsonify(new_process_group), 200) def process_model_create( @@ -303,6 +318,9 @@ def process_model_create( ) ProcessModelService.add_process_model(process_model_info) + commit_and_push_to_git( + f"User: {g.user.username} created process model {process_model_info.id}" + ) return Response( json.dumps(ProcessModelInfoSchema().dump(process_model_info)), status=201, @@ -316,6 +334,9 @@ def process_model_delete( """Process_model_delete.""" process_model_identifier = modified_process_model_identifier.replace(":", "/") ProcessModelService().process_model_delete(process_model_identifier) + commit_and_push_to_git( + f"User: {g.user.username} deleted process model {process_model_identifier}" + ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -339,6 +360,9 @@ def process_model_update( process_model = get_process_model(process_model_identifier) ProcessModelService.update_process_model(process_model, body_filtered) + commit_and_push_to_git( + f"User: {g.user.username} updated process model {process_model_identifier}" + ) return ProcessModelInfoSchema().dump(process_model) @@ -370,7 +394,10 @@ def process_model_move( new_process_model = ProcessModelService().process_model_move( original_process_model_id, new_location ) - return make_response(jsonify(new_process_model), 201) + commit_and_push_to_git( + f"User: {g.user.username} moved process model {original_process_model_id} to {new_process_model.id}" + ) + return make_response(jsonify(new_process_model), 200) def process_model_publish( @@ -466,14 +493,9 @@ def process_model_file_update( ) SpecFileService.update_file(process_model, file_name, request_file_contents) - - if current_app.config["GIT_COMMIT_ON_SAVE"]: - git_output = GitService.commit( - message=f"User: {g.user.username} clicked save for {process_model_identifier}/{file_name}" - ) - current_app.logger.info(f"git output: {git_output}") - else: - current_app.logger.info("Git commit on save is disabled") + commit_and_push_to_git( + f"User: {g.user.username} clicked save for {process_model_identifier}/{file_name}" + ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -495,6 +517,9 @@ 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}" + ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -516,6 +541,9 @@ def add_file(modified_process_model_identifier: str) -> flask.wrappers.Response: file_contents = SpecFileService.get_data(process_model, file.name) 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}" + ) return Response( json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json" ) @@ -1024,11 +1052,11 @@ def process_instance_list( elif attribute in instance_metadata_aliases: if order_by_option.startswith("-"): order_by_query_array.append( - instance_metadata_aliases[attribute].value.desc() + func.max(instance_metadata_aliases[attribute].value).desc() ) else: order_by_query_array.append( - instance_metadata_aliases[attribute].value.asc() + func.max(instance_metadata_aliases[attribute].value).asc() ) process_instances = ( @@ -1073,25 +1101,48 @@ 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 @@ -1389,11 +1440,44 @@ def get_tasks( return make_response(jsonify(response_json), 200) -def process_instance_task_list( +def process_instance_task_list_without_task_data( modified_process_model_identifier: str, process_instance_id: int, all_tasks: bool = False, spiff_step: int = 0, +) -> flask.wrappers.Response: + """Process_instance_task_list_without_task_data.""" + return process_instance_task_list( + modified_process_model_identifier, + process_instance_id, + all_tasks, + spiff_step, + get_task_data=False, + ) + + +def process_instance_task_list_with_task_data( + modified_process_model_identifier: str, + process_instance_id: int, + all_tasks: bool = False, + spiff_step: int = 0, +) -> flask.wrappers.Response: + """Process_instance_task_list_with_task_data.""" + return process_instance_task_list( + modified_process_model_identifier, + process_instance_id, + all_tasks, + spiff_step, + get_task_data=True, + ) + + +def process_instance_task_list( + _modified_process_model_identifier: str, + process_instance_id: int, + all_tasks: bool = False, + spiff_step: int = 0, + get_task_data: bool = False, ) -> flask.wrappers.Response: """Process_instance_task_list.""" process_instance = find_process_instance_by_id_or_raise(process_instance_id) @@ -1409,7 +1493,8 @@ def process_instance_task_list( ) if step_detail is not None and process_instance.bpmn_json is not None: bpmn_json = json.loads(process_instance.bpmn_json) - bpmn_json["tasks"] = step_detail.task_json + bpmn_json["tasks"] = step_detail.task_json["tasks"] + bpmn_json["subprocesses"] = step_detail.task_json["subprocesses"] process_instance.bpmn_json = json.dumps(bpmn_json) processor = ProcessInstanceProcessor(process_instance) @@ -1423,7 +1508,8 @@ def process_instance_task_list( tasks = [] for spiff_task in spiff_tasks: task = ProcessInstanceService.spiff_task_to_api_task(spiff_task) - task.data = spiff_task.data + if get_task_data: + task.data = spiff_task.data tasks.append(task) return make_response(jsonify(tasks), 200) @@ -1459,7 +1545,25 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response task.data = spiff_task.data task.process_model_display_name = process_model.display_name task.process_model_identifier = process_model.id + process_model_with_form = process_model + refs = SpecFileService.get_references_for_process(process_model_with_form) + all_processes = [i.identifier for i in refs] + if task.process_identifier not in all_processes: + bpmn_file_full_path = ( + ProcessInstanceProcessor.bpmn_file_full_path_from_bpmn_process_identifier( + task.process_identifier + ) + ) + relative_path = os.path.relpath( + bpmn_file_full_path, start=FileSystemService.root_path() + ) + process_model_relative_path = os.path.dirname(relative_path) + process_model_with_form = ( + ProcessModelService.get_process_model_from_relative_path( + process_model_relative_path + ) + ) if task.type == "User Task": if not form_schema_file_name: @@ -1588,7 +1692,7 @@ def task_submit( def script_unit_test_create( - process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]] ) -> flask.wrappers.Response: """Script_unit_test_create.""" bpmn_task_identifier = _get_required_parameter_or_raise( @@ -1599,7 +1703,7 @@ def script_unit_test_create( "expected_output_json", body ) - process_model_identifier = f"{process_group_id}/{process_model_id}" + process_model_identifier = modified_process_model_identifier.replace(":", "/") process_model = get_process_model(process_model_identifier) file = SpecFileService.get_files(process_model, process_model.primary_file_name)[0] if file is None: @@ -1677,7 +1781,7 @@ def script_unit_test_create( def script_unit_test_run( - process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]] ) -> flask.wrappers.Response: """Script_unit_test_run.""" # FIXME: We should probably clear this somewhere else but this works @@ -1708,6 +1812,8 @@ def get_file_from_request() -> Any: return request_file +# process_model_id uses forward slashes on all OSes +# this seems to return an object where process_model.id has backslashes on windows def get_process_model(process_model_id: str) -> ProcessModelInfo: """Get_process_model.""" process_model = None @@ -1871,7 +1977,6 @@ def secret_list( def add_secret(body: Dict) -> Response: """Add secret.""" secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id) - assert secret_model # noqa: S101 return Response( json.dumps(SecretModelSchema().dump(secret_model)), status=201, @@ -2012,3 +2117,12 @@ def update_task_data( status=200, mimetype="application/json", ) + + +def commit_and_push_to_git(message: str) -> None: + """Commit_and_push_to_git.""" + if current_app.config["GIT_COMMIT_ON_SAVE"]: + git_output = GitService.commit(message=message) + current_app.logger.info(f"git output: {git_output}") + else: + current_app.logger.info("Git commit on save is disabled") diff --git a/src/spiffworkflow_backend/routes/user.py b/src/spiffworkflow_backend/routes/user.py index 2bbbc1374..ad98fbbc6 100644 --- a/src/spiffworkflow_backend/routes/user.py +++ b/src/spiffworkflow_backend/routes/user.py @@ -16,8 +16,9 @@ from flask_bpmn.api.api_error import ApiError from werkzeug.wrappers import Response from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.authentication_service import AuthenticationService from spiffworkflow_backend.services.authentication_service import ( - AuthenticationService, + MissingAccessTokenError, ) from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.user_service import UserService @@ -268,10 +269,10 @@ def login_api_return(code: str, state: str, session_state: str) -> str: code, "/v1.0/login_api_return" ) access_token: str = auth_token_object["access_token"] - assert access_token # noqa: S101 + if access_token is None: + raise MissingAccessTokenError("Cannot find the access token for the request") + return access_token - # return redirect("localhost:7000/v1.0/ui") - # return {'uid': 'user_1'} def logout(id_token: str, redirect_url: Optional[str]) -> Response: diff --git a/src/spiffworkflow_backend/services/authentication_service.py b/src/spiffworkflow_backend/services/authentication_service.py index f4bd357b1..95c1eaa89 100644 --- a/src/spiffworkflow_backend/services/authentication_service.py +++ b/src/spiffworkflow_backend/services/authentication_service.py @@ -16,6 +16,10 @@ from werkzeug.wrappers import Response from spiffworkflow_backend.models.refresh_token import RefreshTokenModel +class MissingAccessTokenError(Exception): + """MissingAccessTokenError.""" + + class AuthenticationProviderTypes(enum.Enum): """AuthenticationServiceProviders.""" diff --git a/src/spiffworkflow_backend/services/git_service.py b/src/spiffworkflow_backend/services/git_service.py index f187a47cb..8ef952c3c 100644 --- a/src/spiffworkflow_backend/services/git_service.py +++ b/src/spiffworkflow_backend/services/git_service.py @@ -46,24 +46,39 @@ 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) @classmethod - def commit(cls, message: str, repo_path: Optional[str] = None) -> str: + def commit( + cls, + message: str, + repo_path: Optional[str] = None, + branch_name: Optional[str] = None, + ) -> str: """Commit.""" + cls.check_for_basic_configs() + branch_name_to_use = branch_name + if branch_name_to_use is None: + branch_name_to_use = current_app.config["GIT_BRANCH"] repo_path_to_use = repo_path if repo_path is None: repo_path_to_use = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"] @@ -82,14 +97,25 @@ class GitService: shell_command_path, repo_path_to_use, message, + branch_name_to_use, git_username, git_email, ] return cls.run_shell_command_to_get_stdout(shell_command) @classmethod - def check_for_configs(cls) -> None: + def check_for_basic_configs(cls) -> None: + """Check_for_basic_configs.""" + if current_app.config["GIT_BRANCH"] is None: + raise MissingGitConfigsError( + "Missing config for GIT_BRANCH. " + "This is required for publishing process models" + ) + + @classmethod + def check_for_publish_configs(cls) -> None: """Check_for_configs.""" + cls.check_for_basic_configs() if current_app.config["GIT_BRANCH_TO_PUBLISH_TO"] is None: raise MissingGitConfigsError( "Missing config for GIT_BRANCH_TO_PUBLISH_TO. " @@ -115,7 +141,7 @@ class GitService: result: subprocess.CompletedProcess[bytes] = cls.run_shell_command( command, return_success_state=False ) # type: ignore - return result.stdout.decode("utf-8") + return result.stdout.decode("utf-8").strip() @classmethod def run_shell_command( @@ -142,7 +168,7 @@ class GitService: @classmethod def handle_web_hook(cls, webhook: dict) -> bool: """Handle_web_hook.""" - cls.check_for_configs() + cls.check_for_publish_configs() if "repository" not in webhook or "clone_url" not in webhook["repository"]: raise InvalidGitWebhookBodyError( @@ -178,7 +204,7 @@ class GitService: @classmethod def publish(cls, process_model_id: str, branch_to_update: str) -> str: """Publish.""" - cls.check_for_configs() + cls.check_for_publish_configs() source_process_model_root = FileSystemService.root_path() source_process_model_path = os.path.join( source_process_model_root, process_model_id @@ -227,10 +253,7 @@ class GitService: f"Request to publish changes to {process_model_id}, " f"from {g.user.username} on {current_app.config['ENV_IDENTIFIER']}" ) - cls.commit(commit_message, destination_process_root) - cls.run_shell_command( - ["git", "push", "--set-upstream", "origin", branch_to_pull_request] - ) + cls.commit(commit_message, destination_process_root, branch_to_pull_request) # build url for github page to open PR git_remote = cls.run_shell_command_to_get_stdout( diff --git a/src/spiffworkflow_backend/services/process_instance_processor.py b/src/spiffworkflow_backend/services/process_instance_processor.py index ffe69fd72..5edc526cf 100644 --- a/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/src/spiffworkflow_backend/services/process_instance_processor.py @@ -551,7 +551,7 @@ class ProcessInstanceProcessor: """SaveSpiffStepDetails.""" bpmn_json = self.serialize() wf_json = json.loads(bpmn_json) - task_json = wf_json["tasks"] + task_json = {"tasks": wf_json["tasks"], "subprocesses": wf_json["subprocesses"]} return { "process_instance_id": self.process_instance_model.id, diff --git a/src/spiffworkflow_backend/services/process_instance_service.py b/src/spiffworkflow_backend/services/process_instance_service.py index 46bd252b9..5b2781a20 100644 --- a/src/spiffworkflow_backend/services/process_instance_service.py +++ b/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 @@ -316,9 +321,10 @@ class ProcessInstanceService: multi_instance_type=mi_type, multi_instance_count=info["mi_count"], multi_instance_index=info["mi_index"], - process_name=spiff_task.task_spec._wf_spec.description, + process_identifier=spiff_task.task_spec._wf_spec.name, properties=props, parent=parent_id, + call_activity_process_identifier=call_activity_process_identifier, ) return task diff --git a/src/spiffworkflow_backend/services/process_model_service.py b/src/spiffworkflow_backend/services/process_model_service.py index d4fa5647b..67be986e1 100644 --- a/src/spiffworkflow_backend/services/process_model_service.py +++ b/src/spiffworkflow_backend/services/process_model_service.py @@ -172,7 +172,6 @@ class ProcessModelService(FileSystemService): cls, relative_path: str ) -> ProcessModelInfo: """Get_process_model_from_relative_path.""" - process_group_identifier, _ = os.path.split(relative_path) path = os.path.join(FileSystemService.root_path(), relative_path) return cls.__scan_process_model(path) @@ -224,7 +223,7 @@ class ProcessModelService(FileSystemService): user = UserService.current_user() new_process_model_list = [] for process_model in process_models: - uri = f"/v1.0/process-models/{process_model.id.replace('/', ':')}/process-instances" + uri = f"/v1.0/process-instances/{process_model.id.replace('/', ':')}" result = AuthorizationService.user_has_permission( user=user, permission="create", target_uri=uri ) @@ -430,6 +429,9 @@ class ProcessModelService(FileSystemService): # process_group.process_groups.sort() return process_group + # path might have backslashes on windows, not sure + # not sure if os.path.join converts forward slashes in the relative_path argument to backslashes: + # path = os.path.join(FileSystemService.root_path(), relative_path) @classmethod def __scan_process_model( cls, @@ -446,6 +448,10 @@ class ProcessModelService(FileSystemService): data.pop("process_group_id") # we don't save `id` in the json file, so we add it back in here. relative_path = os.path.relpath(path, FileSystemService.root_path()) + + # even on windows, use forward slashes for ids + relative_path = relative_path.replace("\\", "/") + data["id"] = relative_path process_model_info = ProcessModelInfo(**data) if process_model_info is None: diff --git a/src/spiffworkflow_backend/services/service_task_service.py b/src/spiffworkflow_backend/services/service_task_service.py index 15e25a759..6fec8b796 100644 --- a/src/spiffworkflow_backend/services/service_task_service.py +++ b/src/spiffworkflow_backend/services/service_task_service.py @@ -31,7 +31,6 @@ class ServiceTaskDelegate: if value.startswith(secret_prefix): key = value.removeprefix(secret_prefix) secret = SecretService().get_secret(key) - assert secret # noqa: S101 return secret.value file_prefix = "file:" diff --git a/src/spiffworkflow_backend/services/spec_file_service.py b/src/spiffworkflow_backend/services/spec_file_service.py index c69f41c30..72f59d1f7 100644 --- a/src/spiffworkflow_backend/services/spec_file_service.py +++ b/src/spiffworkflow_backend/services/spec_file_service.py @@ -171,13 +171,18 @@ class SpecFileService(FileSystemService): ref.is_primary = True if ref.is_primary: - ProcessModelService.update_process_model( - process_model_info, - { - "primary_process_id": ref.identifier, - "primary_file_name": file_name, - }, - ) + update_hash = {} + if not process_model_info.primary_file_name: + update_hash["primary_process_id"] = ref.identifier + update_hash["primary_file_name"] = file_name + elif file_name == process_model_info.primary_file_name: + update_hash["primary_process_id"] = ref.identifier + + if len(update_hash) > 0: + ProcessModelService.update_process_model( + process_model_info, + update_hash, + ) SpecFileService.update_caches(ref) return file diff --git a/tests/spiffworkflow_backend/integration/test_process_api.py b/tests/spiffworkflow_backend/integration/test_process_api.py index 0070c5c94..3bc21456e 100644 --- a/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/tests/spiffworkflow_backend/integration/test_process_api.py @@ -1167,6 +1167,60 @@ class TestProcessApi(BaseTest): xml_file_contents = f_open.read() assert show_response.json["bpmn_xml_file_contents"] == xml_file_contents + def test_process_instance_show_with_specified_process_identifier( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_process_instance_show_with_specified_process_identifier.""" + process_model_id = "call_activity_nested" + process_model_identifier = self.create_group_and_model_with_bpmn( + client=client, + user=with_super_admin_user, + process_group_id="test_group_two", + process_model_id=process_model_id, + bpmn_file_location="call_activity_nested", + ) + spec_reference = SpecReferenceCache.query.filter_by( + identifier="Level2b" + ).first() + assert spec_reference + modified_process_model_identifier = ( + self.modify_process_identifier_for_path_param(process_model_identifier) + ) + headers = self.logged_in_headers(with_super_admin_user) + create_response = self.create_process_instance_from_process_model_id( + client, process_model_identifier, headers + ) + assert create_response.json is not None + assert create_response.status_code == 201 + process_instance_id = create_response.json["id"] + client.post( + f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}/run", + headers=self.logged_in_headers(with_super_admin_user), + ) + show_response = client.get( + f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}?process_identifier={spec_reference.identifier}", + headers=self.logged_in_headers(with_super_admin_user), + ) + assert show_response.json is not None + assert show_response.status_code == 200 + file_system_root = FileSystemService.root_path() + process_instance_file_path = ( + f"{file_system_root}/{process_model_identifier}/{process_model_id}.bpmn" + ) + with open(process_instance_file_path) as f_open: + xml_file_contents = f_open.read() + assert show_response.json["bpmn_xml_file_contents"] != xml_file_contents + spec_reference_file_path = os.path.join( + file_system_root, spec_reference.relative_path + ) + with open(spec_reference_file_path) as f_open: + xml_file_contents = f_open.read() + assert show_response.json["bpmn_xml_file_contents"] == xml_file_contents + def test_message_start_when_starting_process_instance( self, app: Flask, @@ -2496,7 +2550,7 @@ class TestProcessApi(BaseTest): f"/v1.0/process-models/{modified_original_process_model_id}/move?new_location={new_location}", headers=self.logged_in_headers(with_super_admin_user), ) - assert response.status_code == 201 + assert response.status_code == 200 assert response.json["id"] == new_process_model_path # make sure the original model does not exist @@ -2541,7 +2595,7 @@ class TestProcessApi(BaseTest): f"/v1.0/process-groups/{modified_original_process_group_id}/move?new_location={new_location}", headers=self.logged_in_headers(with_super_admin_user), ) - assert response.status_code == 201 + assert response.status_code == 200 assert response.json["id"] == new_sub_path # make sure the original subgroup does not exist diff --git a/tests/spiffworkflow_backend/unit/test_git_service.py b/tests/spiffworkflow_backend/unit/test_git_service.py new file mode 100644 index 000000000..ed1e24e1e --- /dev/null +++ b/tests/spiffworkflow_backend/unit/test_git_service.py @@ -0,0 +1,22 @@ +"""Process Model.""" +from flask.app import Flask +from flask.testing import FlaskClient +from tests.spiffworkflow_backend.helpers.base_test import BaseTest + +from spiffworkflow_backend.services.git_service import GitService + + +class TestGitService(BaseTest): + """TestGitService.""" + + def test_strips_output_of_stdout_from_command( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + """Test_strips_output_of_stdout_from_command.""" + output = GitService.run_shell_command_to_get_stdout( + ["echo", " This output should not end in space or newline \n"] + ) + assert output == "This output should not end in space or newline"