From 0f5d2855d4b6b8234be3b45821a350d8228738fd Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 10 Nov 2022 17:30:27 -0500 Subject: [PATCH 01/20] added home page routes and some tab stuff w/ burnettk --- .../src/spiffworkflow_backend/api.yml | 29 ++++++++++++ .../routes/process_api_blueprint.py | 37 +++++++++++++++ spiffworkflow-frontend/src/App.tsx | 15 ++----- .../src/routes/HomePageRoutes.tsx | 45 +++++++++++++++++++ .../src/routes/{HomePage.tsx => MyTasks.tsx} | 2 +- 5 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 spiffworkflow-frontend/src/routes/HomePageRoutes.tsx rename spiffworkflow-frontend/src/routes/{HomePage.tsx => MyTasks.tsx} (99%) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index cbd21576..9f3c03ab 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -926,6 +926,35 @@ paths: items: $ref: "#/components/schemas/Task" + /tasks/for-my-open-processes: + 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_my_open_processes + 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: parameters: - name: process_instance_id diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index e37f2d0b..49dc03d3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -13,6 +13,7 @@ from typing import Union import connexion # type: ignore import flask.wrappers import jinja2 +from spiffworkflow_backend.models.group import GroupModel import werkzeug from flask import Blueprint from flask import current_app @@ -989,6 +990,42 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res 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: + user_id = g.user.id + active_tasks = ( + ActiveTaskModel.query.order_by(desc(ActiveTaskModel.id)) # type: ignore + .join(ProcessInstanceModel) + .filter_by(process_initiator_id=user_id) + .outerjoin(GroupModel) + # just need this add_columns to add the process_model_identifier. Then add everything back that was removed. + .add_columns( + ProcessInstanceModel.process_model_identifier, + ProcessInstanceModel.status, + ProcessInstanceModel.updated_at_in_seconds, + ProcessInstanceModel.created_at_in_seconds, + GroupModel.identifier.label("group_identifier"), + ActiveTaskModel.task_name, + ActiveTaskModel.task_title, + ActiveTaskModel.process_model_display_name, + ActiveTaskModel.process_instance_id, + ) + .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 = { + "results": active_tasks.items, + "pagination": { + "count": len(active_tasks.items), + "total": active_tasks.total, + "pages": active_tasks.pages, + }, + } + + return make_response(jsonify(response_json), 200) + + def process_instance_task_list( process_instance_id: int, all_tasks: bool = False, spiff_step: int = 0 ) -> flask.wrappers.Response: diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index 2561c2a8..2d59f0de 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -6,8 +6,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import ErrorContext from './contexts/ErrorContext'; import NavigationBar from './components/NavigationBar'; -import HomePage from './routes/HomePage'; -import TaskShow from './routes/TaskShow'; +import HomePageRoutes from './routes/HomePageRoutes'; import ErrorBoundary from './components/ErrorBoundary'; import AdminRoutes from './routes/AdminRoutes'; import { ErrorForDisplay } from './interfaces'; @@ -54,17 +53,9 @@ export default function App() { {errorTag} - } /> - } /> + } /> + } /> } /> - } - /> - } - /> diff --git a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx new file mode 100644 index 00000000..fa0be5fd --- /dev/null +++ b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx @@ -0,0 +1,45 @@ +import { useContext, useEffect, useState } from 'react'; +import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; +// @ts-ignore +import { Tabs, TabList, Tab } from '@carbon/react'; +import TaskShow from './TaskShow'; +import ErrorContext from '../contexts/ErrorContext'; +import MyTasks from './MyTasks'; + +export default function HomePageRoutes() { + const location = useLocation(); + const setErrorMessage = (useContext as any)(ErrorContext)[1]; + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + const navigate = useNavigate(); + + useEffect(() => { + setErrorMessage(null); + }, [location, setErrorMessage]); + + // selectedIndex={selectedTabIndex} + // onChange={(event: any) => { + // setSelectedTabIndex(event.selectedIndex); + // }} + return ( + <> +

HELO

+ + + navigate('http://www.google.com')}> + Tab Label 1 + + Tab Label 2 + Tab Label 3 + + Tab Label 4 with a very long long title + + Tab Label 5 + + + + } /> + } /> + + + ); +} diff --git a/spiffworkflow-frontend/src/routes/HomePage.tsx b/spiffworkflow-frontend/src/routes/MyTasks.tsx similarity index 99% rename from spiffworkflow-frontend/src/routes/HomePage.tsx rename to spiffworkflow-frontend/src/routes/MyTasks.tsx index 71fda73f..6b6eabd2 100644 --- a/spiffworkflow-frontend/src/routes/HomePage.tsx +++ b/spiffworkflow-frontend/src/routes/MyTasks.tsx @@ -12,7 +12,7 @@ import { PaginationObject, RecentProcessModel } from '../interfaces'; const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; -export default function HomePage() { +export default function MyTasks() { const [searchParams] = useSearchParams(); const [tasks, setTasks] = useState([]); const [pagination, setPagination] = useState(null); From 57ec4a31fc178d8128ce6c1c19460c1b31b9a3c6 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 11 Nov 2022 10:28:08 -0500 Subject: [PATCH 02/20] some more task tab play --- spiffworkflow-frontend/package-lock.json | 5 +-- .../src/routes/HomePageRoutes.tsx | 34 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 258b3e58..79b2bbb5 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -46545,7 +46545,7 @@ "@csstools/postcss-text-decoration-shorthand": "^1.0.0", "@csstools/postcss-trigonometric-functions": "^1.0.2", "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "10.4.8", + "autoprefixer": "10.4.5", "browserslist": "^4.21.3", "css-blank-pseudo": "^3.0.3", "css-has-pseudo": "^3.0.4", @@ -46583,7 +46583,8 @@ }, "dependencies": { "autoprefixer": { - "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "requires": { "browserslist": "^4.20.2", diff --git a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx index fa0be5fd..261e9035 100644 --- a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx @@ -14,30 +14,36 @@ export default function HomePageRoutes() { useEffect(() => { setErrorMessage(null); + let newSelectedTabIndex = 0; + if (location.pathname.match(/^\/tasks\/\d/)) { + newSelectedTabIndex = 1; + } + setSelectedTabIndex(newSelectedTabIndex); }, [location, setErrorMessage]); - // selectedIndex={selectedTabIndex} - // onChange={(event: any) => { - // setSelectedTabIndex(event.selectedIndex); - // }} return ( <> -

HELO

- + { + setSelectedTabIndex(event.selectedIndex); + }} + > - navigate('http://www.google.com')}> - Tab Label 1 + navigate('/tasks/my-tasks')}>My Tasks + + navigate('/tasks/9/4dc9f6e3-2256-47b2-9f78-6bc2f061db80') + } + > + One Task - Tab Label 2 - Tab Label 3 - - Tab Label 4 with a very long long title - - Tab Label 5 +
} /> + } /> } /> From 2d21bd50d2f1642e8f04c81fc069c24d7353b512 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 11 Nov 2022 11:07:21 -0500 Subject: [PATCH 03/20] added tasks for my open processes page w/ burnettk --- .../routes/process_api_blueprint.py | 2 +- .../src/routes/HomePageRoutes.tsx | 24 ++- .../src/routes/TasksForMyOpenProcesses.tsx | 141 ++++++++++++++++++ 3 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index 6e8bc3eb..a5b92870 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1007,7 +1007,7 @@ def task_list_for_my_open_processes(page: int = 1, per_page: int = 100) -> flask # just need this add_columns to add the process_model_identifier. Then add everything back that was removed. .add_columns( ProcessInstanceModel.process_model_identifier, - ProcessInstanceModel.status, + ProcessInstanceModel.status.label("process_instance_status"), ProcessInstanceModel.updated_at_in_seconds, ProcessInstanceModel.created_at_in_seconds, GroupModel.identifier.label("group_identifier"), diff --git a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx index 261e9035..cbd1bd4e 100644 --- a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx @@ -5,6 +5,7 @@ import { Tabs, TabList, Tab } from '@carbon/react'; import TaskShow from './TaskShow'; import ErrorContext from '../contexts/ErrorContext'; import MyTasks from './MyTasks'; +import TasksForMyOpenProcesses from './TasksForMyOpenProcesses'; export default function HomePageRoutes() { const location = useLocation(); @@ -15,7 +16,7 @@ export default function HomePageRoutes() { useEffect(() => { setErrorMessage(null); let newSelectedTabIndex = 0; - if (location.pathname.match(/^\/tasks\/\d/)) { + if (location.pathname.match(/^\/tasks\/for-my-open-processes/)) { newSelectedTabIndex = 1; } setSelectedTabIndex(newSelectedTabIndex); @@ -23,28 +24,23 @@ export default function HomePageRoutes() { return ( <> - { - setSelectedTabIndex(event.selectedIndex); - }} - > + navigate('/tasks/my-tasks')}>My Tasks - - navigate('/tasks/9/4dc9f6e3-2256-47b2-9f78-6bc2f061db80') - } - > - One Task + navigate('/tasks/for-my-open-processes')}> + Tasks for My Open Processes
} /> - } /> + } /> } /> + } + /> ); diff --git a/spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx new file mode 100644 index 00000000..9ac84334 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'react'; +// @ts-ignore +import { Button, Table } from '@carbon/react'; +import { Link, useSearchParams } from 'react-router-dom'; +import PaginationForTable from '../components/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 MyOpenProcesses() { + const [searchParams] = useSearchParams(); + const [tasks, setTasks] = useState([]); + const [pagination, setPagination] = useState(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-my-open-processes?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.id}`; + const modifiedProcessModelIdentifier = modifyProcessModelPath( + rowToUse.process_model_identifier + ); + return ( + + + + {rowToUse.process_model_display_name} + + + + + View {rowToUse.process_instance_id} + + + + {rowToUse.task_title} + + {rowToUse.process_instance_status} + {rowToUse.group_identifier || '-'} + + {convertSecondsToFormattedDateTime( + rowToUse.created_at_in_seconds + ) || '-'} + + + {convertSecondsToFormattedDateTime( + rowToUse.updated_at_in_seconds + ) || '-'} + + + + + + ); + }); + return ( + + + + + + + + + + + + + + {rows} +
Process ModelProcess InstanceTask NameProcess Instance StatusAssigned GroupProcess StartedProcess UpdatedActions
+ ); + }; + + const tasksWaitingForMeComponent = () => { + if (pagination && pagination.total < 1) { + return null; + } + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + PER_PAGE_FOR_TASKS_ON_HOME_PAGE + ); + return ( + <> +

Tasks for my open processes

+ + + ); + }; + + const tasksWaitingForMe = tasksWaitingForMeComponent(); + + if (pagination) { + if (tasksWaitingForMe === null) { + return

No tasks are waiting for you.

; + } + return <>{tasksWaitingForMe}; + } + return null; +} From 43cbcf7924fbf811e72f14e3c97e509d38c95c91 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 11 Nov 2022 16:31:48 -0500 Subject: [PATCH 04/20] added more task tables w/ burnettk --- .../src/spiffworkflow_backend/__init__.py | 7 +- .../src/spiffworkflow_backend/api.yml | 29 ++++ .../models/process_instance.py | 4 +- .../routes/process_api_blueprint.py | 46 ++++-- .../MyTasksForProcessesStartedByOthers.tsx | 137 ++++++++++++++++++ .../TasksForMyOpenProcesses.tsx | 14 +- .../src/routes/GroupedTasks.tsx | 12 ++ .../src/routes/HomePageRoutes.tsx | 13 +- 8 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx rename spiffworkflow-frontend/src/{routes => components}/TasksForMyOpenProcesses.tsx (92%) create mode 100644 spiffworkflow-frontend/src/routes/GroupedTasks.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index 9f1a74e7..a6dc7f38 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -38,11 +38,14 @@ class MyJSONEncoder(DefaultJSONProvider): return_dict = {} for row_key in obj.keys(): 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__) else: 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 super().default(obj) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 7f82d29b..a87d5a2f 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -901,6 +901,35 @@ paths: items: $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: parameters: - name: process_instance_id diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index cc53623e..ee95007b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -99,17 +99,17 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): local_bpmn_xml_file_contents = "" if self.bpmn_xml_file_contents: local_bpmn_xml_file_contents = self.bpmn_xml_file_contents.decode("utf-8") - return { "id": self.id, "process_model_identifier": self.process_model_identifier, "process_group_identifier": self.process_group_identifier, "status": self.status, - "bpmn_json": self.bpmn_json, "start_in_seconds": self.start_in_seconds, "end_in_seconds": self.end_in_seconds, "process_initiator_id": self.process_initiator_id, "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, } diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index a5b92870..592d6d5f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -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.spec_file_service import SpecFileService from spiffworkflow_backend.services.user_service import UserService -from sqlalchemy import asc +from sqlalchemy import and_, asc 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) -# @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: + 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 - active_tasks = ( - ActiveTaskModel.query.order_by(desc(ActiveTaskModel.id)) # type: ignore + active_tasks_query = ( + ActiveTaskModel.query .join(ProcessInstanceModel) - .filter_by(process_initiator_id=user_id) - .outerjoin(GroupModel) + .order_by(desc(ProcessInstanceModel.created_at_in_seconds)) # type: ignore + ) + + 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. .add_columns( 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.process_model_display_name, ActiveTaskModel.process_instance_id, + ActiveTaskUserModel.user_id.label("current_user_is_potential_owner") ) .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 = { "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, }, } - 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( - process_instance_id: int, + process_instance_id: int ) -> ProcessInstanceModel: """Find_process_instance_by_id_or_raise.""" - process_instance = ProcessInstanceModel.query.filter_by( + process_instance_query = ProcessInstanceModel.query.filter_by( 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: raise ( ApiError( diff --git a/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx b/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx new file mode 100644 index 00000000..d8de933d --- /dev/null +++ b/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx @@ -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(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 ( + + + + {rowToUse.process_model_display_name} + + + + + View {rowToUse.process_instance_id} + + + + {rowToUse.task_title} + + {rowToUse.process_instance_status} + {rowToUse.group_identifier || '-'} + + {convertSecondsToFormattedDateTime( + rowToUse.created_at_in_seconds + ) || '-'} + + + {convertSecondsToFormattedDateTime( + rowToUse.updated_at_in_seconds + ) || '-'} + + + + + + ); + }); + return ( + + + + + + + + + + + + + + {rows} +
Process ModelProcess InstanceTask NameProcess Instance StatusAssigned GroupProcess StartedProcess UpdatedActions
+ ); + }; + + const tasksComponent = () => { + if (pagination && pagination.total < 1) { + return null; + } + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + PER_PAGE_FOR_TASKS_ON_HOME_PAGE + ); + return ( + <> +

Tasks waiting for me

+ + + ); + }; + + if (pagination) { + return tasksComponent(); + } + return null; +} diff --git a/spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx similarity index 92% rename from spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx rename to spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx index 9ac84334..8a2f0bdd 100644 --- a/spiffworkflow-frontend/src/routes/TasksForMyOpenProcesses.tsx +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; // @ts-ignore import { Button, Table } from '@carbon/react'; import { Link, useSearchParams } from 'react-router-dom'; -import PaginationForTable from '../components/PaginationForTable'; +import PaginationForTable from './PaginationForTable'; import { convertSecondsToFormattedDateTime, getPageInfoFromSearchParams, @@ -36,7 +36,7 @@ export default function MyOpenProcesses() { const buildTable = () => { const rows = tasks.map((row) => { 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( rowToUse.process_model_identifier ); @@ -80,6 +80,7 @@ export default function MyOpenProcesses() { variant="primary" href={taskUrl} hidden={rowToUse.process_instance_status === 'suspended'} + disabled={!rowToUse.current_user_is_potential_owner} > Go @@ -106,7 +107,7 @@ export default function MyOpenProcesses() { ); }; - const tasksWaitingForMeComponent = () => { + const tasksComponent = () => { if (pagination && pagination.total < 1) { return null; } @@ -129,13 +130,8 @@ export default function MyOpenProcesses() { ); }; - const tasksWaitingForMe = tasksWaitingForMeComponent(); - if (pagination) { - if (tasksWaitingForMe === null) { - return

No tasks are waiting for you.

; - } - return <>{tasksWaitingForMe}; + return tasksComponent(); } return null; } diff --git a/spiffworkflow-frontend/src/routes/GroupedTasks.tsx b/spiffworkflow-frontend/src/routes/GroupedTasks.tsx new file mode 100644 index 00000000..7312bc55 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/GroupedTasks.tsx @@ -0,0 +1,12 @@ +import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers'; +import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses'; + +export default function GroupedTasks() { + return ( + <> + +
+ + + ); +} diff --git a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx index cbd1bd4e..d02cb9d7 100644 --- a/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx @@ -5,7 +5,7 @@ import { Tabs, TabList, Tab } from '@carbon/react'; import TaskShow from './TaskShow'; import ErrorContext from '../contexts/ErrorContext'; import MyTasks from './MyTasks'; -import TasksForMyOpenProcesses from './TasksForMyOpenProcesses'; +import GroupedTasks from './GroupedTasks'; export default function HomePageRoutes() { const location = useLocation(); @@ -16,7 +16,7 @@ export default function HomePageRoutes() { useEffect(() => { setErrorMessage(null); let newSelectedTabIndex = 0; - if (location.pathname.match(/^\/tasks\/for-my-open-processes/)) { + if (location.pathname.match(/^\/tasks\/grouped\b/)) { newSelectedTabIndex = 1; } setSelectedTabIndex(newSelectedTabIndex); @@ -27,9 +27,7 @@ export default function HomePageRoutes() { navigate('/tasks/my-tasks')}>My Tasks - navigate('/tasks/for-my-open-processes')}> - Tasks for My Open Processes - + navigate('/tasks/grouped')}>Grouped Tasks
@@ -37,10 +35,7 @@ export default function HomePageRoutes() { } /> } /> } /> - } - /> + } /> ); From 4dd121fac79d303e55cc18640c1922a9b63e2b4f Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 11 Nov 2022 17:15:38 -0500 Subject: [PATCH 05/20] add the username to the task list w/ burnettk --- .../routes/process_api_blueprint.py | 22 ++++++++++--------- .../MyTasksForProcessesStartedByOthers.tsx | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index ae704584..0c5ed42f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1012,32 +1012,34 @@ def get_tasks(processes_started_by_user: bool = True, page: int = 1, per_page: i user_id = g.user.id active_tasks_query = ( ActiveTaskModel.query + .outerjoin(GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id) .join(ProcessInstanceModel) - .order_by(desc(ProcessInstanceModel.created_at_in_seconds)) # type: ignore - ) + .join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id) + ) if processes_started_by_user: - active_tasks_query = active_tasks_query.filter_by(process_initiator_id=user_id) + active_tasks_query = (active_tasks_query.filter(ProcessInstanceModel.process_initiator_id==user_id) + .outerjoin(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id)) + ) else: - active_tasks_query = active_tasks_query.filter(ProcessInstanceModel.process_initiator_id != user_id) + active_tasks_query = (active_tasks_query.filter(ProcessInstanceModel.process_initiator_id != user_id) + .join(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_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. - .add_columns( + active_tasks_query.add_columns( ProcessInstanceModel.process_model_identifier, ProcessInstanceModel.status.label("process_instance_status"), ProcessInstanceModel.updated_at_in_seconds, ProcessInstanceModel.created_at_in_seconds, + UserModel.username, GroupModel.identifier.label("group_identifier"), ActiveTaskModel.task_name, ActiveTaskModel.task_title, ActiveTaskModel.process_model_display_name, 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) ) response_json = { diff --git a/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx b/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx index d8de933d..d1fafd39 100644 --- a/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx +++ b/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx @@ -63,6 +63,7 @@ export default function MyTasksForProcessesStartedByOthers() { > {rowToUse.task_title} + {rowToUse.username} {rowToUse.process_instance_status} {rowToUse.group_identifier || '-'} @@ -95,6 +96,7 @@ export default function MyTasksForProcessesStartedByOthers() { Process Model Process Instance Task Name + Process Started By Process Instance Status Assigned Group Process Started From f89b8bdda2896025351fb9774d7d9fe7aa046f07 Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 11 Nov 2022 22:12:35 -0500 Subject: [PATCH 06/20] fix a couple tests --- .../cypress/e2e/process_models.cy.js | 5 ++- .../src/components/ButtonWithConfirmation.tsx | 3 ++ .../src/routes/ProcessGroupShow.tsx | 41 +++++++++++-------- .../src/routes/ProcessInstanceShow.tsx | 1 + 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js index cc4ef606..705f6011 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js @@ -144,10 +144,11 @@ describe('process-models', () => { cy.getBySel('process-instance-list-link').click(); cy.getBySel('process-instance-show-link').click(); - cy.contains('Delete').click(); + cy.getBySel('process-instance-delete').click(); cy.contains('Are you sure'); cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click(); - cy.contains(`Process Instances for: ${groupId}/${modelId}`); + + // in breadcrumb cy.contains(modelId).click(); cy.contains('Edit process model').click(); diff --git a/spiffworkflow-frontend/src/components/ButtonWithConfirmation.tsx b/spiffworkflow-frontend/src/components/ButtonWithConfirmation.tsx index 88386919..a58dbe1c 100644 --- a/spiffworkflow-frontend/src/components/ButtonWithConfirmation.tsx +++ b/spiffworkflow-frontend/src/components/ButtonWithConfirmation.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { Button, Modal } from '@carbon/react'; type OwnProps = { + 'data-qa'?: string; description?: string; buttonLabel?: string; onConfirmation: (..._args: any[]) => any; @@ -18,6 +19,7 @@ export default function ButtonWithConfirmation({ description, buttonLabel, onConfirmation, + 'data-qa': dataQa, title = 'Are you sure?', confirmButtonLabel = 'OK', kind = 'danger', @@ -58,6 +60,7 @@ export default function ButtonWithConfirmation({ return ( <>