This commit is contained in:
Dan 2023-04-19 17:00:09 -04:00
parent b1568fb472
commit a8fd0a246d
9 changed files with 32 additions and 65 deletions

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import uuid
from sys import exc_info
from typing import Any
from typing import Dict
from typing import Generator
from typing import Optional
from typing import TypedDict
from typing import Union
@ -353,7 +354,7 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
return make_response(jsonify(task), 200)
def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task):
def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task) -> str:
"""Assure any instructions for end user are processed for jinja syntax."""
if task.properties and "instructionsForEndUser" in task.properties:
if task.properties["instructionsForEndUser"]:
@ -391,7 +392,7 @@ def process_data_show(
)
def _interstitial_stream(process_instance_id: int):
def _interstitial_stream(process_instance_id: int) -> Generator[str, str, None]:
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
processor = ProcessInstanceProcessor(process_instance)
reported_ids = [] # bit of an issue with end tasks showing as getting completed twice.
@ -415,9 +416,8 @@ def _interstitial_stream(process_instance_id: int):
processor.save() # Fixme - maybe find a way not to do this on every method?
def interstitial(process_instance_id: int):
"""A Server Side Events Stream for watching the execution of engine tasks in a
process instance."""
def interstitial(process_instance_id: int) -> Response:
"""A Server Side Events Stream for watching the execution of engine tasks."""
return Response(stream_with_context(_interstitial_stream(process_instance_id)), mimetype="text/event-stream")

View File

@ -433,9 +433,9 @@ class ProcessInstanceProcessor:
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
current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = (
f"{process_instance_model.process_model_identifier}"
)
current_app.config[
"THREAD_LOCAL_DATA"
].process_model_identifier = f"{process_instance_model.process_model_identifier}"
self.process_instance_model = process_instance_model
self.process_model_service = ProcessModelService()
@ -595,9 +595,9 @@ class ProcessInstanceProcessor:
bpmn_subprocess_definition.bpmn_identifier
] = bpmn_process_definition_dict
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_identifier
)
bpmn_subprocess_definition_bpmn_identifiers[
bpmn_subprocess_definition.id
] = bpmn_subprocess_definition.bpmn_identifier
task_definitions = TaskDefinitionModel.query.filter(
TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore

View File

@ -267,8 +267,7 @@ class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy):
class RunUntilUserTaskOrMessageExecutionStrategy(ExecutionStrategy):
"""When you want to run tasks until you hit something to report to the end user, or
until there are no other engine steps to complete."""
"""When you want to run tasks until you hit something to report to the end user."""
def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]:
return list(

View File

@ -1654,7 +1654,7 @@ class TestProcessApi(BaseTest):
# a list. It tests all of our code. No reason to test Flasks SSE support.
results = list(_interstitial_stream(process_instance_id))
# strip the "data:" prefix and convert remaining string to dict.
json_results = list(map(lambda x: json.loads(x[5:]), results))
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
# There should be 2 results back -
# the first script task should not be returned (it contains no end user instructions)
# The second script task should produce rendered jinja text
@ -1675,10 +1675,10 @@ class TestProcessApi(BaseTest):
# we should now be on a task that does not belong to the original user, and the interstitial page should know this.
results = list(_interstitial_stream(process_instance_id))
json_results = list(map(lambda x: json.loads(x[5:]), results))
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
assert len(results) == 1
assert json_results[0]["state"] == "READY"
assert json_results[0]["can_complete"] == False
assert json_results[0]["can_complete"] is False
assert json_results[0]["title"] == "Please Approve"
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane"
@ -1692,7 +1692,7 @@ class TestProcessApi(BaseTest):
list(_interstitial_stream(process_instance_id))
list(_interstitial_stream(process_instance_id))
results = list(_interstitial_stream(process_instance_id))
json_results = list(map(lambda x: json.loads(x[5:]), results))
json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore
assert len(json_results) == 1
assert json_results[0]["state"] == "COMPLETED"
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am the end task"

View File

@ -17,6 +17,7 @@ export default function ProcessInterstitial() {
const [status, setStatus] = useState<string>('running');
const params = useParams();
const navigate = useNavigate();
const userTasks = ['User Task', 'Manual Task'];
useEffect(() => {
fetchEventSource(
@ -35,7 +36,8 @@ export default function ProcessInterstitial() {
},
}
);
}, []);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // it is critical to only run this once.
useEffect(() => {
// Added this seperate use effect so that the timer interval will be cleared if
@ -43,7 +45,7 @@ export default function ProcessInterstitial() {
if (
lastTask &&
lastTask.can_complete &&
['User Task', 'Manual Task'].includes(lastTask.type)
userTasks.includes(lastTask.type)
) {
const timerId = setInterval(() => {
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
@ -57,14 +59,9 @@ export default function ProcessInterstitial() {
if (status !== 'running') {
setStatus(lastTask.state);
}
if (
!lastTask.can_complete &&
['User Task', 'Manual Task'].includes(lastTask.type)
) {
if (!lastTask.can_complete && userTasks.includes(lastTask.type)) {
setStatus('LOCKED');
}
console.log(`Status is : ${status}}`);
console.log('last task is : ', lastTask);
switch (status) {
case 'running':
return (
@ -87,10 +84,7 @@ export default function ProcessInterstitial() {
};
const userMessage = (myTask: ProcessInstanceTask) => {
if (
!myTask.can_complete &&
['User Task', 'Manual Task'].includes(myTask.type)
) {
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
return <div>This next task must be completed by a different person.</div>;
}
return (

View File

@ -1,12 +1,5 @@
import { useEffect, useState } from 'react';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom';
// @ts-ignore
import { Tabs, TabList, Tab } from '@carbon/react';
import TaskShow from './TaskShow';
import MyTasks from './MyTasks';
import GroupedTasks from './GroupedTasks';
import CompletedInstances from './CompletedInstances';
import CreateNewInstance from './CreateNewInstance';
import ProcessInterstitial from './ProcessInterstitial';
export default function ProcessRoutes() {

View File

@ -13,7 +13,6 @@ import {
ButtonSet,
} from '@carbon/react';
import MDEditor from '@uiw/react-md-editor';
// eslint-disable-next-line import/no-named-as-default
import Form from '../themes/carbon';
import HttpService from '../services/HttpService';
@ -90,13 +89,6 @@ function TypeAheadWidget({
);
}
class UnexpectedHumanTaskType extends Error {
constructor(message: string) {
super(message);
this.name = 'UnexpectedHumanTaskType';
}
}
enum FormSubmitType {
Default,
Draft,
@ -108,16 +100,11 @@ export default function TaskShow() {
const params = useParams();
const navigate = useNavigate();
const [disabled, setDisabled] = useState(false);
const [refreshSeconds, setRefreshSeconds] = useState(0);
// save current form data so that we can avoid validations in certain situations
const [currentFormObject, setCurrentFormObject] = useState<any>({});
const { addError, removeError } = useAPIError();
// eslint-disable-next-line sonarjs/no-duplicate-string
const supportedHumanTaskTypes = ['User Task', 'Manual Task'];
const navigateToInterstitial = (myTask: ProcessInstanceTask) => {
navigate(
`/process/${modifyProcessIdentifierForPathParam(
@ -361,12 +348,6 @@ export default function TaskShow() {
Save as draft
</Button>
);
} else {
return (
<p>
<i>Page will refresh in {refreshSeconds} seconds.</i>
</p>
);
}
reactFragmentToHideSubmitButton = (
<ButtonSet>