From 77aa32646a3decf27e413460db13cd31b1f83d0f Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 12 Dec 2022 12:21:37 -0500 Subject: [PATCH 1/6] split group task tables by group and created component for group tables --- .../src/spiffworkflow_backend/api.yml | 22 ++ .../config/permissions/development.yml | 1 + .../routes/process_api_blueprint.py | 25 ++- .../components/TasksForMyOpenProcesses.tsx | 159 +------------- .../src/components/TasksTable.tsx | 200 ++++++++++++++++++ .../src/components/TasksWaitingForMe.tsx | 154 +------------- .../components/TasksWaitingForMyGroups.tsx | 169 +++------------ spiffworkflow-frontend/src/helpers.tsx | 12 +- spiffworkflow-frontend/src/interfaces.ts | 10 + 9 files changed, 304 insertions(+), 448 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/TasksTable.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 764ba543f..2836dac22 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -1082,6 +1082,12 @@ paths: /tasks/for-my-groups: parameters: + - name: group_identifier + in: query + required: false + description: The identifier of the group to get the tasks for + schema: + type: string - name: page in: query required: false @@ -1109,6 +1115,22 @@ paths: items: $ref: "#/components/schemas/Task" + /tasks/user-groups: + get: + tags: + - Process Instances + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_user_groups + 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}: parameters: - name: modified_process_model_identifier diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 419c925fa..1f38e02bb 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -50,6 +50,7 @@ groups: fin, fin1, harmeet, + jason, sasha, manuchehr, lead, 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 c29cf2148..2f194af63 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1301,7 +1301,6 @@ def task_list_for_my_open_processes( def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response: - """Task_list_for_processes_started_by_others.""" return get_tasks( processes_started_by_user=False, has_lane_assignment_id=False, @@ -1311,10 +1310,18 @@ def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Respo def task_list_for_my_groups( + group_identifier: str = None, page: int = 1, per_page: int = 100 ) -> flask.wrappers.Response: - """Task_list_for_processes_started_by_others.""" - 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 task_list_user_groups( +) -> flask.wrappers.Response: + 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( @@ -1322,6 +1329,7 @@ def get_tasks( has_lane_assignment_id: bool = True, page: int = 1, per_page: int = 100, + group_identifier: str = None, ) -> flask.wrappers.Response: """Get_tasks.""" user_id = g.user.id @@ -1358,9 +1366,14 @@ def get_tasks( ), ) if has_lane_assignment_id: - active_tasks_query = active_tasks_query.filter( - ActiveTaskModel.lane_assignment_id.is_not(None) # type: ignore - ) + if group_identifier: + 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: active_tasks_query = active_tasks_query.filter(ActiveTaskModel.lane_assignment_id.is_(None)) # type: ignore diff --git a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx index deb2030ea..7ee7edece 100644 --- a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx @@ -1,156 +1,17 @@ -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 } from '../interfaces'; -import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords'; +import TasksTable from './TasksTable'; -const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; const paginationQueryParamPrefix = 'tasks_for_my_open_processes'; -const REFRESH_INTERVAL = 5; -const REFRESH_TIMEOUT = 600; export default function MyOpenProcesses() { - const [searchParams] = useSearchParams(); - const [tasks, setTasks] = useState([]); - const [pagination, setPagination] = useState(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(); - 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 ( - - - - {rowToUse.process_instance_id} - - - - - {rowToUse.process_model_display_name} - - - - {rowToUse.task_title} - - {rowToUse.group_identifier || '-'} - - {convertSecondsToFormattedDateTime( - rowToUse.created_at_in_seconds - ) || '-'} - - - - - - - ); - }); - return ( - - - - - - - - - - - - - {rows} -
IdProcessTaskWaiting ForDate StartedLast UpdatedActions
- ); - }; - - const tasksComponent = () => { - if (pagination && pagination.total < 1) { - return ( -

- There are no tasks for processes you started at this time. -

- ); - } - const { page, perPage } = getPageInfoFromSearchParams( - searchParams, - PER_PAGE_FOR_TASKS_ON_HOME_PAGE, - undefined, - paginationQueryParamPrefix - ); - return ( - - ); - }; - return ( - <> -

My open instances

-

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

- {tasksComponent()} - + ); } diff --git a/spiffworkflow-frontend/src/components/TasksTable.tsx b/spiffworkflow-frontend/src/components/TasksTable.tsx new file mode 100644 index 000000000..1c4d15e91 --- /dev/null +++ b/spiffworkflow-frontend/src/components/TasksTable.tsx @@ -0,0 +1,200 @@ +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; +}; + +export default function TasksTable({ + apiPath, + tableTitle, + tableDescription, + additionalParams, + paginationQueryParamPrefix, + paginationClassName, + autoReload = false, + showStartedBy = true, + showWaitingOn = true, +}: OwnProps) { + const [searchParams] = useSearchParams(); + const [tasks, setTasks] = useState(null); + const [pagination, setPagination] = useState(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 = `${params}&${additionalParams}`; + } + HttpService.makeCallToBackend({ + path: `${apiPath}${params}`, + successCallback: setTasksFromResult, + }); + }; + getTasks(); + if (autoReload) { + refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); + } + }, [ + 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 ( + + + + {rowToUse.process_instance_id} + + + + + {rowToUse.process_model_display_name} + + + + {rowToUse.task_title} + + {showStartedBy ? {rowToUse.username} : ''} + {showWaitingOn ? {rowToUse.group_identifier || '-'} : ''} + + {convertSecondsToFormattedDateTime( + rowToUse.created_at_in_seconds + ) || '-'} + + + + + + + ); + }); + 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 ( + + + + {tableHeaders.map((tableHeader: string) => { + return ; + })} + + + {rows} +
{tableHeader}
+ ); + }; + + const tasksComponent = () => { + if (pagination && pagination.total < 1) { + return ( +

+ Your groups have no task assignments at this time. +

+ ); + } + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + PER_PAGE_FOR_TASKS_ON_HOME_PAGE, + undefined, + paginationQueryParamPrefix + ); + return ( + + ); + }; + + if (tasks) { + return ( + <> +

{tableTitle}

+

{tableDescription}

+ {tasksComponent()} + + ); + } + return null; +} diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx index 7d06b7a30..e253afd61 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx @@ -1,149 +1,15 @@ -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, -} from '../helpers'; -import HttpService from '../services/HttpService'; -import { PaginationObject } from '../interfaces'; -import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords'; - -const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; +import TasksTable from './TasksTable'; export default function TasksWaitingForMe() { - 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, - 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 ( - - - - {rowToUse.process_instance_id} - - - - - {rowToUse.process_model_display_name} - - - - {rowToUse.task_title} - - {rowToUse.username} - {rowToUse.group_identifier || '-'} - - {convertSecondsToFormattedDateTime( - rowToUse.created_at_in_seconds - ) || '-'} - - - - - - - ); - }); - return ( - - - - - - - - - - - - - - {rows} -
IdProcessTaskStarted ByWaiting ForDate StartedLast UpdatedActions
- ); - }; - - const tasksComponent = () => { - if (pagination && pagination.total < 1) { - return ( -

- You have no task assignments at this time. -

- ); - } - const { page, perPage } = getPageInfoFromSearchParams( - searchParams, - PER_PAGE_FOR_TASKS_ON_HOME_PAGE, - undefined, - 'tasks_waiting_for_me' - ); - return ( - - ); - }; - return ( - <> -

Tasks waiting for me

-

- These processes are waiting on you to complete the next task. All are - processes created by others that are now actionable by you. -

- {tasksComponent()} - + ); } diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx index 565cd4a55..93d216408 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx @@ -1,156 +1,39 @@ 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 } from '../interfaces'; -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; +import TasksTable from './TasksTable'; export default function TasksWaitingForMyGroups() { - const [searchParams] = useSearchParams(); - const [tasks, setTasks] = useState([]); - const [pagination, setPagination] = useState(null); + const [userGroups, setUserGroups] = useState(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-groups?per_page=${perPage}&page=${page}`, - successCallback: setTasksFromResult, - }); - }; - getTasks(); - refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); - }, [searchParams]); + HttpService.makeCallToBackend({ + path: `/tasks/user-groups`, + successCallback: setUserGroups, + }); + }, [setUserGroups]); + const tableComponents = () => { + if (!userGroups) { + return null; + } - 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 userGroups.map((userGroup: string) => { return ( - - - - {rowToUse.process_instance_id} - - - - - {rowToUse.process_model_display_name} - - - - {rowToUse.task_title} - - {rowToUse.username} - {rowToUse.group_identifier || '-'} - - {convertSecondsToFormattedDateTime( - rowToUse.created_at_in_seconds - ) || '-'} - - - - - - + ); }); - return ( - - - - - - - - - - - - - - {rows} -
IdProcessTaskStarted ByWaiting ForDate StartedLast UpdatedActions
- ); }; - const tasksComponent = () => { - if (pagination && pagination.total < 1) { - return ( -

- Your groups have no task assignments at this time. -

- ); - } - const { page, perPage } = getPageInfoFromSearchParams( - searchParams, - PER_PAGE_FOR_TASKS_ON_HOME_PAGE, - undefined, - paginationQueryParamPrefix - ); - return ( - - ); - }; - - return ( - <> -

Tasks waiting for my groups

-

- This is a list of tasks for groups you belong to that can be completed - by any member of the group. -

- {tasksComponent()} - - ); + if (userGroups) { + return <>{tableComponents()}; + } + return null; } diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx index 6781ada97..fcc3371b7 100644 --- a/spiffworkflow-frontend/src/helpers.tsx +++ b/spiffworkflow-frontend/src/helpers.tsx @@ -203,10 +203,10 @@ export const refreshAtInterval = ( timeout: number, func: Function ) => { - const intervalRef = setInterval(() => func(), interval * 1000); - const timeoutRef = setTimeout( - () => clearInterval(intervalRef), - timeout * 1000 - ); - return [intervalRef, timeoutRef]; + // const intervalRef = setInterval(() => func(), interval * 1000); + // const timeoutRef = setTimeout( + // () => clearInterval(intervalRef), + // timeout * 1000 + // ); + // return [intervalRef, timeoutRef]; }; diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 079e4cdc7..cc3180e55 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -11,6 +11,16 @@ export interface RecentProcessModel { processModelDisplayName: string; } +export interface ProcessInstanceTask { + id: number; + 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; +} + export interface ProcessReference { name: string; // The process or decision Display name. identifier: string; // The unique id of the process From 978849d3793456715410bee9e116073a32de816a Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 12 Dec 2022 12:29:36 -0500 Subject: [PATCH 2/6] pyl --- .../routes/process_api_blueprint.py | 20 ++++++++++++------- .../unit/test_git_service.py | 8 ++++++-- spiffworkflow-frontend/src/helpers.tsx | 12 +++++------ 3 files changed, 25 insertions(+), 15 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 2f194af63..06c23cf59 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1301,6 +1301,7 @@ def task_list_for_my_open_processes( def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response: + """Task_list_for_me.""" return get_tasks( processes_started_by_user=False, has_lane_assignment_id=False, @@ -1310,17 +1311,22 @@ def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Respo def task_list_for_my_groups( - group_identifier: str = None, - page: int = 1, per_page: int = 100 + group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100 ) -> flask.wrappers.Response: - return get_tasks(group_identifier=group_identifier, processes_started_by_user=False, page=page, per_page=per_page) + """Task_list_for_my_groups.""" + return get_tasks( + group_identifier=group_identifier, + processes_started_by_user=False, + page=page, + per_page=per_page, + ) -def task_list_user_groups( -) -> flask.wrappers.Response: +def task_list_user_groups() -> flask.wrappers.Response: + """Task_list_user_groups.""" 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'] + group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"] return make_response(jsonify(sorted(group_identifiers)), 200) @@ -1329,7 +1335,7 @@ def get_tasks( has_lane_assignment_id: bool = True, page: int = 1, per_page: int = 100, - group_identifier: str = None, + group_identifier: Optional[str] = None, ) -> flask.wrappers.Response: """Get_tasks.""" user_id = g.user.id diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_git_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_git_service.py index ad3c814f4..ed1e24e1e 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_git_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_git_service.py @@ -7,6 +7,7 @@ from spiffworkflow_backend.services.git_service import GitService class TestGitService(BaseTest): + """TestGitService.""" def test_strips_output_of_stdout_from_command( self, @@ -14,5 +15,8 @@ class TestGitService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: - output = GitService.run_shell_command_to_get_stdout(["echo", ' This output should not end in space or newline \n']) - assert output == 'This output should not end in space or newline' + """Test_strips_output_of_stdout_from_command.""" + output = GitService.run_shell_command_to_get_stdout( + ["echo", " This output should not end in space or newline \n"] + ) + assert output == "This output should not end in space or newline" diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx index fcc3371b7..6781ada97 100644 --- a/spiffworkflow-frontend/src/helpers.tsx +++ b/spiffworkflow-frontend/src/helpers.tsx @@ -203,10 +203,10 @@ export const refreshAtInterval = ( timeout: number, func: Function ) => { - // const intervalRef = setInterval(() => func(), interval * 1000); - // const timeoutRef = setTimeout( - // () => clearInterval(intervalRef), - // timeout * 1000 - // ); - // return [intervalRef, timeoutRef]; + const intervalRef = setInterval(() => func(), interval * 1000); + const timeoutRef = setTimeout( + () => clearInterval(intervalRef), + timeout * 1000 + ); + return [intervalRef, timeoutRef]; }; From ec754cbee7ce3c2629b69fe4d2795f1e91eaca5f Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 12 Dec 2022 13:19:27 -0500 Subject: [PATCH 3/6] updated group api so it is not under tasks --- spiffworkflow-backend/src/spiffworkflow_backend/api.yml | 4 ++-- .../spiffworkflow_backend/config/permissions/development.yml | 5 +++++ .../spiffworkflow_backend/routes/process_api_blueprint.py | 4 ++-- .../src/components/TasksWaitingForMyGroups.tsx | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 2836dac22..d6066093a 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -1115,11 +1115,11 @@ paths: items: $ref: "#/components/schemas/Task" - /tasks/user-groups: + /user-groups/for-current-user: get: tags: - Process Instances - operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_user_groups + operationId: spiffworkflow_backend.routes.process_api_blueprint.user_groups_for_current_user summary: Group identifiers for current logged in user responses: "200": diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 1f38e02bb..737c1a708 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -81,6 +81,11 @@ permissions: users: [] allowed_permissions: [read] 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 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 06c23cf59..19f389340 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1322,8 +1322,8 @@ def task_list_for_my_groups( ) -def task_list_user_groups() -> flask.wrappers.Response: - """Task_list_user_groups.""" +def user_groups_for_current_user() -> flask.wrappers.Response: + """User_groups_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"] diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx index 93d216408..99616447f 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx @@ -7,7 +7,7 @@ export default function TasksWaitingForMyGroups() { useEffect(() => { HttpService.makeCallToBackend({ - path: `/tasks/user-groups`, + path: `/user-groups/for-current-user`, successCallback: setUserGroups, }); }, [setUserGroups]); From 95be02965638825d485f3610faaad3dbef44ef36 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 12 Dec 2022 13:41:42 -0500 Subject: [PATCH 4/6] split out completed instances by group as well --- .../src/spiffworkflow_backend/api.yml | 6 +++ .../routes/process_api_blueprint.py | 15 ++++-- .../components/ProcessInstanceListTable.tsx | 7 +++ .../src/components/TasksTable.tsx | 2 +- .../components/TasksWaitingForMyGroups.tsx | 1 + .../src/routes/CompletedInstances.tsx | 53 ++++++++++++++----- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index d6066093a..d14a514b6 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -595,6 +595,12 @@ paths: description: Specifies the identifier of a report to use, if any schema: 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: operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list summary: Returns a list of process instances for a given process model 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 19f389340..895f8e1ba 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -821,6 +821,7 @@ def process_instance_list( user_filter: Optional[bool] = False, report_identifier: Optional[str] = None, report_id: Optional[int] = None, + group_identifier: Optional[str] = None, ) -> flask.wrappers.Response: """Process_instance_list.""" process_instance_report = ProcessInstanceReportService.report_with_identifier( @@ -960,10 +961,16 @@ def process_instance_list( process_instance_query = process_instance_query.filter( SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step ) - process_instance_query = process_instance_query.join( - GroupModel, - GroupModel.id == SpiffStepDetailsModel.lane_assignment_id, - ) + if (group_identifier): + process_instance_query = process_instance_query.join( + 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( UserGroupAssignmentModel, UserGroupAssignmentModel.group_id == GroupModel.id, diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 98b76df39..5e62fcf0c 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -79,6 +79,7 @@ type OwnProps = { textToShowIfEmpty?: string; paginationClassName?: string; autoReload?: boolean; + additionalParams?: string; }; interface dateParameters { @@ -90,6 +91,7 @@ export default function ProcessInstanceListTable({ processModelFullIdentifier, paginationQueryParamPrefix, perPageOptions, + additionalParams, showReports = true, reportIdentifier, textToShowIfEmpty, @@ -253,6 +255,10 @@ export default function ProcessInstanceListTable({ } ); + if (additionalParams) { + queryParamString += `&${additionalParams}`; + } + HttpService.makeCallToBackend({ path: `/process-instances?${queryParamString}`, successCallback: setProcessInstancesFromResult, @@ -315,6 +321,7 @@ export default function ProcessInstanceListTable({ processModelFullIdentifier, perPageOptions, reportIdentifier, + additionalParams, ]); // This sets the filter data using the saved reports returned from the initial instance_list query. diff --git a/spiffworkflow-frontend/src/components/TasksTable.tsx b/spiffworkflow-frontend/src/components/TasksTable.tsx index 1c4d15e91..0c6570166 100644 --- a/spiffworkflow-frontend/src/components/TasksTable.tsx +++ b/spiffworkflow-frontend/src/components/TasksTable.tsx @@ -58,7 +58,7 @@ export default function TasksTable({ }; let params = `?per_page=${perPage}&page=${page}`; if (additionalParams) { - params = `${params}&${additionalParams}`; + params += `&${additionalParams}`; } HttpService.makeCallToBackend({ path: `${apiPath}${params}`, diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx index 99616447f..948aee6cc 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx @@ -11,6 +11,7 @@ export default function TasksWaitingForMyGroups() { successCallback: setUserGroups, }); }, [setUserGroups]); + const tableComponents = () => { if (!userGroups) { return null; diff --git a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx index f97bb5d59..4583d0278 100644 --- a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx +++ b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx @@ -1,6 +1,45 @@ +import { useEffect, useState } from 'react'; import ProcessInstanceListTable from '../components/ProcessInstanceListTable'; +import HttpService from '../services/HttpService'; export default function CompletedInstances() { + const [userGroups, setUserGroups] = useState(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 ( + <> +

Tasks completed by {userGroup} group

+

+ This is a list of instances with tasks that were completed by the{' '} + {userGroup} group. +

+ + + ); + }); + }; + return ( <>

My completed instances

@@ -30,19 +69,7 @@ export default function CompletedInstances() { textToShowIfEmpty="You have no completed tasks at this time." paginationClassName="with-large-bottom-margin" /> -

Tasks completed by my groups

-

- This is a list of instances with tasks that were completed by groups you - belong to. -

- + {groupTableComponents()} ); } From 7a099eb68d1b2736e21ef4406b547cd7ed75fbd0 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 16 Dec 2022 14:21:04 -0500 Subject: [PATCH 5/6] updated some text for task tables w/ burnettk --- spiffworkflow-backend/src/spiffworkflow_backend/api.yml | 2 +- .../routes/process_api_blueprint.py | 6 +++--- .../src/components/{TasksTable.tsx => TaskListTable.tsx} | 9 ++++++--- .../src/components/TasksForMyOpenProcesses.tsx | 5 +++-- .../src/components/TasksWaitingForMe.tsx | 5 +++-- .../src/components/TasksWaitingForMyGroups.tsx | 9 +++++---- 6 files changed, 21 insertions(+), 15 deletions(-) rename spiffworkflow-frontend/src/components/{TasksTable.tsx => TaskListTable.tsx} (96%) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 3d78cb43e..be9796aae 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -1166,7 +1166,7 @@ paths: get: tags: - Process Instances - operationId: spiffworkflow_backend.routes.process_api_blueprint.user_groups_for_current_user + operationId: spiffworkflow_backend.routes.process_api_blueprint.user_group_list_for_current_user summary: Group identifiers for current logged in user responses: "200": 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 7907ef970..294a65247 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -989,7 +989,7 @@ def process_instance_list( process_instance_query = process_instance_query.filter( SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step ) - if (group_identifier): + if group_identifier: process_instance_query = process_instance_query.join( GroupModel, GroupModel.identifier == group_identifier, @@ -1380,8 +1380,8 @@ def task_list_for_my_groups( ) -def user_groups_for_current_user() -> flask.wrappers.Response: - """User_groups_for_current_user.""" +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"] diff --git a/spiffworkflow-frontend/src/components/TasksTable.tsx b/spiffworkflow-frontend/src/components/TaskListTable.tsx similarity index 96% rename from spiffworkflow-frontend/src/components/TasksTable.tsx rename to spiffworkflow-frontend/src/components/TaskListTable.tsx index 0c6570166..d6688f22e 100644 --- a/spiffworkflow-frontend/src/components/TasksTable.tsx +++ b/spiffworkflow-frontend/src/components/TaskListTable.tsx @@ -27,15 +27,17 @@ type OwnProps = { autoReload?: boolean; showStartedBy?: boolean; showWaitingOn?: boolean; + textToShowIfEmpty?: string; }; -export default function TasksTable({ +export default function TaskListTable({ apiPath, tableTitle, tableDescription, additionalParams, paginationQueryParamPrefix, paginationClassName, + textToShowIfEmpty, autoReload = false, showStartedBy = true, showWaitingOn = true, @@ -67,8 +69,9 @@ export default function TasksTable({ }; getTasks(); if (autoReload) { - refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); + return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); } + return undefined; }, [ searchParams, additionalParams, @@ -164,7 +167,7 @@ export default function TasksTable({ if (pagination && pagination.total < 1) { return (

- Your groups have no task assignments at this time. + {textToShowIfEmpty}

); } diff --git a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx index 7ee7edece..6634e8e8b 100644 --- a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx @@ -1,15 +1,16 @@ -import TasksTable from './TasksTable'; +import TaskListTable from './TaskListTable'; const paginationQueryParamPrefix = 'tasks_for_my_open_processes'; export default function MyOpenProcesses() { return ( - diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx index e253afd61..1939e4ba3 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx @@ -1,13 +1,14 @@ -import TasksTable from './TasksTable'; +import TaskListTable from './TaskListTable'; export default function TasksWaitingForMe() { return ( - diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx index 948aee6cc..b7515c916 100644 --- a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import HttpService from '../services/HttpService'; -import TasksTable from './TasksTable'; +import TaskListTable from './TaskListTable'; export default function TasksWaitingForMyGroups() { const [userGroups, setUserGroups] = useState(null); @@ -19,13 +19,14 @@ export default function TasksWaitingForMyGroups() { return userGroups.map((userGroup: string) => { return ( - From d4925334046a4dbdfe3fecae81cd2b730032f206 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 16 Dec 2022 14:41:13 -0500 Subject: [PATCH 6/6] some more updates for text w/ burnettk --- .../src/components/TasksForMyOpenProcesses.tsx | 2 +- .../src/routes/CompletedInstances.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx index 6634e8e8b..be1d9042a 100644 --- a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx @@ -7,7 +7,7 @@ export default function MyOpenProcesses() { { return ( <> -

Tasks completed by {userGroup} group

+

With tasks completed by group: {userGroup}

This is a list of instances with tasks that were completed by the{' '} {userGroup} group.

@@ -56,7 +56,7 @@ export default function CompletedInstances() { paginationClassName="with-large-bottom-margin" autoReload /> -

Tasks completed by me

+

With tasks completed by me

This is a list of instances where you have completed tasks.

@@ -66,7 +66,7 @@ export default function CompletedInstances() { perPageOptions={[2, 5, 25]} reportIdentifier="system_report_instances_with_tasks_completed_by_me" 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" /> {groupTableComponents()}