diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index 0b37eadd..8cbd0da4 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -847,6 +847,20 @@ paths: items: $ref: "#/components/schemas/Task" + /service_tasks: + get: + tags: + - Service Tasks + operationId: spiffworkflow_backend.routes.process_api_blueprint.service_tasks_show + summary: Gets all available service task connectors + responses: + "200": + description: All service task connectors + content: + application/json: + schema: + $ref: "#/components/schemas/ServiceTask" + /tasks/{process_instance_id}/{task_id}: parameters: - name: task_id @@ -1654,6 +1668,32 @@ components: type: number format: integer example: 5 + ServiceTask: + properties: + items: + type: array + $ref: "#/components/schemas/ServiceTaskConnector" + readOnly: true + ServiceTaskConnector: + properties: + id: + type: string + example: xero/CreateInvoice + parameters: + type: array + $ref: "#/components/schemas/ServiceTaskOperatorParameter" + readOnly: true + ServiceTaskOperatorParameter: + properties: + id: + type: string + example: client_id + type: + type: string + example: str + required: + type: boolean + example: false GitRepo: properties: # remote: diff --git a/src/spiffworkflow_backend/config/default.py b/src/spiffworkflow_backend/config/default.py index 0946095c..2dde7ad2 100644 --- a/src/spiffworkflow_backend/config/default.py +++ b/src/spiffworkflow_backend/config/default.py @@ -34,3 +34,8 @@ OPEN_ID_CLIENT_SECRET_KEY = environ.get( SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = ( environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="false") == "true" ) + +# service task connector proxy +CONNECTOR_PROXY_URL = environ.get( + "CONNECTOR_PROXY_URL", default="http://localhost:7004" +) diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index a05cb1f2..0ed24f73 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -54,6 +54,7 @@ from spiffworkflow_backend.services.process_instance_service import ( ProcessInstanceService, ) from spiffworkflow_backend.services.process_model_service import ProcessModelService +from spiffworkflow_backend.services.service_task_service import ServiceTaskService from spiffworkflow_backend.services.spec_file_service import SpecFileService process_api_blueprint = Blueprint("process_api", __name__) @@ -652,6 +653,16 @@ def process_instance_report_delete( return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") +def service_tasks_show() -> flask.wrappers.Response: + """service_tasks_show.""" + available_connectors = ServiceTaskService.available_connectors() + print(available_connectors) + + return Response( + json.dumps(available_connectors), status=200, mimetype="application/json" + ) + + def process_instance_report_show( process_group_id: str, process_model_id: str, diff --git a/src/spiffworkflow_backend/services/process_instance_processor.py b/src/spiffworkflow_backend/services/process_instance_processor.py index 9bf08aa3..a5ea7af2 100644 --- a/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/src/spiffworkflow_backend/services/process_instance_processor.py @@ -41,6 +41,7 @@ from SpiffWorkflow.spiff.serializer import ManualTaskConverter from SpiffWorkflow.spiff.serializer import NoneTaskConverter from SpiffWorkflow.spiff.serializer import ReceiveTaskConverter from SpiffWorkflow.spiff.serializer import SendTaskConverter +from SpiffWorkflow.spiff.serializer import ServiceTaskConverter from SpiffWorkflow.spiff.serializer import StartEventConverter from SpiffWorkflow.spiff.serializer import SubWorkflowTaskConverter from SpiffWorkflow.spiff.serializer import TransactionSubprocessConverter @@ -69,6 +70,7 @@ from spiffworkflow_backend.models.task_event import TaskEventModel from spiffworkflow_backend.models.user import UserModelSchema from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.process_model_service import ProcessModelService +from spiffworkflow_backend.services.service_task_service import ServiceTaskService from spiffworkflow_backend.services.spec_file_service import SpecFileService from spiffworkflow_backend.services.user_service import UserService @@ -111,15 +113,21 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore "'%s', %s" % (expression, str(exception)), ) from exception - def execute(self, task: SpiffTask, script: str) -> None: + def execute( + self, task: SpiffTask, script: str, external_methods: Any = None + ) -> None: """Execute.""" try: - super().execute(task, script) + super().execute(task, script, external_methods) except WorkflowException as e: raise e except Exception as e: raise WorkflowTaskExecException(task, f" {script}, {e}", e) from e + def available_service_task_external_methods(self) -> Dict[str, Any]: + """Returns available service task external methods.""" + return ServiceTaskService.scripting_additions() + class MyCustomParser(BpmnDmnParser): # type: ignore """A BPMN and DMN parser that can also parse spiffworkflow-specific extensions.""" @@ -150,6 +158,7 @@ class ProcessInstanceProcessor: NoneTaskConverter, ReceiveTaskConverter, SendTaskConverter, + ServiceTaskConverter, StartEventConverter, SubWorkflowTaskConverter, TransactionSubprocessConverter, diff --git a/src/spiffworkflow_backend/services/service_task_service.py b/src/spiffworkflow_backend/services/service_task_service.py new file mode 100644 index 00000000..45a2eb81 --- /dev/null +++ b/src/spiffworkflow_backend/services/service_task_service.py @@ -0,0 +1,62 @@ +"""ServiceTask_service.""" +import json +from typing import Any +from typing import Dict + +import requests +from flask import current_app + + +def connector_proxy_url() -> Any: + """Returns the connector proxy url.""" + return current_app.config["CONNECTOR_PROXY_URL"] + + +class ServiceTaskDelegate: + """ServiceTaskDelegate.""" + + @staticmethod + def call_connector( + name: str, bpmn_params: Any + ) -> None: # TODO what is the return/type + """Calls a connector via the configured proxy.""" + + def normalize_value(v: Any) -> Any: + value = v["value"] + secret_prefix = "secret:" # noqa: S105 + if value.startswith(secret_prefix): + key = value.removeprefix(secret_prefix) + # TODO replace with call to secret store + value = key + return value + + params = {k: normalize_value(v) for k, v in bpmn_params.items()} + proxied_response = requests.get(f"{connector_proxy_url()}/v1/do/{name}", params) + + if proxied_response.status_code != 200: + print("got error from connector proxy") + + +class ServiceTaskService: + """ServiceTaskService.""" + + @staticmethod + def available_connectors() -> Any: + """Returns a list of available connectors.""" + try: + print(connector_proxy_url) + response = requests.get(f"{connector_proxy_url()}/v1/commands") + + if response.status_code != 200: + return [] + + parsed_response = json.loads(response.text) + return parsed_response + except Exception as e: + print(e) + return [] + + @staticmethod + def scripting_additions() -> Dict[str, Any]: + """Allows the ServiceTaskDelegate to be available to script engine instances.""" + return {"ServiceTaskDelegate": ServiceTaskDelegate}