disable form submit buttons when appropriate, lock process instance when sending events, and ensure return events match ones associated with desired guids w/ burnettk (#359)
Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
15b2947107
commit
3a8cfd2642
|
@ -1920,6 +1920,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
# NOTE: this should probably be /process-instances instead
|
||||
/tasks/{process_instance_id}/send-user-signal-event:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -90,6 +92,12 @@ class TaskModel(SpiffworkflowBaseDBModel):
|
|||
def json_data(self) -> dict:
|
||||
return JsonDataModel.find_data_dict_by_hash(self.json_data_hash)
|
||||
|
||||
def parent_task_model(self) -> TaskModel | None:
|
||||
if "parent" not in self.properties_json:
|
||||
return None
|
||||
task_model: TaskModel = self.__class__.query.filter_by(guid=self.properties_json["parent"]).first()
|
||||
return task_model
|
||||
|
||||
|
||||
class Task:
|
||||
HUMAN_TASK_TYPES = ["User Task", "Manual Task"]
|
||||
|
|
|
@ -639,7 +639,16 @@ def send_bpmn_event(
|
|||
|
||||
def _send_bpmn_event(process_instance: ProcessInstanceModel, body: dict) -> Response:
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.send_bpmn_event(body)
|
||||
try:
|
||||
with ProcessInstanceQueueService.dequeued(process_instance):
|
||||
processor.send_bpmn_event(body)
|
||||
except (
|
||||
ProcessInstanceIsNotEnqueuedError,
|
||||
ProcessInstanceIsAlreadyLockedError,
|
||||
) as e:
|
||||
ErrorHandlingService.handle_error(process_instance, e)
|
||||
raise e
|
||||
|
||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
||||
return make_response(jsonify(task), 200)
|
||||
|
||||
|
|
|
@ -268,7 +268,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_definition = task_model.task_definition
|
||||
extensions = TaskService.get_extensions_from_task_model(task_model)
|
||||
task_model.signal_buttons = TaskService.get_ready_signals_with_button_labels(process_instance_id)
|
||||
task_model.signal_buttons = TaskService.get_ready_signals_with_button_labels(process_instance_id, task_model.guid)
|
||||
|
||||
if "properties" in extensions:
|
||||
properties = extensions["properties"]
|
||||
|
|
|
@ -642,7 +642,7 @@ class TaskService:
|
|||
return extensions
|
||||
|
||||
@classmethod
|
||||
def get_ready_signals_with_button_labels(cls, process_instance_id: int) -> list[dict]:
|
||||
def get_ready_signals_with_button_labels(cls, process_instance_id: int, associated_task_guid: str) -> list[dict]:
|
||||
waiting_tasks: list[TaskModel] = TaskModel.query.filter_by(
|
||||
state="WAITING", process_instance_id=process_instance_id
|
||||
).all()
|
||||
|
@ -660,7 +660,13 @@ class TaskService:
|
|||
else {}
|
||||
)
|
||||
if "signalButtonLabel" in extensions and "name" in event_definition:
|
||||
result.append({"event": event_definition, "label": extensions["signalButtonLabel"]})
|
||||
parent_task_model = task_model.parent_task_model()
|
||||
if (
|
||||
parent_task_model
|
||||
and "children" in parent_task_model.properties_json
|
||||
and associated_task_guid in parent_task_model.properties_json["children"]
|
||||
):
|
||||
result.append({"event": event_definition, "label": extensions["signalButtonLabel"]})
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
<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:sequenceFlow id="Flow_0elszck" sourceRef="StartEvent_1" targetRef="my_manual_task" />
|
||||
<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_1akz8b3" sourceRef="my_manual_task" 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:manualTask id="my_manual_task" 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>
|
||||
|
@ -30,7 +30,7 @@ Congratulations! You have selected the Eat Additional Spam option, which opens
|
|||
<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:boundaryEvent id="SpamEvent" name="Spam Event" attachedToRef="my_manual_task">
|
||||
<bpmn:extensionElements>
|
||||
<spiffworkflow:signalButtonLabel>Eat Spam</spiffworkflow:signalButtonLabel>
|
||||
</bpmn:extensionElements>
|
||||
|
@ -50,7 +50,7 @@ Congratulations! You have selected the Eat Additional Spam option, which opens
|
|||
<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">
|
||||
<bpmndi:BPMNShape id="Activity_0zxmtux_di" bpmnElement="my_manual_task">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
|
|
|
@ -170,7 +170,10 @@ class TestTaskService(BaseTest):
|
|||
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)
|
||||
spiff_task = processor.__class__.get_task_by_bpmn_identifier("my_manual_task", processor.bpmn_process_instance)
|
||||
assert spiff_task is not None
|
||||
|
||||
events = TaskService.get_ready_signals_with_button_labels(process_instance.id, str(spiff_task.id))
|
||||
assert len(events) == 1
|
||||
signal_event = events[0]
|
||||
assert signal_event["event"]["name"] == "eat_spam"
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function TaskShow() {
|
|||
const [userTasks] = useState(null);
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [formButtonsDisabled, setFormButtonsDisabled] = useState(false);
|
||||
|
||||
const [taskData, setTaskData] = useState<any>(null);
|
||||
const [autosaveOnFormChanges, setAutosaveOnFormChanges] =
|
||||
|
@ -56,7 +56,7 @@ export default function TaskShow() {
|
|||
// convert null back to undefined so rjsf doesn't attempt to incorrectly validate them
|
||||
const taskDataToUse = result.saved_form_data || result.data;
|
||||
setTaskData(recursivelyChangeNullAndUndefined(taskDataToUse, undefined));
|
||||
setDisabled(false);
|
||||
setFormButtonsDisabled(false);
|
||||
if (!result.can_complete) {
|
||||
navigateToInterstitial(result);
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ export default function TaskShow() {
|
|||
navigateToInterstitial(result);
|
||||
}
|
||||
} else {
|
||||
setFormButtonsDisabled(false);
|
||||
addError(result);
|
||||
}
|
||||
};
|
||||
|
@ -136,7 +137,7 @@ export default function TaskShow() {
|
|||
};
|
||||
|
||||
const handleFormSubmit = (formObject: any, _event: any) => {
|
||||
if (disabled) {
|
||||
if (formButtonsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -148,7 +149,7 @@ export default function TaskShow() {
|
|||
}
|
||||
const queryParams = '';
|
||||
|
||||
setDisabled(true);
|
||||
setFormButtonsDisabled(true);
|
||||
removeError();
|
||||
delete dataToSubmit.isManualTask;
|
||||
|
||||
|
@ -168,9 +169,10 @@ export default function TaskShow() {
|
|||
};
|
||||
|
||||
const handleSignalSubmit = (event: EventDefinition) => {
|
||||
if (disabled || !task) {
|
||||
if (formButtonsDisabled || !task) {
|
||||
return;
|
||||
}
|
||||
setFormButtonsDisabled(true);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/send-user-signal-event`,
|
||||
successCallback: processSubmitResult,
|
||||
|
@ -206,7 +208,7 @@ export default function TaskShow() {
|
|||
);
|
||||
}
|
||||
if (userTask.state === 'FUTURE') {
|
||||
return <Tab disabled>{userTask.name_for_display}</Tab>;
|
||||
return <Tab formButtonsDisabled>{userTask.name_for_display}</Tab>;
|
||||
}
|
||||
if (userTask.state === 'READY') {
|
||||
return (
|
||||
|
@ -359,6 +361,7 @@ export default function TaskShow() {
|
|||
|
||||
const handleCloseButton = () => {
|
||||
setAutosaveOnFormChanges(false);
|
||||
setFormButtonsDisabled(true);
|
||||
const successCallback = () => navigate(`/tasks`);
|
||||
sendAutosaveEvent({ successCallback });
|
||||
};
|
||||
|
@ -413,7 +416,7 @@ export default function TaskShow() {
|
|||
<Button
|
||||
id="close-button"
|
||||
onClick={handleCloseButton}
|
||||
disabled={disabled}
|
||||
disabled={formButtonsDisabled}
|
||||
kind="secondary"
|
||||
title="Save data as draft and close the form."
|
||||
>
|
||||
|
@ -423,7 +426,11 @@ export default function TaskShow() {
|
|||
}
|
||||
reactFragmentToHideSubmitButton = (
|
||||
<ButtonSet>
|
||||
<Button type="submit" id="submit-button" disabled={disabled}>
|
||||
<Button
|
||||
type="submit"
|
||||
id="submit-button"
|
||||
disabled={formButtonsDisabled}
|
||||
>
|
||||
{submitButtonText}
|
||||
</Button>
|
||||
{closeButton}
|
||||
|
@ -431,7 +438,7 @@ export default function TaskShow() {
|
|||
{task.signal_buttons.map((signal) => (
|
||||
<Button
|
||||
name="signal.signal"
|
||||
disabled={disabled}
|
||||
disabled={formButtonsDisabled}
|
||||
onClick={() => handleSignalSubmit(signal.event)}
|
||||
>
|
||||
{signal.label}
|
||||
|
@ -456,7 +463,7 @@ export default function TaskShow() {
|
|||
<Column sm={4} md={5} lg={8}>
|
||||
<Form
|
||||
id="form-to-submit"
|
||||
disabled={disabled}
|
||||
disabled={formButtonsDisabled}
|
||||
formData={taskData}
|
||||
onChange={(obj: any) => {
|
||||
setTaskData(obj.formData);
|
||||
|
|
Loading…
Reference in New Issue