Merge pull request #81 from sartography/feature/separate_group_task_tables

Feature/separate group task tables
This commit is contained in:
jasquat 2022-12-16 14:42:02 -05:00 committed by GitHub
commit 3224bdfa34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 379 additions and 462 deletions

View File

@ -595,6 +595,12 @@ paths:
description: Specifies the identifier of a report to use, if any description: Specifies the identifier of a report to use, if any
schema: schema:
type: integer type: integer
- name: group_identifier
in: query
required: false
description: The identifier of the group to get the process instances for
schema:
type: string
get: get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
summary: Returns a list of process instances for a given process model summary: Returns a list of process instances for a given process model
@ -1123,6 +1129,12 @@ paths:
/tasks/for-my-groups: /tasks/for-my-groups:
parameters: parameters:
- name: group_identifier
in: query
required: false
description: The identifier of the group to get the tasks for
schema:
type: string
- name: page - name: page
in: query in: query
required: false required: false
@ -1150,6 +1162,22 @@ paths:
items: items:
$ref: "#/components/schemas/Task" $ref: "#/components/schemas/Task"
/user-groups/for-current-user:
get:
tags:
- Process Instances
operationId: spiffworkflow_backend.routes.process_api_blueprint.user_group_list_for_current_user
summary: Group identifiers for current logged in user
responses:
"200":
description: list of user groups
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Task"
/task-data/{modified_process_model_identifier}/{process_instance_id}: /task-data/{modified_process_model_identifier}/{process_instance_id}:
parameters: parameters:
- name: modified_process_model_identifier - name: modified_process_model_identifier

View File

@ -48,6 +48,7 @@ groups:
fin, fin,
fin1, fin1,
harmeet, harmeet,
jason,
sasha, sasha,
manuchehr, manuchehr,
lead, lead,
@ -95,6 +96,11 @@ permissions:
users: [] users: []
allowed_permissions: [read] allowed_permissions: [read]
uri: /v1.0/service-tasks uri: /v1.0/service-tasks
user-groups-for-current-user:
groups: [everybody]
users: []
allowed_permissions: [read]
uri: /v1.0/user-groups/for-current-user
# read all for everybody # read all for everybody

View File

@ -849,6 +849,7 @@ def process_instance_list(
user_filter: Optional[bool] = False, user_filter: Optional[bool] = False,
report_identifier: Optional[str] = None, report_identifier: Optional[str] = None,
report_id: Optional[int] = None, report_id: Optional[int] = None,
group_identifier: Optional[str] = None,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_instance_list.""" """Process_instance_list."""
process_instance_report = ProcessInstanceReportService.report_with_identifier( process_instance_report = ProcessInstanceReportService.report_with_identifier(
@ -988,10 +989,16 @@ def process_instance_list(
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
) )
process_instance_query = process_instance_query.join( if group_identifier:
GroupModel, process_instance_query = process_instance_query.join(
GroupModel.id == SpiffStepDetailsModel.lane_assignment_id, GroupModel,
) GroupModel.identifier == group_identifier,
)
else:
process_instance_query = process_instance_query.join(
GroupModel,
GroupModel.id == SpiffStepDetailsModel.lane_assignment_id,
)
process_instance_query = process_instance_query.join( process_instance_query = process_instance_query.join(
UserGroupAssignmentModel, UserGroupAssignmentModel,
UserGroupAssignmentModel.group_id == GroupModel.id, UserGroupAssignmentModel.group_id == GroupModel.id,
@ -1352,7 +1359,7 @@ def task_list_for_my_open_processes(
def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response: def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
"""Task_list_for_processes_started_by_others.""" """Task_list_for_me."""
return get_tasks( return get_tasks(
processes_started_by_user=False, processes_started_by_user=False,
has_lane_assignment_id=False, has_lane_assignment_id=False,
@ -1362,10 +1369,23 @@ def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Respo
def task_list_for_my_groups( def task_list_for_my_groups(
page: int = 1, per_page: int = 100 group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_list_for_processes_started_by_others.""" """Task_list_for_my_groups."""
return get_tasks(processes_started_by_user=False, page=page, per_page=per_page) return get_tasks(
group_identifier=group_identifier,
processes_started_by_user=False,
page=page,
per_page=per_page,
)
def user_group_list_for_current_user() -> flask.wrappers.Response:
"""User_group_list_for_current_user."""
groups = g.user.groups
# TODO: filter out the default group and have a way to know what is the default group
group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"]
return make_response(jsonify(sorted(group_identifiers)), 200)
def get_tasks( def get_tasks(
@ -1373,6 +1393,7 @@ def get_tasks(
has_lane_assignment_id: bool = True, has_lane_assignment_id: bool = True,
page: int = 1, page: int = 1,
per_page: int = 100, per_page: int = 100,
group_identifier: Optional[str] = None,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Get_tasks.""" """Get_tasks."""
user_id = g.user.id user_id = g.user.id
@ -1409,9 +1430,14 @@ def get_tasks(
), ),
) )
if has_lane_assignment_id: if has_lane_assignment_id:
active_tasks_query = active_tasks_query.filter( if group_identifier:
ActiveTaskModel.lane_assignment_id.is_not(None) # type: ignore active_tasks_query = active_tasks_query.filter(
) GroupModel.identifier == group_identifier
)
else:
active_tasks_query = active_tasks_query.filter(
ActiveTaskModel.lane_assignment_id.is_not(None) # type: ignore
)
else: else:
active_tasks_query = active_tasks_query.filter(ActiveTaskModel.lane_assignment_id.is_(None)) # type: ignore active_tasks_query = active_tasks_query.filter(ActiveTaskModel.lane_assignment_id.is_(None)) # type: ignore

View File

@ -79,6 +79,7 @@ type OwnProps = {
textToShowIfEmpty?: string; textToShowIfEmpty?: string;
paginationClassName?: string; paginationClassName?: string;
autoReload?: boolean; autoReload?: boolean;
additionalParams?: string;
}; };
interface dateParameters { interface dateParameters {
@ -90,6 +91,7 @@ export default function ProcessInstanceListTable({
processModelFullIdentifier, processModelFullIdentifier,
paginationQueryParamPrefix, paginationQueryParamPrefix,
perPageOptions, perPageOptions,
additionalParams,
showReports = true, showReports = true,
reportIdentifier, reportIdentifier,
textToShowIfEmpty, textToShowIfEmpty,
@ -253,6 +255,10 @@ export default function ProcessInstanceListTable({
} }
); );
if (additionalParams) {
queryParamString += `&${additionalParams}`;
}
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/process-instances?${queryParamString}`, path: `/process-instances?${queryParamString}`,
successCallback: setProcessInstancesFromResult, successCallback: setProcessInstancesFromResult,
@ -320,6 +326,7 @@ export default function ProcessInstanceListTable({
processModelFullIdentifier, processModelFullIdentifier,
perPageOptions, perPageOptions,
reportIdentifier, reportIdentifier,
additionalParams,
]); ]);
// This sets the filter data using the saved reports returned from the initial instance_list query. // This sets the filter data using the saved reports returned from the initial instance_list query.

View File

@ -0,0 +1,203 @@
import { useEffect, useState } from 'react';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject, ProcessInstanceTask } from '../interfaces';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
const REFRESH_INTERVAL = 5;
const REFRESH_TIMEOUT = 600;
type OwnProps = {
apiPath: string;
tableTitle: string;
tableDescription: string;
additionalParams?: string;
paginationQueryParamPrefix?: string;
paginationClassName?: string;
autoReload?: boolean;
showStartedBy?: boolean;
showWaitingOn?: boolean;
textToShowIfEmpty?: string;
};
export default function TaskListTable({
apiPath,
tableTitle,
tableDescription,
additionalParams,
paginationQueryParamPrefix,
paginationClassName,
textToShowIfEmpty,
autoReload = false,
showStartedBy = true,
showWaitingOn = true,
}: OwnProps) {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => {
const getTasks = () => {
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
undefined,
paginationQueryParamPrefix
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
let params = `?per_page=${perPage}&page=${page}`;
if (additionalParams) {
params += `&${additionalParams}`;
}
HttpService.makeCallToBackend({
path: `${apiPath}${params}`,
successCallback: setTasksFromResult,
});
};
getTasks();
if (autoReload) {
return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
}
return undefined;
}, [
searchParams,
additionalParams,
apiPath,
paginationQueryParamPrefix,
autoReload,
]);
const buildTable = () => {
if (!tasks) {
return null;
}
const rows = tasks.map((row) => {
const rowToUse = row as any;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
const modifiedProcessModelIdentifier =
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
title={`View process instance ${rowToUse.process_instance_id}`}
>
{rowToUse.process_instance_id}
</Link>
</td>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={rowToUse.process_model_identifier}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
{showStartedBy ? <td>{rowToUse.username}</td> : ''}
{showWaitingOn ? <td>{rowToUse.group_identifier || '-'}</td> : ''}
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<TableCellWithTimeAgoInWords
timeInSeconds={rowToUse.updated_at_in_seconds}
/>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
);
});
let tableHeaders = ['Id', 'Process', 'Task'];
if (showStartedBy) {
tableHeaders.push('Started By');
}
if (showWaitingOn) {
tableHeaders.push('Waiting For');
}
tableHeaders = tableHeaders.concat([
'Date Started',
'Last Updated',
'Actions',
]);
return (
<Table striped bordered>
<thead>
<tr>
{tableHeaders.map((tableHeader: string) => {
return <th>{tableHeader}</th>;
})}
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
};
const tasksComponent = () => {
if (pagination && pagination.total < 1) {
return (
<p className="no-results-message with-large-bottom-margin">
{textToShowIfEmpty}
</p>
);
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
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}
/>
);
};
if (tasks) {
return (
<>
<h2>{tableTitle}</h2>
<p className="data-table-description">{tableDescription}</p>
{tasksComponent()}
</>
);
}
return null;
}

View File

@ -1,156 +1,18 @@
import { useEffect, useState } from 'react'; import TaskListTable from './TaskListTable';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject } from '../interfaces';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
const paginationQueryParamPrefix = 'tasks_for_my_open_processes'; const paginationQueryParamPrefix = 'tasks_for_my_open_processes';
const REFRESH_INTERVAL = 5;
const REFRESH_TIMEOUT = 600;
export default function MyOpenProcesses() { export default function MyOpenProcesses() {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState([]);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => {
const getTasks = () => {
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
undefined,
paginationQueryParamPrefix
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
HttpService.makeCallToBackend({
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
successCallback: setTasksFromResult,
});
};
getTasks();
return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
}, [searchParams]);
const buildTable = () => {
const rows = tasks.map((row) => {
const rowToUse = row as any;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
const modifiedProcessModelIdentifier =
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
title={`View process instance ${rowToUse.process_instance_id}`}
>
{rowToUse.process_instance_id}
</Link>
</td>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={rowToUse.process_model_identifier}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
<td>{rowToUse.group_identifier || '-'}</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<TableCellWithTimeAgoInWords
timeInSeconds={rowToUse.updated_at_in_seconds}
/>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
);
});
return (
<Table striped bordered>
<thead>
<tr>
<th>Id</th>
<th>Process</th>
<th>Task</th>
<th>Waiting For</th>
<th>Date Started</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
};
const tasksComponent = () => {
if (pagination && pagination.total < 1) {
return (
<p className="no-results-message with-large-bottom-margin">
There are no tasks for processes you started at this time.
</p>
);
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
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="with-large-bottom-margin"
/>
);
};
return ( return (
<> <TaskListTable
<h2>My open instances</h2> apiPath="/tasks/for-my-open-processes"
<p className="data-table-description"> paginationQueryParamPrefix={paginationQueryParamPrefix}
These tasks are for processes you started which are not complete. You tableTitle="Tasks for my open instances"
may not have an action to take at this time. See below for tasks waiting tableDescription="These tasks are for processes you started which are not complete. You may not have an action to take at this time. See below for tasks waiting on you."
on you. paginationClassName="with-large-bottom-margin"
</p> textToShowIfEmpty="There are no tasks for processes you started at this time."
{tasksComponent()} autoReload
</> showStartedBy={false}
/>
); );
} }

View File

@ -1,149 +1,16 @@
import { useEffect, useState } from 'react'; import TaskListTable from './TaskListTable';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject } from '../interfaces';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
export default function TasksWaitingForMe() { export default function TasksWaitingForMe() {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState([]);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => {
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
undefined,
'tasks_waiting_for_me'
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
HttpService.makeCallToBackend({
path: `/tasks/for-me?per_page=${perPage}&page=${page}`,
successCallback: setTasksFromResult,
});
}, [searchParams]);
const buildTable = () => {
const rows = tasks.map((row) => {
const rowToUse = row as any;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
const modifiedProcessModelIdentifier =
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
title={`View process instance ${rowToUse.process_instance_id}`}
>
{rowToUse.process_instance_id}
</Link>
</td>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={rowToUse.process_model_identifier}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
<td>{rowToUse.username}</td>
<td>{rowToUse.group_identifier || '-'}</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<TableCellWithTimeAgoInWords
timeInSeconds={rowToUse.updated_at_in_seconds}
/>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
);
});
return (
<Table striped bordered>
<thead>
<tr>
<th>Id</th>
<th>Process</th>
<th>Task</th>
<th>Started By</th>
<th>Waiting For</th>
<th>Date Started</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
};
const tasksComponent = () => {
if (pagination && pagination.total < 1) {
return (
<p className="no-results-message with-large-bottom-margin">
You have no task assignments at this time.
</p>
);
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
undefined,
'tasks_waiting_for_me'
);
return (
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
paginationQueryParamPrefix="tasks_waiting_for_me"
paginationClassName="with-large-bottom-margin"
/>
);
};
return ( return (
<> <TaskListTable
<h2>Tasks waiting for me</h2> apiPath="/tasks/for-me"
<p className="data-table-description"> paginationQueryParamPrefix="tasks_waiting_for_me"
These processes are waiting on you to complete the next task. All are tableTitle="Tasks waiting for me"
processes created by others that are now actionable by you. tableDescription="These processes are waiting on you to complete the next task. All are processes created by others that are now actionable by you."
</p> paginationClassName="with-large-bottom-margin"
{tasksComponent()} textToShowIfEmpty="No tasks are waiting for you."
</> autoReload
showWaitingOn={false}
/>
); );
} }

View File

@ -1,156 +1,41 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
} from '../helpers';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import { PaginationObject } from '../interfaces'; import TaskListTable from './TaskListTable';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
const paginationQueryParamPrefix = 'tasks_waiting_for_my_groups';
const REFRESH_INTERVAL = 5;
const REFRESH_TIMEOUT = 600;
export default function TasksWaitingForMyGroups() { export default function TasksWaitingForMyGroups() {
const [searchParams] = useSearchParams(); const [userGroups, setUserGroups] = useState<string[] | null>(null);
const [tasks, setTasks] = useState([]);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => { useEffect(() => {
const getTasks = () => { HttpService.makeCallToBackend({
const { page, perPage } = getPageInfoFromSearchParams( path: `/user-groups/for-current-user`,
searchParams, successCallback: setUserGroups,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE, });
undefined, }, [setUserGroups]);
paginationQueryParamPrefix
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
HttpService.makeCallToBackend({
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
successCallback: setTasksFromResult,
});
};
getTasks();
return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
}, [searchParams]);
const buildTable = () => { const tableComponents = () => {
const rows = tasks.map((row) => { if (!userGroups) {
const rowToUse = row as any; return null;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`; }
const modifiedProcessModelIdentifier =
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier); return userGroups.map((userGroup: string) => {
return ( return (
<tr key={rowToUse.id}> <TaskListTable
<td> apiPath="/tasks/for-my-groups"
<Link additionalParams={`group_identifier=${userGroup}`}
data-qa="process-instance-show-link" paginationQueryParamPrefix={`group-tasks-${userGroup}`}
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`} tableTitle={`Tasks waiting for group: ${userGroup}`}
title={`View process instance ${rowToUse.process_instance_id}`} tableDescription={`This is a list of tasks for the ${userGroup} group. They can be completed by any member of the group.`}
> paginationClassName="with-large-bottom-margin"
{rowToUse.process_instance_id} textToShowIfEmpty="This group has no task assignments at this time."
</Link> autoReload
</td> showWaitingOn={false}
<td> />
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
title={rowToUse.process_model_identifier}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
<td>{rowToUse.username}</td>
<td>{rowToUse.group_identifier || '-'}</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<TableCellWithTimeAgoInWords
timeInSeconds={rowToUse.updated_at_in_seconds}
/>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
); );
}); });
return (
<Table striped bordered>
<thead>
<tr>
<th>Id</th>
<th>Process</th>
<th>Task</th>
<th>Started By</th>
<th>Waiting For</th>
<th>Date Started</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
}; };
const tasksComponent = () => { if (userGroups) {
if (pagination && pagination.total < 1) { return <>{tableComponents()}</>;
return ( }
<p className="no-results-message"> return null;
Your groups have no task assignments at this time.
</p>
);
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
undefined,
paginationQueryParamPrefix
);
return (
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
paginationQueryParamPrefix={paginationQueryParamPrefix}
/>
);
};
return (
<>
<h2>Tasks waiting for my groups</h2>
<p className="data-table-description">
This is a list of tasks for groups you belong to that can be completed
by any member of the group.
</p>
{tasksComponent()}
</>
);
} }

View File

@ -13,6 +13,12 @@ export interface RecentProcessModel {
export interface ProcessInstanceTask { export interface ProcessInstanceTask {
id: string; id: string;
process_model_display_name: string;
process_model_identifier: string;
task_title: string;
lane_assignment_id: string;
process_instance_status: number;
updated_at_in_seconds: number;
state: string; state: string;
process_identifier: string; process_identifier: string;
name: string; name: string;

View File

@ -1,6 +1,45 @@
import { useEffect, useState } from 'react';
import ProcessInstanceListTable from '../components/ProcessInstanceListTable'; import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
import HttpService from '../services/HttpService';
export default function CompletedInstances() { export default function CompletedInstances() {
const [userGroups, setUserGroups] = useState<string[] | null>(null);
useEffect(() => {
HttpService.makeCallToBackend({
path: `/user-groups/for-current-user`,
successCallback: setUserGroups,
});
}, [setUserGroups]);
const groupTableComponents = () => {
if (!userGroups) {
return null;
}
return userGroups.map((userGroup: string) => {
return (
<>
<h2>With tasks completed by group: {userGroup}</h2>
<p className="data-table-description">
This is a list of instances with tasks that were completed by the{' '}
{userGroup} group.
</p>
<ProcessInstanceListTable
filtersEnabled={false}
paginationQueryParamPrefix="group_completed_instances"
paginationClassName="with-large-bottom-margin"
perPageOptions={[2, 5, 25]}
reportIdentifier="system_report_instances_with_tasks_completed_by_my_groups"
showReports={false}
textToShowIfEmpty="This group has no completed instances at this time."
additionalParams={`group_identifier=${userGroup}`}
/>
</>
);
});
};
return ( return (
<> <>
<h2>My completed instances</h2> <h2>My completed instances</h2>
@ -17,7 +56,7 @@ export default function CompletedInstances() {
paginationClassName="with-large-bottom-margin" paginationClassName="with-large-bottom-margin"
autoReload autoReload
/> />
<h2>Tasks completed by me</h2> <h2>With tasks completed by me</h2>
<p className="data-table-description"> <p className="data-table-description">
This is a list of instances where you have completed tasks. This is a list of instances where you have completed tasks.
</p> </p>
@ -27,22 +66,10 @@ export default function CompletedInstances() {
perPageOptions={[2, 5, 25]} perPageOptions={[2, 5, 25]}
reportIdentifier="system_report_instances_with_tasks_completed_by_me" reportIdentifier="system_report_instances_with_tasks_completed_by_me"
showReports={false} showReports={false}
textToShowIfEmpty="You have no completed tasks at this time." textToShowIfEmpty="You have no completed instances at this time."
paginationClassName="with-large-bottom-margin" paginationClassName="with-large-bottom-margin"
/> />
<h2>Tasks completed by my groups</h2> {groupTableComponents()}
<p className="data-table-description">
This is a list of instances with tasks that were completed by groups you
belong to.
</p>
<ProcessInstanceListTable
filtersEnabled={false}
paginationQueryParamPrefix="group_completed_tasks"
perPageOptions={[2, 5, 25]}
reportIdentifier="system_report_instances_with_tasks_completed_by_my_groups"
showReports={false}
textToShowIfEmpty="Your group has no completed tasks at this time."
/>
</> </>
); );
} }