added more task tables w/ burnettk
This commit is contained in:
parent
2d21bd50d2
commit
43cbcf7924
|
@ -38,10 +38,13 @@ class MyJSONEncoder(DefaultJSONProvider):
|
||||||
return_dict = {}
|
return_dict = {}
|
||||||
for row_key in obj.keys():
|
for row_key in obj.keys():
|
||||||
row_value = obj[row_key]
|
row_value = obj[row_key]
|
||||||
if hasattr(row_value, "__dict__"):
|
if hasattr(row_value, "serialized"):
|
||||||
|
return_dict.update(row_value.serialized)
|
||||||
|
elif hasattr(row_value, "__dict__"):
|
||||||
return_dict.update(row_value.__dict__)
|
return_dict.update(row_value.__dict__)
|
||||||
else:
|
else:
|
||||||
return_dict.update({row_key: row_value})
|
return_dict.update({row_key: row_value})
|
||||||
|
if "_sa_instance_state" in return_dict:
|
||||||
return_dict.pop("_sa_instance_state")
|
return_dict.pop("_sa_instance_state")
|
||||||
return return_dict
|
return return_dict
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
|
@ -901,6 +901,35 @@ paths:
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Task"
|
$ref: "#/components/schemas/Task"
|
||||||
|
|
||||||
|
/tasks/for-processes-started-by-others:
|
||||||
|
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
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Process Instances
|
||||||
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_processes_started_by_others
|
||||||
|
summary: returns the list of tasks for given user's open process instances
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: list of tasks
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
|
||||||
/process-instance/{process_instance_id}/tasks:
|
/process-instance/{process_instance_id}/tasks:
|
||||||
parameters:
|
parameters:
|
||||||
- name: process_instance_id
|
- name: process_instance_id
|
||||||
|
|
|
@ -99,17 +99,17 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
local_bpmn_xml_file_contents = ""
|
local_bpmn_xml_file_contents = ""
|
||||||
if self.bpmn_xml_file_contents:
|
if self.bpmn_xml_file_contents:
|
||||||
local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8")
|
local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"process_model_identifier": self.process_model_identifier,
|
"process_model_identifier": self.process_model_identifier,
|
||||||
"process_group_identifier": self.process_group_identifier,
|
"process_group_identifier": self.process_group_identifier,
|
||||||
"status": self.status,
|
"status": self.status,
|
||||||
"bpmn_json": self.bpmn_json,
|
|
||||||
"start_in_seconds": self.start_in_seconds,
|
"start_in_seconds": self.start_in_seconds,
|
||||||
"end_in_seconds": self.end_in_seconds,
|
"end_in_seconds": self.end_in_seconds,
|
||||||
"process_initiator_id": self.process_initiator_id,
|
"process_initiator_id": self.process_initiator_id,
|
||||||
"bpmn_xml_file_contents": local_bpmn_xml_file_contents,
|
"bpmn_xml_file_contents": local_bpmn_xml_file_contents,
|
||||||
|
"bpmn_version_control_identifier": self.bpmn_version_control_identifier,
|
||||||
|
"bpmn_version_control_type": self.bpmn_version_control_type,
|
||||||
"spiff_step": self.spiff_step,
|
"spiff_step": self.spiff_step,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ from spiffworkflow_backend.services.secret_service import SecretService
|
||||||
from spiffworkflow_backend.services.service_task_service import ServiceTaskService
|
from spiffworkflow_backend.services.service_task_service import ServiceTaskService
|
||||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
from spiffworkflow_backend.services.user_service import UserService
|
from spiffworkflow_backend.services.user_service import UserService
|
||||||
from sqlalchemy import asc
|
from sqlalchemy import and_, asc
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
|
||||||
|
|
||||||
|
@ -996,14 +996,30 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
|
||||||
return make_response(jsonify(response_json), 200)
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
# @process_api_blueprint.route("/v1.0/tasks", methods=["GET"])
|
|
||||||
def task_list_for_my_open_processes(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def task_list_for_processes_started_by_others(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
|
return get_tasks(processes_started_by_user=False, page=page, per_page=per_page)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tasks(processes_started_by_user: bool = True, page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
user_id = g.user.id
|
user_id = g.user.id
|
||||||
active_tasks = (
|
active_tasks_query = (
|
||||||
ActiveTaskModel.query.order_by(desc(ActiveTaskModel.id)) # type: ignore
|
ActiveTaskModel.query
|
||||||
.join(ProcessInstanceModel)
|
.join(ProcessInstanceModel)
|
||||||
.filter_by(process_initiator_id=user_id)
|
.order_by(desc(ProcessInstanceModel.created_at_in_seconds)) # type: ignore
|
||||||
.outerjoin(GroupModel)
|
)
|
||||||
|
|
||||||
|
if processes_started_by_user:
|
||||||
|
active_tasks_query = active_tasks_query.filter_by(process_initiator_id=user_id)
|
||||||
|
else:
|
||||||
|
active_tasks_query = active_tasks_query.filter(ProcessInstanceModel.process_initiator_id != user_id)
|
||||||
|
|
||||||
|
active_tasks = (
|
||||||
|
active_tasks_query.outerjoin(GroupModel)
|
||||||
|
.outerjoin(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id))
|
||||||
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
||||||
.add_columns(
|
.add_columns(
|
||||||
ProcessInstanceModel.process_model_identifier,
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
@ -1015,10 +1031,10 @@ def task_list_for_my_open_processes(page: int = 1, per_page: int = 100) -> flask
|
||||||
ActiveTaskModel.task_title,
|
ActiveTaskModel.task_title,
|
||||||
ActiveTaskModel.process_model_display_name,
|
ActiveTaskModel.process_model_display_name,
|
||||||
ActiveTaskModel.process_instance_id,
|
ActiveTaskModel.process_instance_id,
|
||||||
|
ActiveTaskUserModel.user_id.label("current_user_is_potential_owner")
|
||||||
)
|
)
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
)
|
)
|
||||||
# tasks = [ActiveTaskModel.to_task(active_task) for active_task in active_tasks.items]
|
|
||||||
|
|
||||||
response_json = {
|
response_json = {
|
||||||
"results": active_tasks.items,
|
"results": active_tasks.items,
|
||||||
|
@ -1028,7 +1044,6 @@ def task_list_for_my_open_processes(page: int = 1, per_page: int = 100) -> flask
|
||||||
"pages": active_tasks.pages,
|
"pages": active_tasks.pages,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_response(jsonify(response_json), 200)
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1383,12 +1398,21 @@ def find_principal_or_raise() -> PrincipalModel:
|
||||||
|
|
||||||
|
|
||||||
def find_process_instance_by_id_or_raise(
|
def find_process_instance_by_id_or_raise(
|
||||||
process_instance_id: int,
|
process_instance_id: int
|
||||||
) -> ProcessInstanceModel:
|
) -> ProcessInstanceModel:
|
||||||
"""Find_process_instance_by_id_or_raise."""
|
"""Find_process_instance_by_id_or_raise."""
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(
|
process_instance_query = ProcessInstanceModel.query.filter_by(
|
||||||
id=process_instance_id
|
id=process_instance_id
|
||||||
).first()
|
)
|
||||||
|
|
||||||
|
# we had a frustrating session trying to do joins and access columns from two tables. here's some notes for our future selves:
|
||||||
|
# this returns an object that allows you to do: process_instance.UserModel.username
|
||||||
|
# process_instance = db.session.query(ProcessInstanceModel, UserModel).filter_by(id=process_instance_id).first()
|
||||||
|
# you can also use splat with add_columns, but it still didn't ultimately give us access to the process instance
|
||||||
|
# attributes or username like we wanted:
|
||||||
|
# process_instance_query.join(UserModel).add_columns(*ProcessInstanceModel.__table__.columns, UserModel.username)
|
||||||
|
|
||||||
|
process_instance = process_instance_query.first()
|
||||||
if process_instance is None:
|
if process_instance is None:
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
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,
|
||||||
|
modifyProcessModelPath,
|
||||||
|
} from '../helpers';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
import { PaginationObject } from '../interfaces';
|
||||||
|
|
||||||
|
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||||
|
|
||||||
|
export default function MyTasksForProcessesStartedByOthers() {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
const setTasksFromResult = (result: any) => {
|
||||||
|
setTasks(result.results);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
};
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/tasks/for-processes-started-by-others?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 = modifyProcessModelPath(
|
||||||
|
rowToUse.process_model_identifier
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<tr key={rowToUse.id}>
|
||||||
|
<td>
|
||||||
|
<Link
|
||||||
|
data-qa="process-model-show-link"
|
||||||
|
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||||
|
>
|
||||||
|
{rowToUse.process_model_display_name}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Link
|
||||||
|
data-qa="process-instance-show-link"
|
||||||
|
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||||
|
>
|
||||||
|
View {rowToUse.process_instance_id}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
|
||||||
|
>
|
||||||
|
{rowToUse.task_title}
|
||||||
|
</td>
|
||||||
|
<td>{rowToUse.process_instance_status}</td>
|
||||||
|
<td>{rowToUse.group_identifier || '-'}</td>
|
||||||
|
<td>
|
||||||
|
{convertSecondsToFormattedDateTime(
|
||||||
|
rowToUse.created_at_in_seconds
|
||||||
|
) || '-'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{convertSecondsToFormattedDateTime(
|
||||||
|
rowToUse.updated_at_in_seconds
|
||||||
|
) || '-'}
|
||||||
|
</td>
|
||||||
|
<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>Process Model</th>
|
||||||
|
<th>Process Instance</th>
|
||||||
|
<th>Task Name</th>
|
||||||
|
<th>Process Instance Status</th>
|
||||||
|
<th>Assigned Group</th>
|
||||||
|
<th>Process Started</th>
|
||||||
|
<th>Process Updated</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tasksComponent = () => {
|
||||||
|
if (pagination && pagination.total < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Tasks waiting for me</h1>
|
||||||
|
<PaginationForTable
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||||
|
pagination={pagination}
|
||||||
|
tableToDisplay={buildTable()}
|
||||||
|
path="/tasks/for-my-open-processes"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination) {
|
||||||
|
return tasksComponent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -2,7 +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 PaginationForTable from '../components/PaginationForTable';
|
import PaginationForTable from './PaginationForTable';
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
getPageInfoFromSearchParams,
|
getPageInfoFromSearchParams,
|
||||||
|
@ -36,7 +36,7 @@ export default function MyOpenProcesses() {
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
const rows = tasks.map((row) => {
|
const rows = tasks.map((row) => {
|
||||||
const rowToUse = row as any;
|
const rowToUse = row as any;
|
||||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.id}`;
|
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
||||||
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||||
rowToUse.process_model_identifier
|
rowToUse.process_model_identifier
|
||||||
);
|
);
|
||||||
|
@ -80,6 +80,7 @@ export default function MyOpenProcesses() {
|
||||||
variant="primary"
|
variant="primary"
|
||||||
href={taskUrl}
|
href={taskUrl}
|
||||||
hidden={rowToUse.process_instance_status === 'suspended'}
|
hidden={rowToUse.process_instance_status === 'suspended'}
|
||||||
|
disabled={!rowToUse.current_user_is_potential_owner}
|
||||||
>
|
>
|
||||||
Go
|
Go
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -106,7 +107,7 @@ export default function MyOpenProcesses() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tasksWaitingForMeComponent = () => {
|
const tasksComponent = () => {
|
||||||
if (pagination && pagination.total < 1) {
|
if (pagination && pagination.total < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -129,13 +130,8 @@ export default function MyOpenProcesses() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tasksWaitingForMe = tasksWaitingForMeComponent();
|
|
||||||
|
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
if (tasksWaitingForMe === null) {
|
return tasksComponent();
|
||||||
return <p>No tasks are waiting for you.</p>;
|
|
||||||
}
|
|
||||||
return <>{tasksWaitingForMe}</>;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers';
|
||||||
|
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||||
|
|
||||||
|
export default function GroupedTasks() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TasksForMyOpenProcesses />
|
||||||
|
<br />
|
||||||
|
<MyTasksForProcessesStartedByOthers />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import { Tabs, TabList, Tab } from '@carbon/react';
|
||||||
import TaskShow from './TaskShow';
|
import TaskShow from './TaskShow';
|
||||||
import ErrorContext from '../contexts/ErrorContext';
|
import ErrorContext from '../contexts/ErrorContext';
|
||||||
import MyTasks from './MyTasks';
|
import MyTasks from './MyTasks';
|
||||||
import TasksForMyOpenProcesses from './TasksForMyOpenProcesses';
|
import GroupedTasks from './GroupedTasks';
|
||||||
|
|
||||||
export default function HomePageRoutes() {
|
export default function HomePageRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -16,7 +16,7 @@ export default function HomePageRoutes() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
let newSelectedTabIndex = 0;
|
let newSelectedTabIndex = 0;
|
||||||
if (location.pathname.match(/^\/tasks\/for-my-open-processes/)) {
|
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
||||||
newSelectedTabIndex = 1;
|
newSelectedTabIndex = 1;
|
||||||
}
|
}
|
||||||
setSelectedTabIndex(newSelectedTabIndex);
|
setSelectedTabIndex(newSelectedTabIndex);
|
||||||
|
@ -27,9 +27,7 @@ export default function HomePageRoutes() {
|
||||||
<Tabs selectedIndex={selectedTabIndex}>
|
<Tabs selectedIndex={selectedTabIndex}>
|
||||||
<TabList aria-label="List of tabs">
|
<TabList aria-label="List of tabs">
|
||||||
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
||||||
<Tab onClick={() => navigate('/tasks/for-my-open-processes')}>
|
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
||||||
Tasks for My Open Processes
|
|
||||||
</Tab>
|
|
||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<br />
|
<br />
|
||||||
|
@ -37,10 +35,7 @@ export default function HomePageRoutes() {
|
||||||
<Route path="/" element={<MyTasks />} />
|
<Route path="/" element={<MyTasks />} />
|
||||||
<Route path="my-tasks" element={<MyTasks />} />
|
<Route path="my-tasks" element={<MyTasks />} />
|
||||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||||
<Route
|
<Route path="grouped" element={<GroupedTasks />} />
|
||||||
path="for-my-open-processes"
|
|
||||||
element={<TasksForMyOpenProcesses />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue