working but barely functional UI for manually sending events
This commit is contained in:
parent
c821b2ad17
commit
232ff1c6f7
|
@ -586,6 +586,27 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Workflow"
|
$ref: "#/components/schemas/Workflow"
|
||||||
|
|
||||||
|
/process-instances/{process_instance_id}/event:
|
||||||
|
parameters:
|
||||||
|
- name: process_instance_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The unique id of the process instance
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
post:
|
||||||
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.send_bpmn_event
|
||||||
|
summary: Send a BPMN event to the process
|
||||||
|
tags:
|
||||||
|
- Process Instances
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Event Sent Successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Workflow"
|
||||||
|
|
||||||
/process-models/{process_group_id}/{process_model_id}/script-unit-tests:
|
/process-models/{process_group_id}/{process_model_id}/script-unit-tests:
|
||||||
parameters:
|
parameters:
|
||||||
- name: process_group_id
|
- name: process_group_id
|
||||||
|
|
|
@ -118,6 +118,7 @@ class Task:
|
||||||
form_schema: Union[str, None] = None,
|
form_schema: Union[str, None] = None,
|
||||||
form_ui_schema: Union[str, None] = None,
|
form_ui_schema: Union[str, None] = None,
|
||||||
parent: Optional[str] = None,
|
parent: Optional[str] = None,
|
||||||
|
event_definition: Union[dict[str, Any], None] = None
|
||||||
):
|
):
|
||||||
"""__init__."""
|
"""__init__."""
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -129,6 +130,7 @@ class Task:
|
||||||
self.documentation = documentation
|
self.documentation = documentation
|
||||||
self.lane = lane
|
self.lane = lane
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.event_definition = event_definition
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
|
@ -187,6 +189,7 @@ class Task:
|
||||||
"form_schema": self.form_schema,
|
"form_schema": self.form_schema,
|
||||||
"form_ui_schema": self.form_ui_schema,
|
"form_ui_schema": self.form_ui_schema,
|
||||||
"parent": self.parent,
|
"parent": self.parent,
|
||||||
|
"event_definition": self.event_definition,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -287,6 +290,7 @@ class TaskSchema(Schema):
|
||||||
"process_instance_id",
|
"process_instance_id",
|
||||||
"form_schema",
|
"form_schema",
|
||||||
"form_ui_schema",
|
"form_ui_schema",
|
||||||
|
"event_definition",
|
||||||
]
|
]
|
||||||
|
|
||||||
multi_instance_type = EnumField(MultiInstanceType)
|
multi_instance_type = EnumField(MultiInstanceType)
|
||||||
|
|
|
@ -1311,7 +1311,7 @@ def process_instance_task_list(
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for spiff_task in spiff_tasks:
|
for spiff_task in spiff_tasks:
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
task.data = spiff_task.data
|
task.data = spiff_task.data
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
|
||||||
|
@ -1344,7 +1344,9 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
||||||
form_schema_file_name = properties["formJsonSchemaFilename"]
|
form_schema_file_name = properties["formJsonSchemaFilename"]
|
||||||
if "formUiSchemaFilename" in properties:
|
if "formUiSchemaFilename" in properties:
|
||||||
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
task.data = spiff_task.data
|
task.data = spiff_task.data
|
||||||
task.process_model_display_name = process_model.display_name
|
task.process_model_display_name = process_model.display_name
|
||||||
task.process_model_identifier = process_model.id
|
task.process_model_identifier = process_model.id
|
||||||
|
@ -1882,3 +1884,21 @@ def update_task_data(process_instance_id: str, task_id: str, body: Dict) -> Resp
|
||||||
status=200,
|
status=200,
|
||||||
mimetype="application/json",
|
mimetype="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_bpmn_event(process_instance_id: str, body: Dict) -> Response:
|
||||||
|
process_instance = ProcessInstanceModel.query.filter(
|
||||||
|
ProcessInstanceModel.id == int(process_instance_id)
|
||||||
|
).first()
|
||||||
|
if process_instance:
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
processor.send_bpmn_event(body)
|
||||||
|
else:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="send_bpmn_event_error",
|
||||||
|
message=f"Could not send event to Instance: {process_instance_id}",
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
||||||
|
|
|
@ -45,9 +45,8 @@ from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import EndEventConverter
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import EndEventConverter
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
||||||
IntermediateCatchEventConverter,
|
IntermediateCatchEventConverter,
|
||||||
)
|
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
|
||||||
IntermediateThrowEventConverter,
|
IntermediateThrowEventConverter,
|
||||||
|
EventBasedGatewayConverter,
|
||||||
)
|
)
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import ManualTaskConverter
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import ManualTaskConverter
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import NoneTaskConverter
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import NoneTaskConverter
|
||||||
|
@ -254,6 +253,7 @@ class ProcessInstanceProcessor:
|
||||||
EndEventConverter,
|
EndEventConverter,
|
||||||
IntermediateCatchEventConverter,
|
IntermediateCatchEventConverter,
|
||||||
IntermediateThrowEventConverter,
|
IntermediateThrowEventConverter,
|
||||||
|
EventBasedGatewayConverter,
|
||||||
ManualTaskConverter,
|
ManualTaskConverter,
|
||||||
NoneTaskConverter,
|
NoneTaskConverter,
|
||||||
ReceiveTaskConverter,
|
ReceiveTaskConverter,
|
||||||
|
@ -267,6 +267,7 @@ class ProcessInstanceProcessor:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
_serializer = BpmnWorkflowSerializer(wf_spec_converter, version=SERIALIZER_VERSION)
|
_serializer = BpmnWorkflowSerializer(wf_spec_converter, version=SERIALIZER_VERSION)
|
||||||
|
_event_serializer = EventBasedGatewayConverter()
|
||||||
|
|
||||||
PROCESS_INSTANCE_ID_KEY = "process_instance_id"
|
PROCESS_INSTANCE_ID_KEY = "process_instance_id"
|
||||||
VALIDATION_PROCESS_KEY = "validate_only"
|
VALIDATION_PROCESS_KEY = "validate_only"
|
||||||
|
@ -658,6 +659,18 @@ class ProcessInstanceProcessor:
|
||||||
db.session.delete(at)
|
db.session.delete(at)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
def serialize_task_spec(self, task_spec: SpiffTask) -> dict[str, Any]:
|
||||||
|
return self._serializer.spec_converter.convert(task_spec)
|
||||||
|
|
||||||
|
def send_bpmn_event(self, event_data: dict[str,Any]) -> None:
|
||||||
|
payload = event_data.pop("payload", None)
|
||||||
|
event_definition = self._event_serializer.restore(event_data)
|
||||||
|
if payload is not None:
|
||||||
|
event_definition.payload = payload
|
||||||
|
current_app.logger.info(f"Event of type {event_definition.event_type} sent to process instance {self.process_instance_model.id}")
|
||||||
|
self.bpmn_process_instance.catch(event_definition)
|
||||||
|
self.do_engine_steps(save=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_parser() -> MyCustomParser:
|
def get_parser() -> MyCustomParser:
|
||||||
"""Get_parser."""
|
"""Get_parser."""
|
||||||
|
|
|
@ -121,7 +121,7 @@ class ProcessInstanceService:
|
||||||
if next_task_trying_again is not None:
|
if next_task_trying_again is not None:
|
||||||
process_instance_api.next_task = (
|
process_instance_api.next_task = (
|
||||||
ProcessInstanceService.spiff_task_to_api_task(
|
ProcessInstanceService.spiff_task_to_api_task(
|
||||||
next_task_trying_again, add_docs_and_forms=True
|
processor, next_task_trying_again, add_docs_and_forms=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -277,7 +277,9 @@ class ProcessInstanceService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def spiff_task_to_api_task(
|
def spiff_task_to_api_task(
|
||||||
spiff_task: SpiffTask, add_docs_and_forms: bool = False
|
processor: ProcessInstanceProcessor,
|
||||||
|
spiff_task: SpiffTask,
|
||||||
|
add_docs_and_forms: bool = False
|
||||||
) -> Task:
|
) -> Task:
|
||||||
"""Spiff_task_to_api_task."""
|
"""Spiff_task_to_api_task."""
|
||||||
task_type = spiff_task.task_spec.spec_type
|
task_type = spiff_task.task_spec.spec_type
|
||||||
|
@ -306,6 +308,8 @@ class ProcessInstanceService:
|
||||||
if spiff_task.parent:
|
if spiff_task.parent:
|
||||||
parent_id = spiff_task.parent.id
|
parent_id = spiff_task.parent.id
|
||||||
|
|
||||||
|
serialized_task_spec = processor.serialize_task_spec(spiff_task.task_spec)
|
||||||
|
|
||||||
task = Task(
|
task = Task(
|
||||||
spiff_task.id,
|
spiff_task.id,
|
||||||
spiff_task.task_spec.name,
|
spiff_task.task_spec.name,
|
||||||
|
@ -319,6 +323,7 @@ class ProcessInstanceService:
|
||||||
process_name=spiff_task.task_spec._wf_spec.description,
|
process_name=spiff_task.task_spec._wf_spec.description,
|
||||||
properties=props,
|
properties=props,
|
||||||
parent=parent_id,
|
parent=parent_id,
|
||||||
|
event_definition=serialized_task_spec.get("event_definition"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
ButtonSet,
|
ButtonSet,
|
||||||
Tag,
|
Tag,
|
||||||
Modal,
|
Modal,
|
||||||
|
Dropdown,
|
||||||
Stack,
|
Stack,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
|
@ -47,6 +48,9 @@ export default function ProcessInstanceShow() {
|
||||||
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
||||||
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
||||||
const [editingTaskData, setEditingTaskData] = useState<boolean>(false);
|
const [editingTaskData, setEditingTaskData] = useState<boolean>(false);
|
||||||
|
const [selectingEvent, setSelectingEvent] = useState<boolean>(false);
|
||||||
|
const [eventToSend, setEventToSend] = useState<any>({});
|
||||||
|
const [eventPayload, setEventPayload] = useState<string>('{}');
|
||||||
|
|
||||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||||
|
|
||||||
|
@ -404,8 +408,34 @@ export default function ProcessInstanceShow() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelEditingTaskData = () => {
|
const canSendEvent = (task: any) => {
|
||||||
|
// We actually could allow this for any waiting events
|
||||||
|
const taskTypes = ['Event Based Gateway'];
|
||||||
|
return (
|
||||||
|
taskTypes.filter((t) => t === task.type).length > 0 &&
|
||||||
|
task.state === 'WAITING' &&
|
||||||
|
showingLastSpiffStep(processInstance as any)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEvents = (task: any) => {
|
||||||
|
const handleMessage = (eventDefinition: any) => {
|
||||||
|
if (eventDefinition.typename === 'MessageEventDefinition') {
|
||||||
|
delete eventDefinition.message_var;
|
||||||
|
eventDefinition.payload = {};
|
||||||
|
}
|
||||||
|
return eventDefinition;
|
||||||
|
};
|
||||||
|
if (task.event_definition && task.event_definition.event_definitions)
|
||||||
|
return task.event_definition.event_definitions.map((e: any) => handleMessage(e));
|
||||||
|
if (task.event_definition)
|
||||||
|
return [handleMessage(task.event_definition)];
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelUpdatingTask = () => {
|
||||||
setEditingTaskData(false);
|
setEditingTaskData(false);
|
||||||
|
setSelectingEvent(false);
|
||||||
initializeTaskDataToDisplay(taskToDisplay);
|
initializeTaskDataToDisplay(taskToDisplay);
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
};
|
};
|
||||||
|
@ -446,6 +476,18 @@ export default function ProcessInstanceShow() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendEvent = () => {
|
||||||
|
if ('payload' in eventToSend)
|
||||||
|
eventToSend.payload = JSON.parse(eventPayload);
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-instances/${params.process_instance_id}/event`,
|
||||||
|
httpMethod: 'POST',
|
||||||
|
successCallback: saveTaskDataResult,
|
||||||
|
failureCallback: saveTaskDataFailure,
|
||||||
|
postBody: eventToSend,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const taskDataButtons = (task: any) => {
|
const taskDataButtons = (task: any) => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
|
@ -460,7 +502,7 @@ export default function ProcessInstanceShow() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canEditTaskData(task)) {
|
if (canEditTaskData(task) || canSendEvent(task)) {
|
||||||
if (editingTaskData) {
|
if (editingTaskData) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
|
@ -473,7 +515,24 @@ export default function ProcessInstanceShow() {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
data-qa="create-script-unit-test-button"
|
data-qa="create-script-unit-test-button"
|
||||||
onClick={cancelEditingTaskData}
|
onClick={cancelUpdatingTask}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else if (selectingEvent) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
data-qa="create-script-unit-test-button"
|
||||||
|
onClick={sendEvent}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
data-qa="create-script-unit-test-button"
|
||||||
|
onClick={cancelUpdatingTask}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -487,6 +546,16 @@ export default function ProcessInstanceShow() {
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
if (canSendEvent(task)) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
data-qa="create-script-unit-test-button"
|
||||||
|
onClick={() => setSelectingEvent(true)}
|
||||||
|
>
|
||||||
|
Send Event
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,8 +576,35 @@ export default function ProcessInstanceShow() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskDataDisplayArea = () => {
|
const eventSelector = (candidateEvents: any) => {
|
||||||
|
const editor = (
|
||||||
|
<Editor
|
||||||
|
height={300}
|
||||||
|
width="auto"
|
||||||
|
defaultLanguage="json"
|
||||||
|
onChange={(value: any) => setEventPayload(value || '{}')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
return selectingEvent ? (
|
||||||
|
<Stack orientation="vertical">
|
||||||
|
<Dropdown
|
||||||
|
id="process-instance-select-event"
|
||||||
|
titleText="Event"
|
||||||
|
label="Select Event"
|
||||||
|
items={candidateEvents}
|
||||||
|
itemToString={(item: any) => item.name || item.label || item.typename}
|
||||||
|
onChange={(value: any) => setEventToSend(value.selectedItem)}
|
||||||
|
/>
|
||||||
|
{'payload' in eventToSend ? editor : ''}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
taskDataContainer()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const taskUpdateDisplayArea = () => {
|
||||||
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
|
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
|
||||||
|
const candidateEvents: any = getEvents(taskToUse);
|
||||||
if (taskToDisplay) {
|
if (taskToDisplay) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -520,7 +616,7 @@ export default function ProcessInstanceShow() {
|
||||||
{taskToUse.name} ({taskToUse.type}): {taskToUse.state}
|
{taskToUse.name} ({taskToUse.type}): {taskToUse.state}
|
||||||
{taskDataButtons(taskToUse)}
|
{taskDataButtons(taskToUse)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{taskDataContainer()}
|
{selectingEvent ? eventSelector(candidateEvents) : taskDataContainer()}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -592,7 +688,7 @@ export default function ProcessInstanceShow() {
|
||||||
<br />
|
<br />
|
||||||
{getInfoTag(processInstanceToUse)}
|
{getInfoTag(processInstanceToUse)}
|
||||||
<br />
|
<br />
|
||||||
{taskDataDisplayArea()}
|
{taskUpdateDisplayArea()}
|
||||||
{stepsElement(processInstanceToUse)}
|
{stepsElement(processInstanceToUse)}
|
||||||
<br />
|
<br />
|
||||||
<ReactDiagramEditor
|
<ReactDiagramEditor
|
||||||
|
|
Loading…
Reference in New Issue