This commit is contained in:
Dan 2023-04-19 17:00:09 -04:00
parent 19e3b7da1e
commit c7457daa31
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: 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

@ -127,9 +127,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

@ -5,6 +5,7 @@ import uuid
from sys import exc_info from sys import exc_info
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Generator
from typing import Optional from typing import Optional
from typing import TypedDict from typing import TypedDict
from typing import Union 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) 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.""" """Assure any instructions for end user are processed for jinja syntax."""
if task.properties and "instructionsForEndUser" in task.properties: if task.properties and "instructionsForEndUser" in task.properties:
if task.properties["instructionsForEndUser"]: 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) process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
reported_ids = [] # bit of an issue with end tasks showing as getting completed twice. 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? processor.save() # Fixme - maybe find a way not to do this on every method?
def interstitial(process_instance_id: int): def interstitial(process_instance_id: int) -> Response:
"""A Server Side Events Stream for watching the execution of engine tasks in a """A Server Side Events Stream for watching the execution of engine tasks."""
process instance."""
return Response(stream_with_context(_interstitial_stream(process_instance_id)), mimetype="text/event-stream") 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 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()
@ -595,9 +595,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

@ -267,8 +267,7 @@ class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy):
class RunUntilUserTaskOrMessageExecutionStrategy(ExecutionStrategy): class RunUntilUserTaskOrMessageExecutionStrategy(ExecutionStrategy):
"""When you want to run tasks until you hit something to report to the end user, or """When you want to run tasks until you hit something to report to the end user."""
until there are no other engine steps to complete."""
def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]: def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]:
return list( 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. # a list. It tests all of our code. No reason to test Flasks SSE support.
results = list(_interstitial_stream(process_instance_id)) results = list(_interstitial_stream(process_instance_id))
# strip the "data:" prefix and convert remaining string to dict. # 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 - # There should be 2 results back -
# the first script task should not be returned (it contains no end user instructions) # the first script task should not be returned (it contains no end user instructions)
# The second script task should produce rendered jinja text # 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. # 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)) 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 len(results) == 1
assert json_results[0]["state"] == "READY" 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]["title"] == "Please Approve"
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane" 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))
list(_interstitial_stream(process_instance_id)) list(_interstitial_stream(process_instance_id))
results = 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 len(json_results) == 1
assert json_results[0]["state"] == "COMPLETED" assert json_results[0]["state"] == "COMPLETED"
assert json_results[0]["properties"]["instructionsForEndUser"] == "I am the end task" 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 [status, setStatus] = useState<string>('running');
const params = useParams(); const params = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const userTasks = ['User Task', 'Manual Task'];
useEffect(() => { useEffect(() => {
fetchEventSource( 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(() => { useEffect(() => {
// 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
@ -43,7 +45,7 @@ export default function ProcessInterstitial() {
if ( if (
lastTask && lastTask &&
lastTask.can_complete && lastTask.can_complete &&
['User Task', 'Manual Task'].includes(lastTask.type) userTasks.includes(lastTask.type)
) { ) {
const timerId = setInterval(() => { const timerId = setInterval(() => {
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`); navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
@ -57,14 +59,9 @@ export default function ProcessInterstitial() {
if (status !== 'running') { if (status !== 'running') {
setStatus(lastTask.state); setStatus(lastTask.state);
} }
if ( if (!lastTask.can_complete && userTasks.includes(lastTask.type)) {
!lastTask.can_complete &&
['User Task', 'Manual Task'].includes(lastTask.type)
) {
setStatus('LOCKED'); setStatus('LOCKED');
} }
console.log(`Status is : ${status}}`);
console.log('last task is : ', lastTask);
switch (status) { switch (status) {
case 'running': case 'running':
return ( return (
@ -87,10 +84,7 @@ export default function ProcessInterstitial() {
}; };
const userMessage = (myTask: ProcessInstanceTask) => { const userMessage = (myTask: ProcessInstanceTask) => {
if ( if (!myTask.can_complete && userTasks.includes(myTask.type)) {
!myTask.can_complete &&
['User Task', 'Manual Task'].includes(myTask.type)
) {
return <div>This next task must be completed by a different person.</div>; return <div>This next task must be completed by a different person.</div>;
} }
return ( return (

View File

@ -1,12 +1,5 @@
import { useEffect, useState } from 'react'; import { Route, Routes } from 'react-router-dom';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
// @ts-ignore // @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'; import ProcessInterstitial from './ProcessInterstitial';
export default function ProcessRoutes() { export default function ProcessRoutes() {

View File

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