From 9a0ef59d937e8aa9915f851ec60c35df770cafad Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 22 Apr 2023 15:38:29 -0400 Subject: [PATCH 1/3] * Scroll to the top on each update of a process result. * When displaying an error, scroll to the top of the page. * Rename a few of the filter titles to better match the titles of the table on the process instances list page. * Add error_message to the task object - so if a task is in an error state and has a message, we send it to the front end. * Handle errors on the Interstitial page - so if an error happens during execution it is diplayed, and if an error happened previously, we show it. * Return the last Errored Tasks if no READY or WAITING tasks exists when calling the Interstitial endpoint. --- .../src/spiffworkflow_backend/models/task.py | 3 +++ .../routes/tasks_controller.py | 13 ++++++++---- .../services/process_instance_processor.py | 14 ++++++++---- .../services/process_instance_service.py | 7 ++++++ .../public/interstitial/errored.png | Bin 0 -> 1960 bytes .../src/components/ErrorDisplay.tsx | 1 + .../components/ProcessInstanceListTable.tsx | 4 ++-- .../src/components/ProcessModelSearch.tsx | 4 ++-- spiffworkflow-frontend/src/interfaces.ts | 1 + .../src/routes/ProcessInterstitial.tsx | 20 ++++++++++++++---- .../src/routes/TaskShow.tsx | 2 +- 11 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 spiffworkflow-frontend/public/interstitial/errored.png diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py index c9bf311b..17573059 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py @@ -110,6 +110,7 @@ class Task: event_definition: Union[dict[str, Any], None] = None, call_activity_process_identifier: Optional[str] = None, calling_subprocess_task_id: Optional[str] = None, + error_message: Optional[str] = None, ): """__init__.""" self.id = id @@ -147,6 +148,7 @@ class Task: self.properties = properties # Arbitrary extension properties from BPMN editor. if self.properties is None: self.properties = {} + self.error_message = error_message @property def serialized(self) -> dict[str, Any]: @@ -183,6 +185,7 @@ class Task: "event_definition": self.event_definition, "call_activity_process_identifier": self.call_activity_process_identifier, "calling_subprocess_task_id": self.calling_subprocess_task_id, + "error_message": self.error_message, } @classmethod diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 40c68885..81436eee 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -405,14 +405,19 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st reported_ids.append(spiff_task.id) yield f"data: {current_app.json.dumps(task)} \n\n" last_task = spiff_task - processor.do_engine_steps(execution_strategy_name="run_until_user_message") - processor.do_engine_steps(execution_strategy_name="one_at_a_time") - spiff_task = processor.next_task() + try: + processor.do_engine_steps(execution_strategy_name="run_until_user_message") + processor.do_engine_steps(execution_strategy_name="one_at_a_time") + except WorkflowTaskException as wfe: + api_error = ApiError.from_workflow_exception("engine_steps_error", "Failed complete an automated task.", exp=wfe) + yield f"data: {current_app.json.dumps(api_error)} \n\n" # Note, this has to be done in case someone leaves the page, # which can otherwise cancel this function and leave completed tasks un-registered. processor.save() # Fixme - maybe find a way not to do this on every loop? + spiff_task = processor.next_task() + + # Always provide some response, in the event no instructions were provided. if len(reported_ids) == 0: - # Always provide some response, in the event no instructions were provided. task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task()) yield f"data: {current_app.json.dumps(task)} \n\n" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 224e20fe..852f15d3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1741,8 +1741,8 @@ class ProcessInstanceProcessor: def next_task(self) -> SpiffTask: """Returns the next task that should be completed even if there are parallel tasks and multiple options are available. - If the process_instance is complete - it will return the final end task. + If the process_instance is complete it will return the final end task. + If the process_instance is in an error state it will return the task that is erroring. """ # If the whole blessed mess is done, return the end_event task in the tree # This was failing in the case of a call activity where we have an intermediate EndEvent @@ -1769,8 +1769,13 @@ class ProcessInstanceProcessor: waiting_tasks = self.bpmn_process_instance.get_tasks(TaskState.WAITING) if len(waiting_tasks) > 0: return waiting_tasks[0] - else: - return # We have not tasks to return. + + # If there are no ready tasks, and not waiting tasks, return the latest error. + error_task = None + for task in SpiffTask.Iterator(self.bpmn_process_instance.task_tree, TaskState.ERROR): + error_task = task + return error_task + # Get a list of all completed user tasks (Non engine tasks) completed_user_tasks = self.completed_user_tasks() @@ -1798,6 +1803,7 @@ class ProcessInstanceProcessor: next_task = task return next_task + def completed_user_tasks(self) -> List[SpiffTask]: """Completed_user_tasks.""" user_tasks = self.bpmn_process_instance.get_tasks(TaskState.COMPLETED) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index db3e62e1..13d655d5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -462,6 +462,12 @@ class ProcessInstanceService: serialized_task_spec = processor.serialize_task_spec(spiff_task.task_spec) + # Grab the last error message. + error_message = None + for event in processor.process_instance_model.process_instance_events: + for detail in event.error_details: + error_message = detail.message + task = Task( spiff_task.id, spiff_task.task_spec.name, @@ -479,6 +485,7 @@ class ProcessInstanceService: event_definition=serialized_task_spec.get("event_definition"), call_activity_process_identifier=call_activity_process_identifier, calling_subprocess_task_id=calling_subprocess_task_id, + error_message=error_message ) return task diff --git a/spiffworkflow-frontend/public/interstitial/errored.png b/spiffworkflow-frontend/public/interstitial/errored.png new file mode 100644 index 0000000000000000000000000000000000000000..c931b7b1a8dfcbe545fc15888bad5e0c58135bcd GIT binary patch literal 1960 zcmXw4dpy&7AD?CBHj7d-rFEm*Y{L+8$))7bTwC`}=VC)O$44qK_Cz*h@PIJ}YDix_QL)uE%SUL@0vjGl&#gx0EiCB?f66ibtgR{h92!L}KCy35LFqCeJLrj=@FU~U9I)yP5#geXO;!cHgk%%LZ)ez;}|w5 zM7cj^^?)%Ryk()S=9*YLHT@EaJTXn!cT;rd%E$LtKEAtEdNa?vb0!3M)YC(9IR@7R zi(FBw83vkfb*k39G`ggBzRzaM>PaKwR)X=rh)og(`<53uz4c{S5m6!!*n007nN=+@ zuI}>k=>i*ZpZ+tNsjL5{qFq#HnuM}5&dDE9y6^(W4i3PUt<`O-vaMh~wbkriN$a=& z@%vVI^3cIC>2Q#JcbFsvQ6jSV?;t3 z7FAnk!Z1bvMI9taG5kAku(nMWM)uQKVj?Y}qZ)ULqy3tPPbc6Q*v5_VPkBlB=<&Lv ziG7+*`rpqF6oxfHXQ_AP!#{2ib1gEa;h)KV$-DEXQ>0=b1GLJ+=K}axjQ+=5svk7R zBI~O;w&Bajp7ir681Kk-XR`m4t0>n-YhF%`E9b;}Q|GbZ_3tb+94lXa?c7oC7n-`3 zSdEKcVzMQkPv*=SQPB5#n+&s{Ev=(t&dT6GkZ8S))?KTlX7HX&>fqpaUBu-_i5=Zw zmTa`^vY1a=ORU5#G&!(yA>lPWa}K@h1Hww%hnr~POiz+#L5%*%ZP(Mk)xOdUG5_Sp z?>b}U-(TrQrgXc4r40QRyE6qH;u3gC!Dh18Iijx{MA+6XWFQ*1`eH>*$TYyOveI<= z=3Bi(v6YkZ?-!FC*jUj{WLlKtWP<$0?!PXFZ$ry06;XePB^nTUw0>;Sa?bBgSwn8# z&tH2^In{}%u81G#)PAU+IkH_xoIf@SBXW8w2 zu1U%v<=6B@>Av6kI(&Xm3W3~Ox`lDp=<5Bm&FD}?L!j^<{ z7T*y@b_>_e#(zsG!_S74KUVA=>rw8Ue9}1N)RBA2`-xM>a7|^5=WK_bGwdU__tV^3 zmzLzm{ecQ+_oeNnuM_c&BZM-cB5Zuedj-kgf1J3fFfXjRdd)Y*dF=N6@I~!#(F;h8 zc%k#{-e~cBLGRSGzOBh#dC02LPDs5i#sac{U&y%LQdD-mkSyNx`LC42X})%)yZ1GW zU453GaLmsCuH<;+;zcE}wo5y<{Mam?R2gGG)O9z&cUM5Z$Y3T+y2xsu4{CZsC_ACy zNzRvT5Ph(!f;Zn^CMD;Efxy8iE8)0w2Gap)y zZ0JveMC#}>8?rox*DU_>q18P;9L~|K*qGgaG&OU{`$gO90)=$&+Lz?@*(d5(PnRAI zx#9|*TE~xhBhO!qq>NtlxvERhM$6_W9sliqjzsr=_^jfrEQWqkmt9?G?IaD%i3$ca zu1U7gjJNegiL%%6;fEenE=;c@NpE)@v1w40?&5}YyWUt%agV$B+o1!tuXt8!?gI%qe)clZrP!OkVajg9dMdZ6g>Knq!Z2m$2dF z7d1&Xo>dj);G~o$KDaqGA-2tKO7@#@Fx*E61G{Z)aLJeRf#Je6J?ZeP8_9X`gJi&m z*79=X%kbKEagEy^>&QwzES5 z5IQs9u0xwAhchtyCa9$qgMa8KLxOTT;NoJf9+o0F_yE$&X*c1N+Xi_Rr<}fB4VyADsxT>DzZWld2B~ N removeError()} type="error"> diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 6a1562af..d214c622 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1190,7 +1190,7 @@ export default function ProcessInstanceListTable({ return null; }} placeholder="Start typing username" - titleText="Process Initiator" + titleText="Started By" selectedItem={processInitiatorSelection} /> ); @@ -1199,7 +1199,7 @@ export default function ProcessInstanceListTable({ { diff --git a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx index 3a6331a3..567d4470 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx @@ -15,7 +15,7 @@ export default function ProcessModelSearch({ processModels, selectedItem, onChange, - titleText = 'Process model', + titleText = 'Process', }: OwnProps) { const getParentGroupsDisplayName = (processModel: ProcessModel) => { if (processModel.parent_groups) { @@ -65,7 +65,7 @@ export default function ProcessModelSearch({ return null; }} shouldFilterItem={shouldFilterProcessModel} - placeholder="Choose a process model" + placeholder="Choose a process" titleText={titleText} selectedItem={selectedItem} /> diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index e482bede..0188e5ea 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -81,6 +81,7 @@ export interface ProcessInstanceTask { potential_owner_usernames?: string; assigned_user_group_identifier?: string; + error_message?: string; } export interface ProcessReference { diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index bdd56699..724dfe59 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -10,6 +10,7 @@ import { getBasicHeaders } from '../services/HttpService'; import InstructionsForEndUser from '../components/InstructionsForEndUser'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import { ProcessInstanceTask } from '../interfaces'; +import useAPIError from '../hooks/UseApiError'; export default function ProcessInterstitial() { const [data, setData] = useState([]); @@ -20,6 +21,7 @@ export default function ProcessInterstitial() { const userTasks = useMemo(() => { return ['User Task', 'Manual Task']; }, []); + const { addError, removeError } = useAPIError(); useEffect(() => { fetchEventSource( @@ -27,9 +29,13 @@ export default function ProcessInterstitial() { { headers: getBasicHeaders(), onmessage(ev) { - const task = JSON.parse(ev.data); - setData((prevData) => [...prevData, task]); - setLastTask(task); + const retValue = JSON.parse(ev.data); + if ('error_code' in retValue) { + addError(retValue); + } else { + setData((prevData) => [...prevData, retValue]); + setLastTask(retValue); + } }, onclose() { setState('CLOSED'); @@ -85,6 +91,8 @@ export default function ProcessInterstitial() { return Waiting ....; case 'COMPLETED': return Completed; + case 'ERROR': + return Errored; default: return getStatus(); } @@ -104,6 +112,10 @@ export default function ProcessInterstitial() { if (shouldRedirect(myTask)) { return
Redirecting you to the next task now ...
; } + if (myTask.error_message) { + return
{myTask.error_message}
+ } + return (
@@ -147,7 +159,7 @@ export default function ProcessInterstitial() { Task: {d.title} - + {userMessage(d)} diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 56ea9bc2..b8d3d10f 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -117,10 +117,10 @@ export default function TaskShow() { const processResult = (result: ProcessInstanceTask) => { setTask(result); setDisabled(false); - if (!result.can_complete) { navigateToInterstitial(result); } + window.scrollTo(0, 0); // Scroll back to the top of the page /* Disable call to load previous tasks -- do not display menu. const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam( From 02d8a86465d972746924c60e1c04752a45a1cd8a Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 22 Apr 2023 15:45:20 -0400 Subject: [PATCH 2/3] run_pyl --- .../src/spiffworkflow_backend/config/__init__.py | 12 ++++++------ .../models/process_instance.py | 6 +++--- .../routes/tasks_controller.py | 4 +++- .../services/process_instance_processor.py | 14 ++++++-------- .../services/process_instance_service.py | 2 +- .../src/routes/ProcessInterstitial.tsx | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index 7711c36f..eaf67f6c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -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") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index 009a7486..44fe8276 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -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 diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 81436eee..49860394 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -409,7 +409,9 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st processor.do_engine_steps(execution_strategy_name="run_until_user_message") processor.do_engine_steps(execution_strategy_name="one_at_a_time") except WorkflowTaskException as wfe: - api_error = ApiError.from_workflow_exception("engine_steps_error", "Failed complete an automated task.", exp=wfe) + api_error = ApiError.from_workflow_exception( + "engine_steps_error", "Failed complete an automated task.", exp=wfe + ) yield f"data: {current_app.json.dumps(api_error)} \n\n" # Note, this has to be done in case someone leaves the page, # which can otherwise cancel this function and leave completed tasks un-registered. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 852f15d3..352bd4ac 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -423,9 +423,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() @@ -585,9 +585,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 @@ -1776,7 +1776,6 @@ class ProcessInstanceProcessor: error_task = task return error_task - # Get a list of all completed user tasks (Non engine tasks) completed_user_tasks = self.completed_user_tasks() @@ -1803,7 +1802,6 @@ class ProcessInstanceProcessor: next_task = task return next_task - def completed_user_tasks(self) -> List[SpiffTask]: """Completed_user_tasks.""" user_tasks = self.bpmn_process_instance.get_tasks(TaskState.COMPLETED) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index 13d655d5..de409783 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -485,7 +485,7 @@ class ProcessInstanceService: event_definition=serialized_task_spec.get("event_definition"), call_activity_process_identifier=call_activity_process_identifier, calling_subprocess_task_id=calling_subprocess_task_id, - error_message=error_message + error_message=error_message, ) return task diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index 724dfe59..59e7e8ae 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -21,7 +21,7 @@ export default function ProcessInterstitial() { const userTasks = useMemo(() => { return ['User Task', 'Manual Task']; }, []); - const { addError, removeError } = useAPIError(); + const { addError } = useAPIError(); useEffect(() => { fetchEventSource( @@ -113,7 +113,7 @@ export default function ProcessInterstitial() { return
Redirecting you to the next task now ...
; } if (myTask.error_message) { - return
{myTask.error_message}
+ return
{myTask.error_message}
; } return ( From 0468f3beb51bb52a10aaa5a320526330b45eff66 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 23 Apr 2023 11:13:25 -0400 Subject: [PATCH 3/3] fixing a bit of needless heartbreak. --- spiffworkflow-frontend/src/components/ProcessModelSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx index 567d4470..8f135652 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx @@ -65,7 +65,7 @@ export default function ProcessModelSearch({ return null; }} shouldFilterItem={shouldFilterProcessModel} - placeholder="Choose a process" + placeholder="Choose a process model" titleText={titleText} selectedItem={selectedItem} />