diff --git a/.github/workflows/backend_tests.yml b/.github/workflows/backend_tests.yml index 88289c61..ef70a5c3 100644 --- a/.github/workflows/backend_tests.yml +++ b/.github/workflows/backend_tests.yml @@ -159,11 +159,12 @@ jobs: - name: Upload coverage data # pin to upload coverage from only one matrix entry, otherwise coverage gets confused later - if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest' + if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest' && matrix.database == 'mysql' uses: "actions/upload-artifact@v3.0.0" + # this action doesn't seem to respect working-directory so include working-directory value in path with: name: coverage-data - path: ".coverage.*" + path: "spiffworkflow-backend/.coverage.*" - name: Upload documentation if: matrix.session == 'docs-build' @@ -221,7 +222,7 @@ jobs: coverage: runs-on: ubuntu-latest - needs: tests + needs: [tests, run_pre_commit_checks, check_docker_start_script] steps: - name: Check out the repository uses: actions/checkout@v3.0.2 @@ -254,6 +255,8 @@ jobs: uses: actions/download-artifact@v3.0.1 with: name: coverage-data + # this action doesn't seem to respect working-directory so include working-directory value in path + path: spiffworkflow-backend - name: Combine coverage data and display human readable report run: | @@ -294,8 +297,8 @@ jobs: path: pr/ build-and-push-image: - if: github.ref_name == 'main' && ${{ github.event_name == 'push' }} - needs: tests + needs: coverage + if: ${{ github.ref_name == 'main' && github.event_name == 'push' }} env: REGISTRY: ghcr.io IMAGE_NAME: sartography/spiffworkflow-backend @@ -312,7 +315,7 @@ jobs: fetch-depth: 0 - name: Get current date id: date - run: echo "::set-output name=date::$(date +%s)" + run: echo "date=$(date +%s)" >> $GITHUB_OUTPUT - name: Log in to the Container registry uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 with: diff --git a/README.md b/README.md index 004c7f85..728a5fed 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # spiff-arena +This is a monorepo based on git subtrees that pulls together various +spiffworkflow-related projects. Here's an example command to push back to one +project: -git subtree push --prefix=spiffworkflow-frontend git@github.com:sartography/spiffworkflow-frontend.git add_md_file + git subtree push --prefix=spiffworkflow-frontend git@github.com:sartography/spiffworkflow-frontend.git add_md_file + +# run all lint checks and tests -# run pyl `./bin/run_pyl` Requires at root: diff --git a/spiffworkflow-backend/.github/workflows/tests.yml b/spiffworkflow-backend/.github/workflows/tests.yml index 31bb2c7c..f1db9198 100644 --- a/spiffworkflow-backend/.github/workflows/tests.yml +++ b/spiffworkflow-backend/.github/workflows/tests.yml @@ -156,7 +156,7 @@ jobs: - name: Upload coverage data # pin to upload coverage from only one matrix entry, otherwise coverage gets confused later - if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest' + if: always() && matrix.session == 'tests' && matrix.python == '3.11' && matrix.os == 'ubuntu-latest' && matrix.database == 'mysql' uses: "actions/upload-artifact@v3.0.0" with: name: coverage-data diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index 6ca51aef..d17beac3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -39,11 +39,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 14a0e52f..a87d5a2f 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -872,6 +872,64 @@ 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" + + /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 cfa89252..2e94e994 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -100,17 +100,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 17b64d4c..0042adcf 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -28,6 +28,7 @@ from lxml import etree # type: ignore from lxml.builder import ElementMaker # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.task import TaskState +from sqlalchemy import and_ from sqlalchemy import asc from sqlalchemy import desc @@ -37,6 +38,7 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import ( from spiffworkflow_backend.models.active_task import ActiveTaskModel from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel from spiffworkflow_backend.models.file import FileSchema +from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.message_model import MessageModel from spiffworkflow_backend.models.message_triggerable_process_model import ( @@ -1000,6 +1002,67 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res return make_response(jsonify(response_json), 200) +def task_list_for_my_open_processes( + page: int = 1, per_page: int = 100 +) -> flask.wrappers.Response: + """Task_list_for_my_open_processes.""" + 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: + """Task_list_for_processes_started_by_others.""" + 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: + """Get_tasks.""" + user_id = g.user.id + active_tasks_query = ( + ActiveTaskModel.query.outerjoin( + GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id + ) + .join(ProcessInstanceModel) + .join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id) + ) + + if processes_started_by_user: + 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 + ).join(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id)) + + active_tasks = active_tasks_query.add_columns( + ProcessInstanceModel.process_model_identifier, + ProcessInstanceModel.status.label("process_instance_status"), # type: ignore + 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) + + 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: @@ -1354,9 +1417,18 @@ def find_process_instance_by_id_or_raise( 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-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index a4533684..7623537d 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -1441,7 +1441,7 @@ class TestProcessApi(BaseTest): updated_at_in_seconds=round(time.time()), start_in_seconds=(1000 * i) + 1000, end_in_seconds=(1000 * i) + 2000, - bpmn_json=json.dumps({"i": i}), + bpmn_version_control_identifier=i, ) db.session.add(process_instance) db.session.commit() @@ -1487,7 +1487,12 @@ class TestProcessApi(BaseTest): results = response.json["results"] assert len(results) == 4 for i in range(4): - assert json.loads(results[i]["bpmn_json"])["i"] in (1, 2, 3, 4) + assert json.loads(results[i]["bpmn_version_control_identifier"]) in ( + 1, + 2, + 3, + 4, + ) # start > 2000, end < 5000 - this should eliminate the first 2 and the last response = client.get( @@ -1497,8 +1502,8 @@ class TestProcessApi(BaseTest): assert response.json is not None results = response.json["results"] assert len(results) == 2 - assert json.loads(results[0]["bpmn_json"])["i"] in (2, 3) - assert json.loads(results[1]["bpmn_json"])["i"] in (2, 3) + assert json.loads(results[0]["bpmn_version_control_identifier"]) in (2, 3) + assert json.loads(results[1]["bpmn_version_control_identifier"]) in (2, 3) # start > 1000, start < 4000 - this should eliminate the first and the last 2 response = client.get( @@ -1508,8 +1513,8 @@ class TestProcessApi(BaseTest): assert response.json is not None results = response.json["results"] assert len(results) == 2 - assert json.loads(results[0]["bpmn_json"])["i"] in (1, 2) - assert json.loads(results[1]["bpmn_json"])["i"] in (1, 2) + assert json.loads(results[0]["bpmn_version_control_identifier"]) in (1, 2) + assert json.loads(results[1]["bpmn_version_control_identifier"]) in (1, 2) # end > 2000, end < 6000 - this should eliminate the first and the last response = client.get( @@ -1520,7 +1525,11 @@ class TestProcessApi(BaseTest): results = response.json["results"] assert len(results) == 3 for i in range(3): - assert json.loads(results[i]["bpmn_json"])["i"] in (1, 2, 3) + assert json.loads(results[i]["bpmn_version_control_identifier"]) in ( + 1, + 2, + 3, + ) def test_process_instance_report_list( self, diff --git a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js index 6999acc2..09cad298 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js @@ -169,14 +169,13 @@ describe('process-instances', () => { cy.getBySel('process-instance-list-link').click(); cy.assertAtLeastOneItemInPaginatedResults(); + const statusSelect = '#process-instance-status-select'; PROCESS_STATUSES.forEach((processStatus) => { if (!['all', 'waiting'].includes(processStatus)) { - cy.get('#process-instance-status-select').click(); - cy.get('#process-instance-status-select') - .contains(processStatus) - .click(); + cy.get(statusSelect).click(); + cy.get(statusSelect).contains(processStatus).click(); // close the dropdown again - cy.get('#process-instance-status-select').click(); + cy.get(statusSelect).click(); cy.getBySel('filter-button').click(); cy.assertAtLeastOneItemInPaginatedResults(); cy.getBySel(`process-instance-status-${processStatus}`).contains( 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/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/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/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 ( <> + + + ); + }); + return ( + + + + + + + + + + + + + + + {rows} +
Process ModelProcess InstanceTask NameProcess Started ByProcess 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/components/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx new file mode 100644 index 00000000..8a2f0bdd --- /dev/null +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.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 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.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 for my open processes

+ + + ); + }; + + if (pagination) { + 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 new file mode 100644 index 00000000..d02cb9d7 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/HomePageRoutes.tsx @@ -0,0 +1,42 @@ +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'; +import GroupedTasks from './GroupedTasks'; + +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); + let newSelectedTabIndex = 0; + if (location.pathname.match(/^\/tasks\/grouped\b/)) { + newSelectedTabIndex = 1; + } + setSelectedTabIndex(newSelectedTabIndex); + }, [location, setErrorMessage]); + + return ( + <> + + + navigate('/tasks/my-tasks')}>My Tasks + navigate('/tasks/grouped')}>Grouped Tasks + + +
+ + } /> + } /> + } /> + } /> + + + ); +} 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); diff --git a/spiffworkflow-frontend/src/routes/ProcessGroupShow.tsx b/spiffworkflow-frontend/src/routes/ProcessGroupShow.tsx index 64e35367..cb2a417b 100644 --- a/spiffworkflow-frontend/src/routes/ProcessGroupShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessGroupShow.tsx @@ -10,7 +10,7 @@ import { modifyProcessModelPath, unModifyProcessModelPath, } from '../helpers'; -import { ProcessGroup, ProcessModel } from '../interfaces'; +import { PaginationObject, ProcessGroup, ProcessModel } from '../interfaces'; export default function ProcessGroupShow() { const params = useParams(); @@ -19,8 +19,10 @@ export default function ProcessGroupShow() { const [processGroup, setProcessGroup] = useState(null); const [processModels, setProcessModels] = useState([]); const [processGroups, setProcessGroups] = useState([]); - const [modelPagination, setModelPagination] = useState(null); - const [groupPagination, setGroupPagination] = useState(null); + const [modelPagination, setModelPagination] = + useState(null); + const [groupPagination, setGroupPagination] = + useState(null); useEffect(() => { const { page, perPage } = getPageInfoFromSearchParams(searchParams); @@ -138,6 +140,7 @@ export default function ProcessGroupShow() { ['', `process_group:${processGroup.id}`], ]} /> +

Process Group: {processGroup.display_name}

); diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index df6944bb..263869ea 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -521,6 +521,7 @@ export default function ProcessInstanceShow() { elements.push(resumeButton(processInstanceToUse)); elements.push( + - + @@ -532,11 +533,11 @@ export default function ProcessModelShow() {

{processInstanceResultTag()} - {processModelButtons()} -

Process Instances

{processInstancesUl()} +
+ {processModelButtons()} ); }