* Because end events won't always have information, allow the interstitial api endpoint to return the last task, even if it doesn't have any end user information. This prevents us from just redirecting everthing back to the home page and encourages better end event messaging.

* Cleaning up icons so they are a little smaller and more consistent.
* Always show that action column.
* Improve the state managment and layout of the interstitial page.
This commit is contained in:
Dan 2023-04-20 15:26:54 -04:00
parent 5b44632af8
commit 5c0e6084ab
13 changed files with 74 additions and 56 deletions

View File

@ -396,9 +396,6 @@ 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.
# return Response(get_data(), mimetype='text/event-stream')
spiff_task = processor.next_task() spiff_task = processor.next_task()
last_task = None last_task = None
while last_task != spiff_task: while last_task != spiff_task:
@ -413,7 +410,11 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, str, None]:
spiff_task = processor.next_task() spiff_task = processor.next_task()
# Note, this has to be done in case someone leaves the page, # Note, this has to be done in case someone leaves the page,
# which can otherwise cancel this function and leave completed tasks un-registered. # 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 method? processor.save() # Fixme - maybe find a way not to do this on every loop?
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"
def interstitial(process_instance_id: int) -> Response: def interstitial(process_instance_id: int) -> Response:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -6,8 +6,7 @@ export default function InstructionsForEndUser({ task }: any) {
if (!task) { if (!task) {
return null; return null;
} }
let instructions = ''; let instructions = 'There is no additional instructions or information for this task.';
console.log('I was passed a task: ', task);
const { properties } = task; const { properties } = task;
const { instructionsForEndUser } = properties; const { instructionsForEndUser } = properties;
if (instructionsForEndUser) { if (instructionsForEndUser) {

View File

@ -10,6 +10,7 @@ export default function MyCompletedInstances() {
perPageOptions={[2, 5, 25]} perPageOptions={[2, 5, 25]}
reportIdentifier="system_report_completed_instances_initiated_by_me" reportIdentifier="system_report_completed_instances_initiated_by_me"
showReports={false} showReports={false}
showActionsColumn
/> />
); );
} }

View File

@ -33,6 +33,7 @@ export default function CompletedInstances() {
showReports={false} showReports={false}
textToShowIfEmpty="This group has no completed instances at this time." textToShowIfEmpty="This group has no completed instances at this time."
additionalParams={`user_group_identifier=${userGroup}`} additionalParams={`user_group_identifier=${userGroup}`}
showActionsColumn
/> />
</> </>
); );
@ -61,6 +62,7 @@ export default function CompletedInstances() {
textToShowIfEmpty="You have no completed instances at this time." textToShowIfEmpty="You have no completed instances at this time."
paginationClassName="with-large-bottom-margin" paginationClassName="with-large-bottom-margin"
autoReload autoReload
showActionsColumn
/> />
<h2 <h2
title={withTasksCompletedByMeTitleText} title={withTasksCompletedByMeTitleText}
@ -76,6 +78,7 @@ export default function CompletedInstances() {
showReports={false} showReports={false}
textToShowIfEmpty="You have no completed instances at this time." textToShowIfEmpty="You have no completed instances at this time."
paginationClassName="with-large-bottom-margin" paginationClassName="with-large-bottom-margin"
showActionsColumn
/> />
{groupTableComponents()} {groupTableComponents()}
</> </>

View File

@ -51,7 +51,7 @@ export default function ProcessInstanceList({ variant }: OwnProps) {
<br /> <br />
{processInstanceBreadcrumbElement()} {processInstanceBreadcrumbElement()}
{processInstanceTitleElement()} {processInstanceTitleElement()}
<ProcessInstanceListTable variant={variant} /> <ProcessInstanceListTable variant={variant} showActionsColumn />
</> </>
); );
} }

View File

@ -14,7 +14,7 @@ import { ProcessInstanceTask } from '../interfaces';
export default function ProcessInterstitial() { export default function ProcessInterstitial() {
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [lastTask, setLastTask] = useState<any>(null); const [lastTask, setLastTask] = useState<any>(null);
const [status, setStatus] = useState<string>('running'); const [state, setState] = useState<string>('RUNNING');
const params = useParams(); const params = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const userTasks = ['User Task', 'Manual Task']; const userTasks = ['User Task', 'Manual Task'];
@ -31,62 +31,74 @@ export default function ProcessInterstitial() {
setLastTask(task); setLastTask(task);
}, },
onclose() { onclose() {
setStatus('closed'); setState('CLOSED');
console.log('Connection Closed by the Server');
}, },
} }
); );
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // it is critical to only run this once. }, []); // it is critical to only run this once.
const shouldRedirect = (myTask: ProcessInstanceTask): boolean => {
return myTask && myTask.can_complete && userTasks.includes(myTask.type);
};
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
// we end up redirecting back to the TaskShow page. // we end up redirecting back to the TaskShow page.
if ( if (shouldRedirect(lastTask)) {
lastTask && setState('REDIRECTING');
lastTask.can_complete && lastTask.properties.instructionsForEndUser = '';
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}`);
}, 1000); }, 2000);
return () => clearInterval(timerId); return () => clearInterval(timerId);
} }
return undefined; return undefined;
}, [lastTask, navigate, userTasks]); }, [lastTask, navigate, userTasks]);
const processStatusImage = () => { const getStatus = (): string => {
if (status !== 'running') {
setStatus(lastTask.state);
}
if (!lastTask.can_complete && userTasks.includes(lastTask.type)) { if (!lastTask.can_complete && userTasks.includes(lastTask.type)) {
setStatus('LOCKED'); return 'LOCKED';
} }
switch (status) { if (state === 'CLOSED') {
case 'running': return lastTask.state;
}
console.log('The State is: ', state);
return state;
};
const getStatusImage = () => {
switch (getStatus()) {
case 'RUNNING':
return ( return (
<Loading description="Active loading indicator" withOverlay={false} /> <Loading description="Active loading indicator" withOverlay={false} />
); );
case 'WAITING':
return <img src="/interstitial/clock.png" alt="Waiting ...." />;
case 'COMPLETED':
return <img src="/interstitial/checkmark.png" alt="Completed" />;
case 'LOCKED': case 'LOCKED':
return ( return <img src="/interstitial/locked.png" alt="Locked" />;
<img case 'REDIRECTING':
src="/interstitial/lock.png" return <img src="/interstitial/redirect.png" alt="Redirecting ...." />;
alt="Locked, Waiting on someone else." case 'WAITING':
/> return <img src="/interstitial/waiting.png" alt="Waiting ...." />;
); case 'COMPLETED':
default: return <img src="/interstitial/completed.png" alt="Completed" />;
return null;
} }
}; };
function capitalize(str: string): string {
console.log('Capitalizing: ', str);
if (str && str.length > 0) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
return '';
}
const userMessage = (myTask: ProcessInstanceTask) => { const userMessage = (myTask: ProcessInstanceTask) => {
if (!myTask.can_complete && userTasks.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 <div>This next task must be completed by a different person.</div>;
} }
if (shouldRedirect(myTask)) {
return <div>Redirecting you to the next task now ...</div>;
}
return ( return (
<div> <div>
<InstructionsForEndUser task={myTask} /> <InstructionsForEndUser task={myTask} />
@ -96,7 +108,7 @@ export default function ProcessInterstitial() {
/** In the event there is no task information and the connection closed, /** In the event there is no task information and the connection closed,
* redirect to the home page. */ * redirect to the home page. */
if (status === 'closed' && lastTask === null) { if (state === 'closed' && lastTask === null) {
navigate(`/tasks`); navigate(`/tasks`);
} }
if (lastTask) { if (lastTask) {
@ -113,26 +125,28 @@ export default function ProcessInterstitial() {
[`Process Instance Id: ${lastTask.process_instance_id}`], [`Process Instance Id: ${lastTask.process_instance_id}`],
]} ]}
/> />
<h1 style={{ display: 'inline-flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
{processStatusImage()} {getStatusImage()}
{lastTask.process_model_display_name}: {lastTask.process_instance_id} <div>
</h1> <h1 style={{ marginBottom: '0em' }}>
{lastTask.process_model_display_name}:{' '}
<Grid condensed fullWidth> {lastTask.process_instance_id}
<Column md={6} lg={8} sm={4}> </h1>
{data && <div>Status: {capitalize(getStatus())}</div>
data.map((d) => ( </div>
<div </div>
style={{ display: 'flex', alignItems: 'center', gap: '2em' }} <br />
> <br />
<div> {data.map((d) => (
Task: <em>{d.title}</em> <Grid fullWidth style={{ marginBottom: '1em' }}>
</div> <Column md={2} lg={4} sm={2}>
<div>{userMessage(d)}</div> Task: <em>{d.title}</em>
</div> </Column>
))} <Column md={6} lg={8} sm={4}>
</Column> {userMessage(d)}
</Grid> </Column>
</Grid>
))}
</> </>
); );
} }