added all users to waiting for column on task list tables w/ burnettk
This commit is contained in:
parent
45097a10d9
commit
0af82d137d
|
@ -63,7 +63,7 @@ groups:
|
||||||
admin-ro:
|
admin-ro:
|
||||||
users:
|
users:
|
||||||
[
|
[
|
||||||
j,
|
j@sartography.com,
|
||||||
]
|
]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
|
@ -15,11 +15,14 @@ from flask import jsonify
|
||||||
from flask import make_response
|
from flask import make_response
|
||||||
from flask.wrappers import Response
|
from flask.wrappers import Response
|
||||||
from flask_bpmn.api.api_error import ApiError
|
from flask_bpmn.api.api_error import ApiError
|
||||||
|
from flask_bpmn.models.db import db
|
||||||
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy import asc
|
from sqlalchemy import asc
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
from sqlalchemy import func
|
||||||
|
from sqlalchemy.orm import aliased
|
||||||
|
|
||||||
from spiffworkflow_backend.models.group import GroupModel
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
||||||
|
@ -147,6 +150,18 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
||||||
process_instance.process_model_identifier,
|
process_instance.process_model_identifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
human_task = HumanTaskModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance_id, task_id=task_id
|
||||||
|
).first()
|
||||||
|
if human_task is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="no_human_task",
|
||||||
|
message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.",
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
form_schema_file_name = ""
|
form_schema_file_name = ""
|
||||||
form_ui_schema_file_name = ""
|
form_ui_schema_file_name = ""
|
||||||
spiff_task = _get_spiff_task_from_process_instance(task_id, process_instance)
|
spiff_task = _get_spiff_task_from_process_instance(task_id, process_instance)
|
||||||
|
@ -302,7 +317,7 @@ def task_submit(
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
error_code="no_human_task",
|
error_code="no_human_task",
|
||||||
message="Cannot find an human task with task id '{task_id}' for process instance {process_instance_id}.",
|
message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.",
|
||||||
status_code=500,
|
status_code=500,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -357,22 +372,25 @@ def _get_tasks(
|
||||||
# pagination later on
|
# pagination later on
|
||||||
# https://stackoverflow.com/q/34582014/6090676
|
# https://stackoverflow.com/q/34582014/6090676
|
||||||
human_tasks_query = (
|
human_tasks_query = (
|
||||||
HumanTaskModel.query.distinct()
|
db.session.query(HumanTaskModel)
|
||||||
|
.group_by(HumanTaskModel.id) # type: ignore
|
||||||
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
|
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
|
||||||
.join(ProcessInstanceModel)
|
.join(ProcessInstanceModel)
|
||||||
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
||||||
.filter(HumanTaskModel.completed == False) # noqa: E712
|
.filter(HumanTaskModel.completed == False) # noqa: E712
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assigned_user = aliased(UserModel)
|
||||||
if processes_started_by_user:
|
if processes_started_by_user:
|
||||||
human_tasks_query = human_tasks_query.filter(
|
human_tasks_query = (
|
||||||
|
human_tasks_query.filter(
|
||||||
ProcessInstanceModel.process_initiator_id == user_id
|
ProcessInstanceModel.process_initiator_id == user_id
|
||||||
).outerjoin(
|
)
|
||||||
|
.outerjoin(
|
||||||
HumanTaskUserModel,
|
HumanTaskUserModel,
|
||||||
and_(
|
|
||||||
HumanTaskUserModel.user_id == user_id,
|
|
||||||
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
||||||
),
|
)
|
||||||
|
.outerjoin(assigned_user, assigned_user.id == HumanTaskUserModel.user_id)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
human_tasks_query = human_tasks_query.filter(
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
@ -402,13 +420,15 @@ def _get_tasks(
|
||||||
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
|
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
|
||||||
ProcessInstanceModel.updated_at_in_seconds,
|
ProcessInstanceModel.updated_at_in_seconds,
|
||||||
ProcessInstanceModel.created_at_in_seconds,
|
ProcessInstanceModel.created_at_in_seconds,
|
||||||
UserModel.username,
|
UserModel.username.label("process_initiator_username"),
|
||||||
GroupModel.identifier.label("user_group_identifier"),
|
GroupModel.identifier.label("assigned_user_group_identifier"),
|
||||||
HumanTaskModel.task_name,
|
HumanTaskModel.task_name,
|
||||||
HumanTaskModel.task_title,
|
HumanTaskModel.task_title,
|
||||||
HumanTaskModel.process_model_display_name,
|
HumanTaskModel.process_model_display_name,
|
||||||
HumanTaskModel.process_instance_id,
|
HumanTaskModel.process_instance_id,
|
||||||
HumanTaskUserModel.user_id.label("current_user_is_potential_owner"),
|
func.group_concat(assigned_user.username.distinct()).label(
|
||||||
|
"potential_owner_usernames"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.order_by(desc(HumanTaskModel.id)) # type: ignore
|
.order_by(desc(HumanTaskModel.id)) # type: ignore
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
@ -422,6 +442,7 @@ def _get_tasks(
|
||||||
"pages": human_tasks.pages,
|
"pages": human_tasks.pages,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_response(jsonify(response_json), 200)
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -581,12 +581,6 @@ class ProcessInstanceProcessor:
|
||||||
)
|
)
|
||||||
return details_model
|
return details_model
|
||||||
|
|
||||||
def save_spiff_step_details(self) -> None:
|
|
||||||
"""SaveSpiffStepDetails."""
|
|
||||||
details_model = self.spiff_step_details()
|
|
||||||
db.session.add(details_model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def extract_metadata(self, process_model_info: ProcessModelInfo) -> None:
|
def extract_metadata(self, process_model_info: ProcessModelInfo) -> None:
|
||||||
"""Extract_metadata."""
|
"""Extract_metadata."""
|
||||||
metadata_extraction_paths = process_model_info.metadata_extraction_paths
|
metadata_extraction_paths = process_model_info.metadata_extraction_paths
|
||||||
|
@ -1233,9 +1227,13 @@ class ProcessInstanceProcessor:
|
||||||
self.increment_spiff_step()
|
self.increment_spiff_step()
|
||||||
self.bpmn_process_instance.complete_task_from_id(task.id)
|
self.bpmn_process_instance.complete_task_from_id(task.id)
|
||||||
human_task.completed_by_user_id = user.id
|
human_task.completed_by_user_id = user.id
|
||||||
|
human_task.completed = True
|
||||||
db.session.add(human_task)
|
db.session.add(human_task)
|
||||||
db.session.commit()
|
details_model = self.spiff_step_details()
|
||||||
self.save_spiff_step_details()
|
db.session.add(details_model)
|
||||||
|
|
||||||
|
# this is the thing that actually commits the db transaction (on behalf of the other updates above as well)
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_data(self) -> dict[str, Any]:
|
def get_data(self) -> dict[str, Any]:
|
||||||
"""Get_data."""
|
"""Get_data."""
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default function App() {
|
||||||
|
|
||||||
let message = <div>{errorObject.message}</div>;
|
let message = <div>{errorObject.message}</div>;
|
||||||
let title = 'Error:';
|
let title = 'Error:';
|
||||||
if ('task_name' in errorObject) {
|
if ('task_name' in errorObject && errorObject.task_name) {
|
||||||
title = `Error in python script:`;
|
title = `Error in python script:`;
|
||||||
message = (
|
message = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default function NavigationBar() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderGlobalAction className="username-header-text">
|
<HeaderGlobalAction className="username-header-text">
|
||||||
{UserService.getUsername()}
|
{UserService.getPreferredUsername()}
|
||||||
</HeaderGlobalAction>
|
</HeaderGlobalAction>
|
||||||
<HeaderGlobalAction
|
<HeaderGlobalAction
|
||||||
aria-label="Logout"
|
aria-label="Logout"
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Button, Table } from '@carbon/react';
|
import { Button, Table } from '@carbon/react';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
|
import UserService from '../services/UserService';
|
||||||
import PaginationForTable from './PaginationForTable';
|
import PaginationForTable from './PaginationForTable';
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
|
@ -46,6 +47,9 @@ export default function TaskListTable({
|
||||||
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
||||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
|
|
||||||
|
const preferredUsername = UserService.getPreferredUsername();
|
||||||
|
const userEmail = UserService.getUserEmail();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getTasks = () => {
|
const getTasks = () => {
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
@ -80,56 +84,82 @@ export default function TaskListTable({
|
||||||
autoReload,
|
autoReload,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const getWaitingForTableCellComponent = (
|
||||||
|
processInstanceTask: ProcessInstanceTask
|
||||||
|
) => {
|
||||||
|
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 =
|
||||||
|
processInstanceTask.potential_owner_usernames.split(',');
|
||||||
|
const firstTwoUsernames = usernames.slice(0, 2);
|
||||||
|
if (usernames.length > 2) {
|
||||||
|
firstTwoUsernames.push('...');
|
||||||
|
}
|
||||||
|
shortUsernameString = firstTwoUsernames.join(',');
|
||||||
|
}
|
||||||
|
return <span title={fullUsernameString}>{shortUsernameString}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
if (!tasks) {
|
if (!tasks) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const rows = tasks.map((row) => {
|
const rows = tasks.map((row: ProcessInstanceTask) => {
|
||||||
const rowToUse = row as any;
|
const taskUrl = `/tasks/${row.process_instance_id}/${row.task_id}`;
|
||||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
|
||||||
const modifiedProcessModelIdentifier =
|
const modifiedProcessModelIdentifier =
|
||||||
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
|
modifyProcessIdentifierForPathParam(row.process_model_identifier);
|
||||||
|
|
||||||
|
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
|
||||||
|
let hasAccessToCompleteTask = false;
|
||||||
|
if (row.potential_owner_usernames.match(regex)) {
|
||||||
|
hasAccessToCompleteTask = true;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<tr key={rowToUse.id}>
|
<tr key={row.id}>
|
||||||
<td>
|
<td>
|
||||||
<Link
|
<Link
|
||||||
data-qa="process-instance-show-link"
|
data-qa="process-instance-show-link"
|
||||||
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${row.process_instance_id}`}
|
||||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
title={`View process instance ${row.process_instance_id}`}
|
||||||
>
|
>
|
||||||
{rowToUse.process_instance_id}
|
{row.process_instance_id}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Link
|
<Link
|
||||||
data-qa="process-model-show-link"
|
data-qa="process-model-show-link"
|
||||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||||
title={rowToUse.process_model_identifier}
|
title={row.process_model_identifier}
|
||||||
>
|
>
|
||||||
{rowToUse.process_model_display_name}
|
{row.process_model_display_name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
|
title={`task id: ${row.name}, spiffworkflow task guid: ${row.id}`}
|
||||||
>
|
>
|
||||||
{rowToUse.task_title}
|
{row.task_title}
|
||||||
</td>
|
</td>
|
||||||
{showStartedBy ? <td>{rowToUse.username}</td> : ''}
|
{showStartedBy ? <td>{row.process_initiator_username}</td> : ''}
|
||||||
{showWaitingOn ? <td>{rowToUse.group_identifier || '-'}</td> : ''}
|
{showWaitingOn ? <td>{getWaitingForTableCellComponent(row)}</td> : ''}
|
||||||
<td>
|
<td>
|
||||||
{convertSecondsToFormattedDateTime(
|
{convertSecondsToFormattedDateTime(row.created_at_in_seconds) ||
|
||||||
rowToUse.created_at_in_seconds
|
'-'}
|
||||||
) || '-'}
|
|
||||||
</td>
|
</td>
|
||||||
<TableCellWithTimeAgoInWords
|
<TableCellWithTimeAgoInWords
|
||||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
timeInSeconds={row.updated_at_in_seconds}
|
||||||
/>
|
/>
|
||||||
<td>
|
<td>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
href={taskUrl}
|
href={taskUrl}
|
||||||
hidden={rowToUse.process_instance_status === 'suspended'}
|
hidden={row.process_instance_status === 'suspended'}
|
||||||
disabled={!rowToUse.current_user_is_potential_owner}
|
disabled={!hasAccessToCompleteTask}
|
||||||
>
|
>
|
||||||
Go
|
Go
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -17,16 +17,23 @@ export interface RecentProcessModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessInstanceTask {
|
export interface ProcessInstanceTask {
|
||||||
id: string;
|
id: number;
|
||||||
|
task_id: string;
|
||||||
|
process_instance_id: number;
|
||||||
process_model_display_name: string;
|
process_model_display_name: string;
|
||||||
process_model_identifier: string;
|
process_model_identifier: string;
|
||||||
task_title: string;
|
task_title: string;
|
||||||
lane_assignment_id: string;
|
lane_assignment_id: string;
|
||||||
process_instance_status: number;
|
process_instance_status: string;
|
||||||
updated_at_in_seconds: number;
|
|
||||||
state: string;
|
state: string;
|
||||||
process_identifier: string;
|
process_identifier: string;
|
||||||
name: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessReference {
|
export interface ProcessReference {
|
||||||
|
|
|
@ -39,7 +39,16 @@ const isLoggedIn = () => {
|
||||||
return !!getAuthToken();
|
return !!getAuthToken();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUsername = () => {
|
const getUserEmail = () => {
|
||||||
|
const idToken = getIdToken();
|
||||||
|
if (idToken) {
|
||||||
|
const idObject = jwt(idToken);
|
||||||
|
return (idObject as any).email;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPreferredUsername = () => {
|
||||||
const idToken = getIdToken();
|
const idToken = getIdToken();
|
||||||
if (idToken) {
|
if (idToken) {
|
||||||
const idObject = jwt(idToken);
|
const idObject = jwt(idToken);
|
||||||
|
@ -78,7 +87,8 @@ const UserService = {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
getAuthToken,
|
getAuthToken,
|
||||||
getAuthTokenFromParams,
|
getAuthTokenFromParams,
|
||||||
getUsername,
|
getPreferredUsername,
|
||||||
|
getUserEmail,
|
||||||
hasRole,
|
hasRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue