mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-03-01 01:20:45 +00:00
Feature/completed tasks on pi show (#591)
* added api to get all completed tasks for an instance and display it in a table w/ burnettk * moved completed tasks table on pi show page to sub tabs --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
a025aaa017
commit
7e128c5a55
@ -1499,6 +1499,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
# NOT USED
|
||||
/tasks:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
@ -1662,6 +1663,41 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/tasks/completed/{process_instance_id}:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
description: The page number to return. Defaults to page 1.
|
||||
schema:
|
||||
type: integer
|
||||
- name: per_page
|
||||
in: query
|
||||
required: false
|
||||
description: The page number to return. Defaults to page 1.
|
||||
schema:
|
||||
type: integer
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
tags:
|
||||
- Process Instances
|
||||
operationId: spiffworkflow_backend.routes.tasks_controller.task_list_completed
|
||||
summary: returns the list of tasks for that the current user has completed
|
||||
responses:
|
||||
"200":
|
||||
description: list of tasks
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/users/search:
|
||||
parameters:
|
||||
- name: username_prefix
|
||||
|
@ -174,6 +174,41 @@ def task_list_completed_by_me(process_instance_id: int, page: int = 1, per_page:
|
||||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def task_list_completed(process_instance_id: int, page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||
human_tasks_query = (
|
||||
db.session.query(HumanTaskModel) # type: ignore
|
||||
.join(UserModel, UserModel.id == HumanTaskModel.completed_by_user_id)
|
||||
.filter(
|
||||
HumanTaskModel.completed == True, # noqa: E712
|
||||
HumanTaskModel.process_instance_id == process_instance_id,
|
||||
)
|
||||
.add_columns(
|
||||
HumanTaskModel.task_name,
|
||||
HumanTaskModel.task_title,
|
||||
HumanTaskModel.process_model_display_name,
|
||||
HumanTaskModel.process_instance_id,
|
||||
HumanTaskModel.updated_at_in_seconds,
|
||||
HumanTaskModel.created_at_in_seconds,
|
||||
UserModel.username.label("completed_by_username"), # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
human_tasks = human_tasks_query.order_by(desc(HumanTaskModel.id)).paginate( # type: ignore
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
|
||||
response_json = {
|
||||
"results": human_tasks.items,
|
||||
"pagination": {
|
||||
"count": len(human_tasks.items),
|
||||
"total": human_tasks.total,
|
||||
"pages": human_tasks.pages,
|
||||
},
|
||||
}
|
||||
|
||||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def task_list_for_my_open_processes(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||
return _get_tasks(page=page, per_page=per_page)
|
||||
|
||||
|
@ -40,6 +40,7 @@ type OwnProps = {
|
||||
canCompleteAllTasks?: boolean;
|
||||
showActionsColumn?: boolean;
|
||||
showViewFormDataButton?: boolean;
|
||||
showCompletedBy?: boolean;
|
||||
};
|
||||
|
||||
export default function TaskListTable({
|
||||
@ -63,6 +64,7 @@ export default function TaskListTable({
|
||||
canCompleteAllTasks = false,
|
||||
showActionsColumn = true,
|
||||
showViewFormDataButton = false,
|
||||
showCompletedBy = false,
|
||||
}: OwnProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
||||
@ -249,9 +251,8 @@ export default function TaskListTable({
|
||||
}
|
||||
};
|
||||
|
||||
const getTableRow = (processInstanceTask: ProcessInstanceTask) => {
|
||||
const getActionButtons = (processInstanceTask: ProcessInstanceTask) => {
|
||||
const taskUrl = `/tasks/${processInstanceTask.process_instance_id}/${processInstanceTask.task_id}`;
|
||||
|
||||
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
|
||||
let hasAccessToCompleteTask = false;
|
||||
if (
|
||||
@ -260,46 +261,7 @@ export default function TaskListTable({
|
||||
) {
|
||||
hasAccessToCompleteTask = true;
|
||||
}
|
||||
const rowElements: ReactElement[] = [];
|
||||
|
||||
dealWithProcessCells(rowElements, processInstanceTask);
|
||||
|
||||
rowElements.push(
|
||||
<td
|
||||
title={`task id: ${processInstanceTask.name}, spiffworkflow task guid: ${processInstanceTask.id}`}
|
||||
>
|
||||
{processInstanceTask.task_title
|
||||
? processInstanceTask.task_title
|
||||
: processInstanceTask.task_name}
|
||||
</td>
|
||||
);
|
||||
if (showStartedBy) {
|
||||
rowElements.push(
|
||||
<td>{processInstanceTask.process_initiator_username}</td>
|
||||
);
|
||||
}
|
||||
if (showWaitingOn) {
|
||||
rowElements.push(
|
||||
<td>{getWaitingForTableCellComponent(processInstanceTask)}</td>
|
||||
);
|
||||
}
|
||||
if (showDateStarted) {
|
||||
rowElements.push(
|
||||
<td>
|
||||
{DateAndTimeService.convertSecondsToFormattedDateTime(
|
||||
processInstanceTask.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
if (showLastUpdated) {
|
||||
rowElements.push(
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={processInstanceTask.updated_at_in_seconds}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (showActionsColumn) {
|
||||
const actions = [];
|
||||
if (
|
||||
!(
|
||||
@ -329,7 +291,54 @@ export default function TaskListTable({
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
rowElements.push(<td>{actions}</td>);
|
||||
return actions;
|
||||
};
|
||||
|
||||
const getTableRow = (processInstanceTask: ProcessInstanceTask) => {
|
||||
const rowElements: ReactElement[] = [];
|
||||
|
||||
dealWithProcessCells(rowElements, processInstanceTask);
|
||||
|
||||
rowElements.push(
|
||||
<td
|
||||
title={`task id: ${processInstanceTask.name}, spiffworkflow task guid: ${processInstanceTask.id}`}
|
||||
>
|
||||
{processInstanceTask.task_title
|
||||
? processInstanceTask.task_title
|
||||
: processInstanceTask.task_name}
|
||||
</td>
|
||||
);
|
||||
if (showStartedBy) {
|
||||
rowElements.push(
|
||||
<td>{processInstanceTask.process_initiator_username}</td>
|
||||
);
|
||||
}
|
||||
if (showWaitingOn) {
|
||||
rowElements.push(
|
||||
<td>{getWaitingForTableCellComponent(processInstanceTask)}</td>
|
||||
);
|
||||
}
|
||||
if (showCompletedBy) {
|
||||
rowElements.push(<td>{processInstanceTask.completed_by_username}</td>);
|
||||
}
|
||||
if (showDateStarted) {
|
||||
rowElements.push(
|
||||
<td>
|
||||
{DateAndTimeService.convertSecondsToFormattedDateTime(
|
||||
processInstanceTask.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
if (showLastUpdated) {
|
||||
rowElements.push(
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={processInstanceTask.updated_at_in_seconds}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (showActionsColumn) {
|
||||
rowElements.push(<td>{getActionButtons(processInstanceTask)}</td>);
|
||||
}
|
||||
return <tr key={processInstanceTask.id}>{rowElements}</tr>;
|
||||
};
|
||||
@ -349,6 +358,9 @@ export default function TaskListTable({
|
||||
if (showWaitingOn) {
|
||||
tableHeaders.push('Waiting for');
|
||||
}
|
||||
if (showCompletedBy) {
|
||||
tableHeaders.push('Completed by');
|
||||
}
|
||||
if (showDateStarted) {
|
||||
tableHeaders.push('Date started');
|
||||
}
|
||||
|
@ -131,6 +131,9 @@ export interface ProcessInstanceTask {
|
||||
task_title?: string;
|
||||
task_name?: string;
|
||||
completed?: boolean;
|
||||
|
||||
// gets shoved onto HumanTaskModel in result
|
||||
completed_by_username?: string;
|
||||
}
|
||||
|
||||
export interface ProcessReference {
|
||||
|
@ -118,6 +118,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
>(null);
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
|
||||
const [selectedTaskTabSubTab, setSelectedTaskTabSubTab] = useState<number>(0);
|
||||
const [copiedShortLinkToClipboard, setCopiedShortLinkToClipboard] =
|
||||
useState<boolean>(false);
|
||||
|
||||
@ -250,6 +251,11 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
if (searchParams.get('tab')) {
|
||||
setSelectedTabIndex(parseInt(searchParams.get('tab') || '0', 10));
|
||||
}
|
||||
if (searchParams.get('taskSubTab')) {
|
||||
setSelectedTaskTabSubTab(
|
||||
parseInt(searchParams.get('taskSubTab') || '0', 10)
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}, [
|
||||
permissionsLoaded,
|
||||
@ -1475,6 +1481,67 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
updateSearchParams(newTabIndex.selectedIndex, 'tab');
|
||||
};
|
||||
|
||||
const updateSelectedTaskTabSubTab = (newTabIndex: any) => {
|
||||
updateSearchParams(newTabIndex.selectedIndex, 'taskSubTab');
|
||||
};
|
||||
|
||||
const taskTabSubTabs = () => {
|
||||
if (!processInstance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
selectedIndex={selectedTaskTabSubTab}
|
||||
onChange={updateSelectedTaskTabSubTab}
|
||||
>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab>Completed by me</Tab>
|
||||
<Tab>All completed</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
{selectedTaskTabSubTab === 0 ? (
|
||||
<TaskListTable
|
||||
apiPath={`/tasks/completed-by-me/${processInstance.id}`}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
textToShowIfEmpty="You have not completed any tasks for this process instance."
|
||||
shouldPaginateTable={false}
|
||||
showProcessModelIdentifier={false}
|
||||
showProcessId={false}
|
||||
showStartedBy={false}
|
||||
showTableDescriptionAsTooltip
|
||||
showDateStarted={false}
|
||||
showWaitingOn={false}
|
||||
canCompleteAllTasks={false}
|
||||
showViewFormDataButton
|
||||
/>
|
||||
) : null}
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
{selectedTaskTabSubTab === 1 ? (
|
||||
<TaskListTable
|
||||
apiPath={`/tasks/completed/${processInstance.id}`}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
textToShowIfEmpty="There are no completed tasks for this process instance."
|
||||
shouldPaginateTable={false}
|
||||
showProcessModelIdentifier={false}
|
||||
showProcessId={false}
|
||||
showStartedBy={false}
|
||||
showTableDescriptionAsTooltip
|
||||
showDateStarted={false}
|
||||
showWaitingOn={false}
|
||||
canCompleteAllTasks={false}
|
||||
showCompletedBy
|
||||
showActionsColumn={false}
|
||||
/>
|
||||
) : null}
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
if (processInstance && (tasks || tasksCallHadError) && permissionsLoaded) {
|
||||
const processModelId = unModifyProcessIdentifierForPathParam(
|
||||
params.process_model_id ? params.process_model_id : ''
|
||||
@ -1505,7 +1572,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
<Tab disabled={!canViewLogs}>Milestones</Tab>
|
||||
<Tab disabled={!canViewLogs}>Events</Tab>
|
||||
<Tab disabled={!canViewMsgs}>Messages</Tab>
|
||||
<Tab>My completed tasks</Tab>
|
||||
<Tab>Tasks</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
@ -1537,22 +1604,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
{selectedTabIndex === 3 ? getMessageDisplay() : null}
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
{selectedTabIndex === 4 ? (
|
||||
<TaskListTable
|
||||
apiPath={`/tasks/completed-by-me/${processInstance.id}`}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
textToShowIfEmpty="You have not completed any tasks for this process instance."
|
||||
shouldPaginateTable={false}
|
||||
showProcessModelIdentifier={false}
|
||||
showProcessId={false}
|
||||
showStartedBy={false}
|
||||
showTableDescriptionAsTooltip
|
||||
showDateStarted={false}
|
||||
showWaitingOn={false}
|
||||
canCompleteAllTasks={false}
|
||||
showViewFormDataButton
|
||||
/>
|
||||
) : null}
|
||||
{selectedTabIndex === 4 ? taskTabSubTabs() : null}
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
|
Loading…
x
Reference in New Issue
Block a user