added more task tables w/ burnettk

This commit is contained in:
jasquat 2022-11-11 16:31:48 -05:00
parent 7e97ce674f
commit d4e984a5ce
8 changed files with 229 additions and 33 deletions

View File

@ -38,11 +38,14 @@ 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})
return_dict.pop("_sa_instance_state") if "_sa_instance_state" in return_dict:
return_dict.pop("_sa_instance_state")
return return_dict return return_dict
return super().default(obj) return super().default(obj)

View File

@ -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

View File

@ -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,
} }

View File

@ -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(

View File

@ -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;
}

View File

@ -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;
} }

View File

@ -0,0 +1,12 @@
import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers';
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
export default function GroupedTasks() {
return (
<>
<TasksForMyOpenProcesses />
<br />
<MyTasksForProcessesStartedByOthers />
</>
);
}

View File

@ -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>
</> </>
); );