mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-02-11 01:06:36 +00:00
added tasks table to process instance show page w/ burnettk
This commit is contained in:
parent
96cc8b02d0
commit
14397099f4
@ -1333,6 +1333,12 @@ paths:
|
|||||||
|
|
||||||
/tasks:
|
/tasks:
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: process_instance_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The process instance id to search by.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
- name: page
|
- name: page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
@ -106,6 +106,11 @@ permissions:
|
|||||||
users: []
|
users: []
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /process-instances/reports/*
|
uri: /process-instances/reports/*
|
||||||
|
read-process-instances-find-by-id:
|
||||||
|
groups: [everybody]
|
||||||
|
users: []
|
||||||
|
allowed_permissions: [read]
|
||||||
|
uri: /process-instances/find-by-id/*
|
||||||
processes-read:
|
processes-read:
|
||||||
groups: [everybody]
|
groups: [everybody]
|
||||||
users: []
|
users: []
|
||||||
|
@ -67,35 +67,48 @@ class ReactJsonSchemaSelectOption(TypedDict):
|
|||||||
|
|
||||||
# TODO: see comment for before_request
|
# TODO: see comment for before_request
|
||||||
# @process_api_blueprint.route("/v1.0/tasks", methods=["GET"])
|
# @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."""
|
"""Task_list_my_tasks."""
|
||||||
principal = _find_principal_or_raise()
|
principal = _find_principal_or_raise()
|
||||||
human_tasks = (
|
human_task_query = (
|
||||||
HumanTaskModel.query.order_by(desc(HumanTaskModel.id)) # type: ignore
|
HumanTaskModel.query.order_by(desc(HumanTaskModel.id)) # type: ignore
|
||||||
.join(ProcessInstanceModel)
|
.group_by(HumanTaskModel.id)
|
||||||
.join(HumanTaskUserModel)
|
.join(
|
||||||
.filter_by(user_id=principal.user_id)
|
ProcessInstanceModel,
|
||||||
.filter(HumanTaskModel.completed == False) # noqa: E712
|
ProcessInstanceModel.id == HumanTaskModel.process_instance_id,
|
||||||
# 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,
|
|
||||||
)
|
)
|
||||||
.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 = {
|
response_json = {
|
||||||
"results": tasks,
|
"results": human_tasks.items,
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"count": len(human_tasks.items),
|
"count": len(human_tasks.items),
|
||||||
"total": human_tasks.total,
|
"total": human_tasks.total,
|
||||||
@ -416,6 +429,7 @@ def _get_tasks(
|
|||||||
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_lane_assignment_id:
|
if has_lane_assignment_id:
|
||||||
if user_group_identifier:
|
if user_group_identifier:
|
||||||
human_tasks_query = human_tasks_query.filter(
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
@ -29,6 +29,13 @@ type OwnProps = {
|
|||||||
showStartedBy?: boolean;
|
showStartedBy?: boolean;
|
||||||
showWaitingOn?: boolean;
|
showWaitingOn?: boolean;
|
||||||
textToShowIfEmpty?: string;
|
textToShowIfEmpty?: string;
|
||||||
|
shouldPaginateTable?: boolean;
|
||||||
|
showProcessId?: boolean;
|
||||||
|
showProcessModelIdentifier?: boolean;
|
||||||
|
showTableDescriptionAsTooltip?: boolean;
|
||||||
|
showDateStarted?: boolean;
|
||||||
|
showLastUpdated?: boolean;
|
||||||
|
hideIfNoTasks?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TaskListTable({
|
export default function TaskListTable({
|
||||||
@ -42,6 +49,13 @@ export default function TaskListTable({
|
|||||||
autoReload = false,
|
autoReload = false,
|
||||||
showStartedBy = true,
|
showStartedBy = true,
|
||||||
showWaitingOn = true,
|
showWaitingOn = true,
|
||||||
|
shouldPaginateTable = true,
|
||||||
|
showProcessId = true,
|
||||||
|
showProcessModelIdentifier = true,
|
||||||
|
showTableDescriptionAsTooltip = false,
|
||||||
|
showDateStarted = true,
|
||||||
|
showLastUpdated = true,
|
||||||
|
hideIfNoTasks = false,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
||||||
@ -89,10 +103,6 @@ export default function TaskListTable({
|
|||||||
) => {
|
) => {
|
||||||
let fullUsernameString = '';
|
let fullUsernameString = '';
|
||||||
let shortUsernameString = '';
|
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) {
|
if (processInstanceTask.potential_owner_usernames) {
|
||||||
fullUsernameString = processInstanceTask.potential_owner_usernames;
|
fullUsernameString = processInstanceTask.potential_owner_usernames;
|
||||||
const usernames =
|
const usernames =
|
||||||
@ -103,82 +113,133 @@ export default function TaskListTable({
|
|||||||
}
|
}
|
||||||
shortUsernameString = firstTwoUsernames.join(',');
|
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>;
|
return <span title={fullUsernameString}>{shortUsernameString}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildTable = () => {
|
const getTableRow = (processInstanceTask: ProcessInstanceTask) => {
|
||||||
if (!tasks) {
|
const taskUrl = `/tasks/${processInstanceTask.process_instance_id}/${processInstanceTask.task_id}`;
|
||||||
return null;
|
const modifiedProcessModelIdentifier = modifyProcessIdentifierForPathParam(
|
||||||
}
|
processInstanceTask.process_model_identifier
|
||||||
const rows = tasks.map((row: ProcessInstanceTask) => {
|
);
|
||||||
const taskUrl = `/tasks/${row.process_instance_id}/${row.task_id}`;
|
|
||||||
const modifiedProcessModelIdentifier =
|
|
||||||
modifyProcessIdentifierForPathParam(row.process_model_identifier);
|
|
||||||
|
|
||||||
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
|
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
|
||||||
let hasAccessToCompleteTask = false;
|
let hasAccessToCompleteTask = false;
|
||||||
if (row.potential_owner_usernames.match(regex)) {
|
if ((processInstanceTask.potential_owner_usernames || '').match(regex)) {
|
||||||
hasAccessToCompleteTask = true;
|
hasAccessToCompleteTask = true;
|
||||||
}
|
}
|
||||||
return (
|
const rowElements = [];
|
||||||
<tr key={row.id}>
|
if (showProcessId) {
|
||||||
<td>
|
rowElements.push(
|
||||||
<Link
|
<td>
|
||||||
data-qa="process-instance-show-link"
|
<Link
|
||||||
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${row.process_instance_id}`}
|
data-qa="process-instance-show-link"
|
||||||
title={`View process instance ${row.process_instance_id}`}
|
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${processInstanceTask.process_instance_id}`}
|
||||||
>
|
title={`View process instance ${processInstanceTask.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}`}
|
|
||||||
>
|
>
|
||||||
{row.task_title}
|
{processInstanceTask.process_instance_id}
|
||||||
</td>
|
</Link>
|
||||||
{showStartedBy ? <td>{row.process_initiator_username}</td> : ''}
|
</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>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
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) {
|
if (showStartedBy) {
|
||||||
tableHeaders.push('Started By');
|
tableHeaders.push('Started By');
|
||||||
}
|
}
|
||||||
if (showWaitingOn) {
|
if (showWaitingOn) {
|
||||||
tableHeaders.push('Waiting For');
|
tableHeaders.push('Waiting For');
|
||||||
}
|
}
|
||||||
tableHeaders = tableHeaders.concat([
|
if (showDateStarted) {
|
||||||
'Date Started',
|
tableHeaders.push('Date Started');
|
||||||
'Last Updated',
|
}
|
||||||
'Actions',
|
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 (
|
return (
|
||||||
<Table striped bordered>
|
<Table striped bordered>
|
||||||
<thead>
|
<thead>
|
||||||
@ -207,24 +268,41 @@ export default function TaskListTable({
|
|||||||
undefined,
|
undefined,
|
||||||
paginationQueryParamPrefix
|
paginationQueryParamPrefix
|
||||||
);
|
);
|
||||||
return (
|
let tableElement = (
|
||||||
<PaginationForTable
|
<div className={paginationClassName}>{buildTable()}</div>
|
||||||
page={page}
|
|
||||||
perPage={perPage}
|
|
||||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
|
||||||
pagination={pagination}
|
|
||||||
tableToDisplay={buildTable()}
|
|
||||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
|
||||||
paginationClassName={paginationClassName}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>{tableTitle}</h2>
|
<h2>{tableTitle}</h2>
|
||||||
<p className="data-table-description">{tableDescription}</p>
|
<p className="data-table-description">{tableDescription}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tasks && (tasks.length > 0 || hideIfNoTasks === false)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tableAndDescriptionElement()}
|
||||||
{tasksComponent()}
|
{tasksComponent()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -34,12 +34,12 @@ export interface ProcessInstanceTask {
|
|||||||
process_identifier: string;
|
process_identifier: string;
|
||||||
name: string;
|
name: string;
|
||||||
process_initiator_username: string;
|
process_initiator_username: string;
|
||||||
assigned_user_group_identifier: string;
|
|
||||||
created_at_in_seconds: number;
|
created_at_in_seconds: number;
|
||||||
updated_at_in_seconds: number;
|
updated_at_in_seconds: number;
|
||||||
current_user_is_potential_owner: number;
|
current_user_is_potential_owner: number;
|
||||||
potential_owner_usernames: string;
|
|
||||||
calling_subprocess_task_id: string;
|
calling_subprocess_task_id: string;
|
||||||
|
potential_owner_usernames?: string;
|
||||||
|
assigned_user_group_identifier?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessReference {
|
export interface ProcessReference {
|
||||||
|
@ -48,6 +48,7 @@ import {
|
|||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
|
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
|
||||||
|
import TaskListTable from '../components/TaskListTable';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
variant: string;
|
variant: string;
|
||||||
@ -1009,6 +1010,26 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<br />
|
<br />
|
||||||
<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()}
|
{getInfoTag()}
|
||||||
<br />
|
<br />
|
||||||
{taskUpdateDisplayArea()}
|
{taskUpdateDisplayArea()}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user