mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-26 17:59:04 +00:00
Bug/various bugs (#494)
* Don't bug out in older instances that don't have runtime_info * Add zoom buttons to React Diagram Editor * This removes some potential features for on-boarding, that we are not currently using, but fixes the issue with 100's of onboarding processes piling up and sitting around. Hoepfully we can wrap this into the extensions mechanism so everything works the same way eventually. * Improved error messages on form builder Don't try to auto-save the file before it is fully loaded. Example data was not getting saved on update. * Found several errors with new zooming buttons in DMN, so cleaning that up. Recent changes prevented creating a new dmn table. * Errors were not being displyed for the Editor Routes * Going to disable handling user tasks in the onboarding controller for now.
This commit is contained in:
parent
183e7ab379
commit
691611c695
@ -1,44 +1,47 @@
|
||||
"""APIs for dealing with process groups, process models, and process instances."""
|
||||
|
||||
from flask import g
|
||||
from flask import make_response
|
||||
from flask.wrappers import Response
|
||||
from SpiffWorkflow.exceptions import WorkflowException # type: ignore
|
||||
|
||||
from spiffworkflow_backend import db
|
||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||
from spiffworkflow_backend.routes.process_instances_controller import _process_instance_start
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import ProcessEntityNotFoundError
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.services.jinja_service import JinjaService
|
||||
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
|
||||
|
||||
|
||||
def get_onboarding() -> Response:
|
||||
result: dict = {}
|
||||
|
||||
persistence_level = "none" # Going to default this to none for now as we aren't using it interactively and its
|
||||
# creating a lot of extra data in the database and UI. We can revisit this later if we need to.
|
||||
# This is a short term fix that removes some of the potential benefits - such as routing users through an actual
|
||||
# workflow, asking questions, and saving information about them.
|
||||
# Hope to replace this with Extensions in the future.
|
||||
try:
|
||||
process_instance, processor = _process_instance_start("site-administration/onboarding")
|
||||
except ApiError:
|
||||
process_instance = ProcessInstanceModel(
|
||||
status=ProcessInstanceStatus.not_started.value,
|
||||
process_initiator_id=g.user.id,
|
||||
process_model_identifier="site-administration/onboarding",
|
||||
process_model_display_name="On Boarding",
|
||||
persistence_level=persistence_level,
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
except ProcessEntityNotFoundError:
|
||||
# The process doesn't exist, so bail out without an error
|
||||
return make_response(result, 200)
|
||||
try:
|
||||
processor.do_engine_steps(save=True, execution_strategy_name="greedy") # type: ignore
|
||||
if processor is not None:
|
||||
bpmn_process = processor.bpmn_process_instance
|
||||
if bpmn_process.is_completed():
|
||||
workflow_data = bpmn_process.data
|
||||
result = workflow_data.get("onboarding", {})
|
||||
# Delete the process instance, we don't need to keep this around if no users tasks were created.
|
||||
db.session.delete(process_instance)
|
||||
db.session.flush() # Clear it out BEFORE returning.
|
||||
elif len(bpmn_process.get_ready_user_tasks()) > 0:
|
||||
process_instance.persistence_level = "full"
|
||||
processor.save()
|
||||
result = {
|
||||
"type": "user_input_required",
|
||||
"process_instance_id": process_instance.id,
|
||||
}
|
||||
task = processor.next_task()
|
||||
if task:
|
||||
result["task_id"] = task.id
|
||||
result["instructions"] = JinjaService.render_instructions_for_end_user(task)
|
||||
processor.do_engine_steps(save=False, execution_strategy_name="greedy")
|
||||
bpmn_process = processor.bpmn_process_instance
|
||||
if bpmn_process.is_completed():
|
||||
workflow_data = bpmn_process.data
|
||||
result = workflow_data.get("onboarding", {})
|
||||
task = processor.next_task()
|
||||
if task:
|
||||
result["task_id"] = task.id
|
||||
result["instructions"] = JinjaService.render_instructions_for_end_user(task)
|
||||
except WorkflowException as e:
|
||||
raise ApiError.from_workflow_exception("onboard_failed", "Error building onboarding message", e) from e
|
||||
except Exception as e:
|
||||
|
@ -58,13 +58,16 @@ class TestOnboarding(BaseTest):
|
||||
# Assure no residual process model is left behind if it executes and completes without additinal user tasks
|
||||
assert len(ProcessInstanceModel.query.all()) == 0
|
||||
|
||||
def test_persists_if_user_task_encountered(
|
||||
def skip_test_persists_if_user_task_encountered(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""We are moving towards replacing the onboarding with Extensions
|
||||
so disabling this test, and the ability to start a person off on
|
||||
a workflow instantly on arrival."""
|
||||
self.set_up_onboarding(client, with_super_admin_user, "onboarding_with_user_task")
|
||||
results = client.get(
|
||||
"/v1.0/onboarding",
|
||||
|
@ -54,6 +54,7 @@ import TouchModule from 'diagram-js/lib/navigation/touch';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Can } from '@casl/react';
|
||||
import { ZoomIn, ZoomOut, ZoomFit } from '@carbon/icons-react';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
||||
@ -189,6 +190,7 @@ export default function ReactDiagramEditor({
|
||||
spiffworkflow,
|
||||
BpmnPropertiesPanelModule,
|
||||
BpmnPropertiesProviderModule,
|
||||
ZoomScrollModule,
|
||||
],
|
||||
moddleExtensions: {
|
||||
spiffworkflow: spiffModdleExtension,
|
||||
@ -207,6 +209,7 @@ export default function ReactDiagramEditor({
|
||||
additionalModules: [
|
||||
DmnPropertiesPanelModule,
|
||||
DmnPropertiesProviderModule,
|
||||
ZoomScrollModule,
|
||||
],
|
||||
},
|
||||
});
|
||||
@ -724,10 +727,67 @@ export default function ReactDiagramEditor({
|
||||
return null;
|
||||
};
|
||||
|
||||
const zoom = (amount: number) => {
|
||||
if (diagramModelerState) {
|
||||
let modeler = diagramModelerState as any;
|
||||
if (diagramType === 'dmn') {
|
||||
modeler = (diagramModelerState as any).getActiveViewer();
|
||||
}
|
||||
try {
|
||||
if (amount === 0) {
|
||||
const canvas = (modeler as any).get('canvas');
|
||||
canvas.zoom(FitViewport, 'auto');
|
||||
} else {
|
||||
modeler.get('zoomScroll').stepZoom(amount);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'zoom failed, certain modes in DMN do not support zooming.',
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const diagramControlButtons = () => {
|
||||
return (
|
||||
<div className="diagram-control-buttons">
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={ZoomIn}
|
||||
iconDescription="Zoom In"
|
||||
hasIconOnly
|
||||
onClick={() => {
|
||||
zoom(1);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={ZoomOut}
|
||||
iconDescription="Zoom Out"
|
||||
hasIconOnly
|
||||
onClick={() => {
|
||||
zoom(-1);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={ZoomFit}
|
||||
iconDescription="Zoom Fit"
|
||||
hasIconOnly
|
||||
onClick={() => {
|
||||
zoom(0);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{userActionOptions()}
|
||||
{showReferences()}
|
||||
{diagramControlButtons()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -90,7 +90,9 @@ export default function ReactFormBuilder({
|
||||
HttpService.makeCallToBackend({
|
||||
path: url,
|
||||
successCallback: () => {},
|
||||
failureCallback: () => {}, // fixme: handle errors
|
||||
failureCallback: (e: any) => {
|
||||
setErrorMessage(`Failed to save file: '${fileName}'. ${e.message}`);
|
||||
},
|
||||
httpMethod,
|
||||
postBody: submission,
|
||||
});
|
||||
@ -112,7 +114,11 @@ export default function ReactFormBuilder({
|
||||
if (ready) {
|
||||
return true;
|
||||
}
|
||||
if (strSchema !== '' && strUI !== '' && strFormData !== '') {
|
||||
if (
|
||||
debouncedStrSchema !== '' &&
|
||||
debouncedStrUI !== '' &&
|
||||
debouncedFormData !== ''
|
||||
) {
|
||||
setReady(true);
|
||||
return true;
|
||||
}
|
||||
@ -121,24 +127,24 @@ export default function ReactFormBuilder({
|
||||
|
||||
// Auto save schema changes
|
||||
useEffect(() => {
|
||||
if (baseFileName !== '') {
|
||||
if (baseFileName !== '' && ready) {
|
||||
saveFile(new File([debouncedStrSchema], baseFileName + SCHEMA_EXTENSION));
|
||||
}
|
||||
}, [debouncedStrSchema, baseFileName, saveFile]);
|
||||
}, [debouncedStrSchema, baseFileName, saveFile, ready]);
|
||||
|
||||
// Auto save ui changes
|
||||
useEffect(() => {
|
||||
if (baseFileName !== '') {
|
||||
if (baseFileName !== '' && ready) {
|
||||
saveFile(new File([debouncedStrUI], baseFileName + UI_EXTENSION));
|
||||
}
|
||||
}, [debouncedStrUI, baseFileName, saveFile]);
|
||||
}, [debouncedStrUI, baseFileName, saveFile, ready]);
|
||||
|
||||
// Auto save example data changes
|
||||
useEffect(() => {
|
||||
if (baseFileName !== '') {
|
||||
saveFile(new File([debouncedFormData], baseFileName + DATA_EXTENSION));
|
||||
}
|
||||
}, [debouncedFormData, baseFileName, saveFile]);
|
||||
}, [debouncedFormData, baseFileName, saveFile, ready]);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
@ -173,7 +179,7 @@ export default function ReactFormBuilder({
|
||||
try {
|
||||
data = JSON.parse(debouncedFormData);
|
||||
} catch (e) {
|
||||
setErrorMessage('Please check the Task Data for errors.');
|
||||
setErrorMessage('Please check the Data View for errors.');
|
||||
return;
|
||||
}
|
||||
setErrorMessage('');
|
||||
@ -224,6 +230,7 @@ export default function ReactFormBuilder({
|
||||
// @ts-ignore
|
||||
value !== dataEditorRef.current.getValue()
|
||||
) {
|
||||
setStrFormData(value);
|
||||
// @ts-ignore
|
||||
dataEditorRef.current.setValue(value);
|
||||
}
|
||||
@ -289,7 +296,9 @@ export default function ReactFormBuilder({
|
||||
fileName
|
||||
)}${UI_EXTENSION}`,
|
||||
successCallback: setJsonUiFromResponseJson,
|
||||
failureCallback: () => {},
|
||||
failureCallback: () => {
|
||||
setJsonUiFromResponseJson({ file_contents: '{}' });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -299,7 +308,9 @@ export default function ReactFormBuilder({
|
||||
fileName
|
||||
)}${DATA_EXTENSION}`,
|
||||
successCallback: setDataFromResponseJson,
|
||||
failureCallback: () => {},
|
||||
failureCallback: () => {
|
||||
setDataFromResponseJson({ file_contents: '{}' });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -449,7 +460,7 @@ export default function ReactFormBuilder({
|
||||
</Column>
|
||||
<Column sm={4} md={5} lg={8}>
|
||||
<h2>Form Preview</h2>
|
||||
<div>{errorMessage}</div>
|
||||
<div className="error_info_small">{errorMessage}</div>
|
||||
<ErrorBoundary>
|
||||
<CustomForm
|
||||
id="custom_form"
|
||||
|
@ -590,6 +590,11 @@ svg.notification-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.error_info_small {
|
||||
font-size: 0.8em;
|
||||
color: #550000;
|
||||
}
|
||||
|
||||
.please-press-filter-button {
|
||||
margin-bottom: 1rem;
|
||||
font-weight: bold;
|
||||
|
@ -3,6 +3,7 @@ import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
import React, { useEffect } from 'react';
|
||||
import ProcessModelEditDiagram from './ProcessModelEditDiagram';
|
||||
import UserService from '../services/UserService';
|
||||
import ErrorDisplay from '../components/ErrorDisplay';
|
||||
|
||||
export default function EditorRoutes() {
|
||||
const location = useLocation();
|
||||
@ -12,6 +13,7 @@ export default function EditorRoutes() {
|
||||
if (UserService.hasRole(['admin'])) {
|
||||
return (
|
||||
<div className="full-width-container no-center-stuff">
|
||||
<ErrorDisplay />
|
||||
<Routes>
|
||||
<Route
|
||||
path="process-models/:process_model_id/files"
|
||||
|
@ -1138,7 +1138,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
};
|
||||
|
||||
const multiInstanceSelector = () => {
|
||||
if (!taskToDisplay) {
|
||||
if (!taskToDisplay || !taskToDisplay.runtime_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1249,17 +1249,18 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
onRequestSubmit = addPotentialOwners;
|
||||
dangerous = true;
|
||||
}
|
||||
|
||||
if (typeof taskToUse.runtime_info.instance !== 'undefined') {
|
||||
secondaryButtonText = 'Return to MultiInstance Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
} else if (typeof taskToUse.runtime_info.iteration !== 'undefined') {
|
||||
secondaryButtonText = 'Return to Loop Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
if (taskToUse.runtime_info) {
|
||||
if (typeof taskToUse.runtime_info.instance !== 'undefined') {
|
||||
secondaryButtonText = 'Return to MultiInstance Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
} else if (typeof taskToUse.runtime_info.iteration !== 'undefined') {
|
||||
secondaryButtonText = 'Return to Loop Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1035,8 +1035,9 @@ export default function ProcessModelEditDiagram() {
|
||||
|
||||
const onLaunchDmnEditor = (processId: string) => {
|
||||
const file = findFileNameForReferenceId(processId, 'dmn');
|
||||
let path = '';
|
||||
if (file) {
|
||||
const path = generatePath(
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files/:file_name',
|
||||
{
|
||||
process_model_id: params.process_model_id,
|
||||
@ -1044,7 +1045,15 @@ export default function ProcessModelEditDiagram() {
|
||||
}
|
||||
);
|
||||
window.open(path);
|
||||
} else {
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files?file_type=dmn',
|
||||
{
|
||||
process_model_id: params.process_model_id,
|
||||
}
|
||||
);
|
||||
}
|
||||
window.open(path);
|
||||
};
|
||||
|
||||
const isDmn = () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user