* 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 dedd72acf4
commit fade40ccb8
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)
processor = ProcessInstanceProcessor(process_instance)
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()
last_task = None
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()
# 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 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:

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) {
return null;
}
let instructions = '';
console.log('I was passed a task: ', task);
let instructions = 'There is no additional instructions or information for this task.';
const { properties } = task;
const { instructionsForEndUser } = properties;
if (instructionsForEndUser) {

View File

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

View File

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

View File

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

View File

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