Merge branch 'main' of github.com:sartography/spiff-arena

This commit is contained in:
jasquat 2023-05-03 10:17:35 -04:00
commit 063c5faa8f
17 changed files with 914 additions and 378 deletions

View File

@ -18,13 +18,13 @@ def setup_database_uri(app: Flask) -> None:
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None: if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None:
database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}" database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}"
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite": if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
app.config["SQLALCHEMY_DATABASE_URI"] = ( app.config[
f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" "SQLALCHEMY_DATABASE_URI"
) ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3"
elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres":
app.config["SQLALCHEMY_DATABASE_URI"] = ( app.config[
f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" "SQLALCHEMY_DATABASE_URI"
) ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}"
else: else:
# use pswd to trick flake8 with hardcoded passwords # use pswd to trick flake8 with hardcoded passwords
db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD")

View File

@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
def serialized_with_metadata(self) -> dict[str, Any]: def serialized_with_metadata(self) -> dict[str, Any]:
process_instance_attributes = self.serialized process_instance_attributes = self.serialized
process_instance_attributes["process_metadata"] = self.process_metadata process_instance_attributes["process_metadata"] = self.process_metadata
process_instance_attributes["process_model_with_diagram_identifier"] = ( process_instance_attributes[
self.process_model_with_diagram_identifier "process_model_with_diagram_identifier"
) ] = self.process_model_with_diagram_identifier
return process_instance_attributes return process_instance_attributes
@property @property

View File

@ -2,6 +2,7 @@
import enum import enum
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from typing import List
from typing import Optional from typing import Optional
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
@ -85,6 +86,7 @@ class TaskModel(SpiffworkflowBaseDBModel):
can_complete: Optional[bool] = None can_complete: Optional[bool] = None
extensions: Optional[dict] = None extensions: Optional[dict] = None
name_for_display: Optional[str] = None name_for_display: Optional[str] = None
signal_buttons: Optional[List[dict]] = None
def get_data(self) -> dict: def get_data(self) -> dict:
return {**self.python_env_data(), **self.json_data()} return {**self.python_env_data(), **self.json_data()}

View File

@ -18,7 +18,6 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
) )
from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
from spiffworkflow_backend.models.process_instance_file_data import ( from spiffworkflow_backend.models.process_instance_file_data import (
ProcessInstanceFileDataModel, ProcessInstanceFileDataModel,
) )
@ -32,6 +31,7 @@ from spiffworkflow_backend.services.process_caller_service import ProcessCallerS
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelService
@ -199,16 +199,13 @@ def send_bpmn_event(
if process_instance: if process_instance:
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.send_bpmn_event(body) processor.send_bpmn_event(body)
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
return make_response(jsonify(task), 200)
else: else:
raise ApiError( raise ApiError(
error_code="send_bpmn_event_error", error_code="send_bpmn_event_error",
message=f"Could not send event to Instance: {process_instance_id}", message=f"Could not send event to Instance: {process_instance_id}",
) )
return Response(
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
status=200,
mimetype="application/json",
)
def _commit_and_push_to_git(message: str) -> None: def _commit_and_push_to_git(message: str) -> None:

View File

@ -289,6 +289,7 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id) task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
task_definition = task_model.task_definition task_definition = task_model.task_definition
extensions = TaskService.get_extensions_from_task_model(task_model) extensions = TaskService.get_extensions_from_task_model(task_model)
task_model.signal_buttons = TaskService.get_ready_signals_with_button_labels(process_instance_id)
if "properties" in extensions: if "properties" in extensions:
properties = extensions["properties"] properties = extensions["properties"]

View File

@ -1,28 +0,0 @@
"""Get_process_info."""
from typing import Any
from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext,
)
from spiffworkflow_backend.scripts.script import Script
# DEPRECATED: please use GetToplevelProcessInfo instead
class GetProcessInfo(Script):
"""GetProcessInfo."""
@staticmethod
def requires_privileged_permissions() -> bool:
"""We have deemed this function safe to run without elevated permissions."""
return False
def get_description(self) -> str:
"""Get_description."""
return """Returns a dictionary of information about the currently running process."""
def run(self, script_attributes_context: ScriptAttributesContext, *_args: Any, **kwargs: Any) -> Any:
"""Run."""
return {
"process_instance_id": script_attributes_context.process_instance_id,
"process_model_identifier": script_attributes_context.process_model_identifier,
}

View File

@ -423,9 +423,9 @@ class ProcessInstanceProcessor:
tld.process_instance_id = process_instance_model.id tld.process_instance_id = process_instance_model.id
# we want this to be the fully qualified path to the process model including all group subcomponents # we want this to be the fully qualified path to the process model including all group subcomponents
current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( current_app.config[
f"{process_instance_model.process_model_identifier}" "THREAD_LOCAL_DATA"
) ].process_model_identifier = f"{process_instance_model.process_model_identifier}"
self.process_instance_model = process_instance_model self.process_instance_model = process_instance_model
self.process_model_service = ProcessModelService() self.process_model_service = ProcessModelService()
@ -585,9 +585,9 @@ class ProcessInstanceProcessor:
bpmn_subprocess_definition.bpmn_identifier bpmn_subprocess_definition.bpmn_identifier
] = bpmn_process_definition_dict ] = bpmn_process_definition_dict
spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {}
bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = ( bpmn_subprocess_definition_bpmn_identifiers[
bpmn_subprocess_definition.bpmn_identifier bpmn_subprocess_definition.id
) ] = bpmn_subprocess_definition.bpmn_identifier
task_definitions = TaskDefinitionModel.query.filter( task_definitions = TaskDefinitionModel.query.filter(
TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore

View File

@ -2,6 +2,7 @@ import copy
import json import json
import time import time
from hashlib import sha256 from hashlib import sha256
from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
from typing import TypedDict from typing import TypedDict
@ -607,6 +608,28 @@ class TaskService:
) )
return extensions return extensions
@classmethod
def get_ready_signals_with_button_labels(cls, process_instance_id: int) -> list[dict]:
waiting_tasks: List[TaskModel] = TaskModel.query.filter_by(
state="WAITING", process_instance_id=process_instance_id
).all()
result = []
for task_model in waiting_tasks:
task_definition = task_model.task_definition
extensions: dict = (
task_definition.properties_json["extensions"]
if "extensions" in task_definition.properties_json
else {}
)
event_definition: dict = (
task_definition.properties_json["event_definition"]
if "event_definition" in task_definition.properties_json
else {}
)
if "signalButtonLabel" in extensions and "name" in event_definition:
result.append({"event": event_definition, "label": extensions["signalButtonLabel"]})
return result
@classmethod @classmethod
def get_spec_reference_from_bpmn_process(cls, bpmn_process: BpmnProcessModel) -> SpecReferenceCache: def get_spec_reference_from_bpmn_process(cls, bpmn_process: BpmnProcessModel) -> SpecReferenceCache:
"""Get the bpmn file for a given task model. """Get the bpmn file for a given task model.

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="SpiffCatchEventExtensions" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0elszck</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0elszck" sourceRef="StartEvent_1" targetRef="Activity_0cmmlen" />
<bpmn:endEvent id="Event_1mjvim4">
<bpmn:incoming>Flow_1akz8b3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1akz8b3" sourceRef="Activity_0cmmlen" targetRef="Event_1mjvim4" />
<bpmn:sequenceFlow id="Flow_0uenxs3" sourceRef="SpamEvent" targetRef="Activity_1u4om4i" />
<bpmn:endEvent id="Event_1dvll15">
<bpmn:incoming>Flow_16bzuvz</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_16bzuvz" sourceRef="Activity_1u4om4i" targetRef="Event_1dvll15" />
<bpmn:manualTask id="Activity_0cmmlen" name="My Manual Task">
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser># Welcome
This manual task has Two Buttons! The first is standard submit button that will take you to the end. The second button will fire a signal event and take you to a different manual task.</spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0elszck</bpmn:incoming>
<bpmn:outgoing>Flow_1akz8b3</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:manualTask id="Activity_1u4om4i" name="Spam Message">
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser># Spam Eaten!
Congratulations! You have selected the Eat Additional Spam option, which opens up new doors to vast previously uncharted culinary eating experiences! Oh the Joy! Oh the Reward! Sweet savory wonderful Spam! </spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0uenxs3</bpmn:incoming>
<bpmn:outgoing>Flow_16bzuvz</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:boundaryEvent id="SpamEvent" name="Spam Event" attachedToRef="Activity_0cmmlen">
<bpmn:extensionElements>
<spiffworkflow:signalButtonLabel>Eat Spam</spiffworkflow:signalButtonLabel>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_0uenxs3</bpmn:outgoing>
<bpmn:signalEventDefinition id="SignalEventDefinition_11tlwya" signalRef="Signal_17t90lm" />
</bpmn:boundaryEvent>
</bpmn:process>
<bpmn:signal id="Signal_17t90lm" name="eat_spam" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_z1jgvu5">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1mjvim4_di" bpmnElement="Event_1mjvim4">
<dc:Bounds x="432" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1dvll15_di" bpmnElement="Event_1dvll15">
<dc:Bounds x="562" y="282" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0zxmtux_di" bpmnElement="Activity_0cmmlen">
<dc:Bounds x="270" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0tll58x_di" bpmnElement="Activity_1u4om4i">
<dc:Bounds x="410" y="260" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0vnraxp_di" bpmnElement="SpamEvent">
<dc:Bounds x="322" y="199" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="311" y="242" width="61" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0elszck_di" bpmnElement="Flow_0elszck">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1akz8b3_di" bpmnElement="Flow_1akz8b3">
<di:waypoint x="370" y="177" />
<di:waypoint x="432" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0uenxs3_di" bpmnElement="Flow_0uenxs3">
<di:waypoint x="340" y="235" />
<di:waypoint x="340" y="300" />
<di:waypoint x="410" y="300" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16bzuvz_di" bpmnElement="Flow_16bzuvz">
<di:waypoint x="510" y="300" />
<di:waypoint x="562" y="300" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -2662,7 +2662,8 @@ class TestProcessApi(BaseTest):
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json is not None assert response.json is not None
assert response.json["status"] == "complete" assert response.json["type"] == "End Event"
assert response.json["state"] == "COMPLETED"
response = client.get( response = client.get(
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/task-info?all_tasks=true", f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/task-info?all_tasks=true",

View File

@ -156,3 +156,30 @@ class TestTaskService(BaseTest):
assert task_model_level_3 is not None assert task_model_level_3 is not None
bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model_level_3) bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model_level_3)
assert bpmn_process.bpmn_process_definition.bpmn_identifier == "Level3" assert bpmn_process.bpmn_process_definition.bpmn_identifier == "Level3"
def test_get_button_labels_for_waiting_signal_event_tasks(
self,
app: Flask,
with_db_and_bpmn_file_cleanup: None,
) -> None:
process_model = load_test_spec(
"test_group/signal_event_extensions",
process_model_source_directory="signal_event_extensions",
bpmn_file_name="signal_event_extensions",
)
load_test_spec(
"test_group/SpiffCatchEventExtensions",
process_model_source_directory="call_activity_nested",
bpmn_file_name="SpiffCatchEventExtensions",
)
process_instance = self.create_process_instance_from_process_model(process_model)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
events = TaskService.get_ready_signals_with_button_labels(process_instance.id)
assert len(events) == 1
signal_event = events[0]
assert signal_event["event"]["name"] == "eat_spam"
assert signal_event["event"]["typename"] == "SignalEventDefinition"
assert signal_event["label"] == "Eat Spam"
print(events)

File diff suppressed because it is too large Load Diff

View File

@ -31,14 +31,14 @@
"autoprefixer": "10.4.8", "autoprefixer": "10.4.8",
"axios": "^0.27.2", "axios": "^0.27.2",
"bootstrap": "^5.2.0", "bootstrap": "^5.2.0",
"bpmn-js": "^9.3.2", "bpmn-js": "^13.0.0",
"bpmn-js-properties-panel": "^1.10.0", "bpmn-js-properties-panel": "^1.22.0",
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main", "bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"craco": "^0.0.3", "craco": "^0.0.3",
"cypress-slow-down": "^1.2.1", "cypress-slow-down": "^1.2.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"diagram-js": "^8.5.0", "diagram-js": "^11.9.1",
"dmn-js": "^12.2.0", "dmn-js": "^12.2.0",
"dmn-js-properties-panel": "^1.1", "dmn-js-properties-panel": "^1.1",
"dmn-js-shared": "^12.1.1", "dmn-js-shared": "^12.1.1",

View File

@ -1,7 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... Remove this comment to see the full error message
import BpmnModeler from 'bpmn-js/lib/Modeler'; import BpmnModeler from 'bpmn-js/lib/Modeler';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... Remove this comment to see the full error message
import BpmnViewer from 'bpmn-js/lib/Viewer'; import BpmnViewer from 'bpmn-js/lib/Viewer';
import { import {
BpmnPropertiesPanelModule, BpmnPropertiesPanelModule,
@ -144,11 +142,14 @@ export default function ReactDiagramEditor({
} }
const temp = document.createElement('template'); const temp = document.createElement('template');
const panelId: string =
diagramType === 'readonly'
? 'hidden-properties-panel'
: 'js-properties-panel';
temp.innerHTML = ` temp.innerHTML = `
<div class="content with-diagram" id="js-drop-zone"> <div class="content with-diagram" id="js-drop-zone">
<div class="canvas ${canvasClass}" id="canvas" <div class="canvas ${canvasClass}" id="canvas"></div>
></div> <div class="properties-panel-parent" id="${panelId}"></div>
<div class="properties-panel-parent" id="js-properties-panel"></div>
</div> </div>
`; `;
const frag = temp.content; const frag = temp.content;

View File

@ -37,6 +37,11 @@ export interface EventDefinition {
message_var?: string; message_var?: string;
} }
export interface SignalButton {
label: string;
event: EventDefinition;
}
// TODO: merge with ProcessInstanceTask // TODO: merge with ProcessInstanceTask
export interface Task { export interface Task {
id: number; id: number;
@ -60,6 +65,7 @@ export interface Task {
can_complete: boolean; can_complete: boolean;
form_schema: any; form_schema: any;
form_ui_schema: any; form_ui_schema: any;
signal_buttons: SignalButton[];
} }
export interface ProcessInstanceTask { export interface ProcessInstanceTask {

View File

@ -58,7 +58,6 @@ export default function ProcessInterstitial() {
// Added this seperate use effect so that the timer interval will be cleared if // Added this seperate use effect so that the timer interval will be cleared if
// we end up redirecting back to the TaskShow page. // we end up redirecting back to the TaskShow page.
if (shouldRedirect(lastTask)) { if (shouldRedirect(lastTask)) {
setState('REDIRECTING');
lastTask.properties.instructionsForEndUser = ''; lastTask.properties.instructionsForEndUser = '';
const timerId = setInterval(() => { const timerId = setInterval(() => {
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`); navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
@ -103,9 +102,9 @@ export default function ProcessInterstitial() {
const getReturnHomeButton = (index: number) => { const getReturnHomeButton = (index: number) => {
if ( if (
index === 0 && index === 0 &&
state !== 'REDIRECTING' && !shouldRedirect(lastTask) &&
['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus()) ['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus())
) ) {
return ( return (
<div style={{ padding: '10px 0 0 0' }}> <div style={{ padding: '10px 0 0 0' }}>
<Button kind="secondary" onClick={() => navigate(`/tasks`)}> <Button kind="secondary" onClick={() => navigate(`/tasks`)}>
@ -113,6 +112,7 @@ export default function ProcessInterstitial() {
</Button> </Button>
</div> </div>
); );
}
return ''; return '';
}; };
@ -165,7 +165,7 @@ export default function ProcessInterstitial() {
/** In the event there is no task information and the connection closed, /** In the event there is no task information and the connection closed,
* redirect to the home page. */ * redirect to the home page. */
if (state === 'closed' && lastTask === null) { if (state === 'CLOSED' && lastTask === null) {
navigate(`/tasks`); navigate(`/tasks`);
} }
if (lastTask) { if (lastTask) {

View File

@ -18,7 +18,7 @@ import Form from '../themes/carbon';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
import { modifyProcessIdentifierForPathParam } from '../helpers'; import { modifyProcessIdentifierForPathParam } from '../helpers';
import { Task } from '../interfaces'; import { EventDefinition, Task } from '../interfaces';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import InstructionsForEndUser from '../components/InstructionsForEndUser'; import InstructionsForEndUser from '../components/InstructionsForEndUser';
@ -195,6 +195,23 @@ export default function TaskShow() {
}); });
}; };
const handleSignalSubmit = (event: EventDefinition) => {
if (disabled || !task) {
return;
}
HttpService.makeCallToBackend({
path: `/send-event/${modifyProcessIdentifierForPathParam(
task.process_model_identifier
)}/${params.process_instance_id}`,
successCallback: processSubmitResult,
failureCallback: (error: any) => {
addError(error);
},
httpMethod: 'POST',
postBody: event,
});
};
const buildTaskNavigation = () => { const buildTaskNavigation = () => {
let userTasksElement; let userTasksElement;
let selectedTabIndex = 0; let selectedTabIndex = 0;
@ -355,6 +372,17 @@ export default function TaskShow() {
{submitButtonText} {submitButtonText}
</Button> </Button>
{saveAsDraftButton} {saveAsDraftButton}
<>
{task.signal_buttons.map((signal) => (
<Button
name="signal.signal"
disabled={disabled}
onClick={() => handleSignalSubmit(signal.event)}
>
{signal.label}
</Button>
))}
</>
</ButtonSet> </ButtonSet>
); );
} }