added tasks table to process instance show page w/ burnettk

This commit is contained in:
jasquat 2023-01-13 14:31:12 -05:00
parent a2bb41301a
commit 2d61bf378e
6 changed files with 227 additions and 103 deletions

View File

@ -1333,6 +1333,12 @@ paths:
/tasks:
parameters:
- name: process_instance_id
in: query
required: false
description: The process instance id to search by.
schema:
type: integer
- name: page
in: query
required: false

View File

@ -106,6 +106,11 @@ permissions:
users: []
allowed_permissions: [create, read, update, delete]
uri: /process-instances/reports/*
read-process-instances-find-by-id:
groups: [everybody]
users: []
allowed_permissions: [read]
uri: /process-instances/find-by-id/*
processes-read:
groups: [everybody]
users: []

View File

@ -67,35 +67,48 @@ class ReactJsonSchemaSelectOption(TypedDict):
# TODO: see comment for before_request
# @process_api_blueprint.route("/v1.0/tasks", methods=["GET"])
def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
def task_list_my_tasks(
process_instance_id: Optional[int] = None, page: int = 1, per_page: int = 100
) -> flask.wrappers.Response:
"""Task_list_my_tasks."""
principal = _find_principal_or_raise()
human_tasks = (
human_task_query = (
HumanTaskModel.query.order_by(desc(HumanTaskModel.id)) # type: ignore
.join(ProcessInstanceModel)
.join(HumanTaskUserModel)
.filter_by(user_id=principal.user_id)
.filter(HumanTaskModel.completed == False) # noqa: E712
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
.add_columns(
ProcessInstanceModel.process_model_identifier,
ProcessInstanceModel.process_model_display_name,
ProcessInstanceModel.status,
HumanTaskModel.task_name,
HumanTaskModel.task_title,
HumanTaskModel.task_type,
HumanTaskModel.task_status,
HumanTaskModel.task_id,
HumanTaskModel.id,
HumanTaskModel.process_model_display_name,
HumanTaskModel.process_instance_id,
.group_by(HumanTaskModel.id)
.join(
ProcessInstanceModel,
ProcessInstanceModel.id == HumanTaskModel.process_instance_id,
)
.paginate(page=page, per_page=per_page, error_out=False)
.join(HumanTaskUserModel, HumanTaskUserModel.human_task_id == HumanTaskModel.id)
.filter(HumanTaskUserModel.user_id == principal.user_id)
.join(UserModel, UserModel.id == HumanTaskUserModel.user_id)
.filter(HumanTaskModel.completed == False) # noqa: E712
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
)
tasks = [HumanTaskModel.to_task(human_task) for human_task in human_tasks.items]
if process_instance_id is not None:
human_task_query = human_task_query.filter(
ProcessInstanceModel.id == process_instance_id
)
human_tasks = human_task_query.add_columns(
ProcessInstanceModel.process_model_identifier,
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
ProcessInstanceModel.updated_at_in_seconds,
ProcessInstanceModel.created_at_in_seconds,
UserModel.username.label("process_initiator_username"), # type: ignore
GroupModel.identifier.label("assigned_user_group_identifier"),
HumanTaskModel.task_name,
HumanTaskModel.task_title,
HumanTaskModel.process_model_display_name,
HumanTaskModel.process_instance_id,
func.group_concat(UserModel.username.distinct()).label( # type: ignore
"potential_owner_usernames"
),
).paginate(page=page, per_page=per_page, error_out=False)
response_json = {
"results": tasks,
"results": human_tasks.items,
"pagination": {
"count": len(human_tasks.items),
"total": human_tasks.total,
@ -416,6 +429,7 @@ def _get_tasks(
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
),
)
if has_lane_assignment_id:
if user_group_identifier:
human_tasks_query = human_tasks_query.filter(

View File

@ -29,6 +29,13 @@ type OwnProps = {
showStartedBy?: boolean;
showWaitingOn?: boolean;
textToShowIfEmpty?: string;
shouldPaginateTable?: boolean;
showProcessId?: boolean;
showProcessModelIdentifier?: boolean;
showTableDescriptionAsTooltip?: boolean;
showDateStarted?: boolean;
showLastUpdated?: boolean;
hideIfNoTasks?: boolean;
};
export default function TaskListTable({
@ -42,6 +49,13 @@ export default function TaskListTable({
autoReload = false,
showStartedBy = true,
showWaitingOn = true,
shouldPaginateTable = true,
showProcessId = true,
showProcessModelIdentifier = true,
showTableDescriptionAsTooltip = false,
showDateStarted = true,
showLastUpdated = true,
hideIfNoTasks = false,
}: OwnProps) {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
@ -89,10 +103,6 @@ export default function TaskListTable({
) => {
let fullUsernameString = '';
let shortUsernameString = '';
if (processInstanceTask.assigned_user_group_identifier) {
fullUsernameString = processInstanceTask.assigned_user_group_identifier;
shortUsernameString = processInstanceTask.assigned_user_group_identifier;
}
if (processInstanceTask.potential_owner_usernames) {
fullUsernameString = processInstanceTask.potential_owner_usernames;
const usernames =
@ -103,82 +113,133 @@ export default function TaskListTable({
}
shortUsernameString = firstTwoUsernames.join(',');
}
if (processInstanceTask.assigned_user_group_identifier) {
fullUsernameString = processInstanceTask.assigned_user_group_identifier;
shortUsernameString = processInstanceTask.assigned_user_group_identifier;
}
return <span title={fullUsernameString}>{shortUsernameString}</span>;
};
const buildTable = () => {
if (!tasks) {
return null;
}
const rows = tasks.map((row: ProcessInstanceTask) => {
const taskUrl = `/tasks/${row.process_instance_id}/${row.task_id}`;
const modifiedProcessModelIdentifier =
modifyProcessIdentifierForPathParam(row.process_model_identifier);
const getTableRow = (processInstanceTask: ProcessInstanceTask) => {
const taskUrl = `/tasks/${processInstanceTask.process_instance_id}/${processInstanceTask.task_id}`;
const modifiedProcessModelIdentifier = modifyProcessIdentifierForPathParam(
processInstanceTask.process_model_identifier
);
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
let hasAccessToCompleteTask = false;
if (row.potential_owner_usernames.match(regex)) {
hasAccessToCompleteTask = true;
}
return (
<tr key={row.id}>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${row.process_instance_id}`}
title={`View process instance ${row.process_instance_id}`}
>
{row.process_instance_id}
</Link>
</td>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={row.process_model_identifier}
>
{row.process_model_display_name}
</Link>
</td>
<td
title={`task id: ${row.name}, spiffworkflow task guid: ${row.id}`}
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
let hasAccessToCompleteTask = false;
if ((processInstanceTask.potential_owner_usernames || '').match(regex)) {
hasAccessToCompleteTask = true;
}
const rowElements = [];
if (showProcessId) {
rowElements.push(
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${processInstanceTask.process_instance_id}`}
title={`View process instance ${processInstanceTask.process_instance_id}`}
>
{row.task_title}
</td>
{showStartedBy ? <td>{row.process_initiator_username}</td> : ''}
{showWaitingOn ? <td>{getWaitingForTableCellComponent(row)}</td> : ''}
<td>
{convertSecondsToFormattedDateTime(row.created_at_in_seconds) ||
'-'}
</td>
<TableCellWithTimeAgoInWords
timeInSeconds={row.updated_at_in_seconds}
/>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={row.process_instance_status === 'suspended'}
disabled={!hasAccessToCompleteTask}
>
Go
</Button>
</td>
</tr>
{processInstanceTask.process_instance_id}
</Link>
</td>
);
});
let tableHeaders = ['Id', 'Process', 'Task'];
}
if (showProcessModelIdentifier) {
rowElements.push(
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={processInstanceTask.process_model_identifier}
>
{processInstanceTask.process_model_display_name}
</Link>
</td>
);
}
rowElements.push(
<td
title={`task id: ${processInstanceTask.name}, spiffworkflow task guid: ${processInstanceTask.id}`}
>
{processInstanceTask.task_title}
</td>
);
if (showStartedBy) {
rowElements.push(
<td>{processInstanceTask.process_initiator_username}</td>
);
}
if (showWaitingOn) {
rowElements.push(
<td>{getWaitingForTableCellComponent(processInstanceTask)}</td>
);
}
if (showDateStarted) {
rowElements.push(
<td>
{convertSecondsToFormattedDateTime(
processInstanceTask.created_at_in_seconds
) || '-'}
</td>
);
}
if (showLastUpdated) {
rowElements.push(
<TableCellWithTimeAgoInWords
timeInSeconds={processInstanceTask.updated_at_in_seconds}
/>
);
}
rowElements.push(
<td>
<Button
variant="primary"
href={taskUrl}
hidden={processInstanceTask.process_instance_status === 'suspended'}
disabled={!hasAccessToCompleteTask}
>
Go
</Button>
</td>
);
return <tr key={processInstanceTask.id}>{rowElements}</tr>;
};
const getTableHeaders = () => {
let tableHeaders = [];
if (showProcessId) {
tableHeaders.push('Id');
}
if (showProcessModelIdentifier) {
tableHeaders.push('Process');
}
tableHeaders.push('Task');
if (showStartedBy) {
tableHeaders.push('Started By');
}
if (showWaitingOn) {
tableHeaders.push('Waiting For');
}
tableHeaders = tableHeaders.concat([
'Date Started',
'Last Updated',
'Actions',
]);
if (showDateStarted) {
tableHeaders.push('Date Started');
}
if (showLastUpdated) {
tableHeaders.push('Last Updated');
}
tableHeaders = tableHeaders.concat(['Actions']);
return tableHeaders;
};
const buildTable = () => {
if (!tasks) {
return null;
}
const tableHeaders = getTableHeaders();
const rows = tasks.map((processInstanceTask: ProcessInstanceTask) => {
return getTableRow(processInstanceTask);
});
return (
<Table striped bordered>
<thead>
@ -207,24 +268,41 @@ export default function TaskListTable({
undefined,
paginationQueryParamPrefix
);
return (
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
paginationQueryParamPrefix={paginationQueryParamPrefix}
paginationClassName={paginationClassName}
/>
let tableElement = (
<div className={paginationClassName}>{buildTable()}</div>
);
if (shouldPaginateTable) {
tableElement = (
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
paginationQueryParamPrefix={paginationQueryParamPrefix}
paginationClassName={paginationClassName}
/>
);
}
return tableElement;
};
if (tasks) {
const tableAndDescriptionElement = () => {
if (showTableDescriptionAsTooltip) {
return <h2 title={tableDescription}>{tableTitle}</h2>;
}
return (
<>
<h2>{tableTitle}</h2>
<p className="data-table-description">{tableDescription}</p>
</>
);
};
if (tasks && (tasks.length > 0 || hideIfNoTasks === false)) {
return (
<>
{tableAndDescriptionElement()}
{tasksComponent()}
</>
);

View File

@ -34,12 +34,12 @@ export interface ProcessInstanceTask {
process_identifier: string;
name: string;
process_initiator_username: string;
assigned_user_group_identifier: string;
created_at_in_seconds: number;
updated_at_in_seconds: number;
current_user_is_potential_owner: number;
potential_owner_usernames: string;
calling_subprocess_task_id: string;
potential_owner_usernames?: string;
assigned_user_group_identifier?: string;
}
export interface ProcessReference {

View File

@ -48,6 +48,7 @@ import {
} from '../interfaces';
import { usePermissionFetcher } from '../hooks/PermissionService';
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
import TaskListTable from '../components/TaskListTable';
type OwnProps = {
variant: string;
@ -1009,6 +1010,26 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
</Stack>
<br />
<br />
<Grid condensed fullWidth>
<Column md={6} lg={8} sm={4}>
<TaskListTable
apiPath="/tasks"
additionalParams={`process_instance_id=${processInstance.id}`}
tableTitle="Tasks I can complete"
tableDescription="These are tasks that can be completed by you, either because they were assigned to a group you are in, or because they were assigned directly to you."
paginationClassName="with-large-bottom-margin"
textToShowIfEmpty="There are no tasks you can complete for this process instance."
shouldPaginateTable={false}
showProcessModelIdentifier={false}
showProcessId={false}
showStartedBy={false}
showTableDescriptionAsTooltip
showDateStarted={false}
showLastUpdated={false}
hideIfNoTasks
/>
</Column>
</Grid>
{getInfoTag()}
<br />
{taskUpdateDisplayArea()}