Merge pull request #31 from sartography/feature/home_page_redesign

Feature/home page redesign
This commit is contained in:
Kevin Burnett 2022-11-13 23:17:32 +00:00 committed by GitHub
commit 498c0b41ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 548 additions and 65 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/tasks" element={<HomePage />} />
<Route path="/" element={<HomePageRoutes />} />
<Route path="/tasks/*" element={<HomePageRoutes />} />
<Route path="/admin/*" element={<AdminRoutes />} />
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
</Routes>
</ErrorBoundary>
</Content>

View File

@ -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 (
<>
<Button
data-qa={dataQa}
onClick={handleShowConfirmationPrompt}
kind={kind}
renderIcon={renderIcon}

View File

@ -0,0 +1,139 @@
import { useEffect, useState } from 'react';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject } from '../interfaces';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
export default function MyTasksForProcessesStartedByOthers() {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState([]);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => {
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
HttpService.makeCallToBackend({
path: `/tasks/for-processes-started-by-others?per_page=${perPage}&page=${page}`,
successCallback: setTasksFromResult,
});
}, [searchParams]);
const buildTable = () => {
const rows = tasks.map((row) => {
const rowToUse = row as any;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
const modifiedProcessModelIdentifier = modifyProcessModelPath(
rowToUse.process_model_identifier
);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
>
View {rowToUse.process_instance_id}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
<td>{rowToUse.username}</td>
<td>{rowToUse.process_instance_status}</td>
<td>{rowToUse.group_identifier || '-'}</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.updated_at_in_seconds
) || '-'}
</td>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
);
});
return (
<Table striped bordered>
<thead>
<tr>
<th>Process Model</th>
<th>Process Instance</th>
<th>Task Name</th>
<th>Process Started By</th>
<th>Process Instance Status</th>
<th>Assigned Group</th>
<th>Process Started</th>
<th>Process Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
};
const tasksComponent = () => {
if (pagination && pagination.total < 1) {
return null;
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
);
return (
<>
<h1>Tasks waiting for me</h1>
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
path="/tasks/for-my-open-processes"
/>
</>
);
};
if (pagination) {
return tasksComponent();
}
return null;
}

View File

@ -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<PaginationObject | null>(null);
useEffect(() => {
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);
};
HttpService.makeCallToBackend({
path: `/tasks/for-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 (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
>
{rowToUse.process_model_display_name}
</Link>
</td>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
>
View {rowToUse.process_instance_id}
</Link>
</td>
<td
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
>
{rowToUse.task_title}
</td>
<td>{rowToUse.process_instance_status}</td>
<td>{rowToUse.group_identifier || '-'}</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.created_at_in_seconds
) || '-'}
</td>
<td>
{convertSecondsToFormattedDateTime(
rowToUse.updated_at_in_seconds
) || '-'}
</td>
<td>
<Button
variant="primary"
href={taskUrl}
hidden={rowToUse.process_instance_status === 'suspended'}
disabled={!rowToUse.current_user_is_potential_owner}
>
Go
</Button>
</td>
</tr>
);
});
return (
<Table striped bordered>
<thead>
<tr>
<th>Process Model</th>
<th>Process Instance</th>
<th>Task Name</th>
<th>Process Instance Status</th>
<th>Assigned Group</th>
<th>Process Started</th>
<th>Process Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
};
const tasksComponent = () => {
if (pagination && pagination.total < 1) {
return null;
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
);
return (
<>
<h1>Tasks for my open processes</h1>
<PaginationForTable
page={page}
perPage={perPage}
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
pagination={pagination}
tableToDisplay={buildTable()}
path="/tasks/for-my-open-processes"
/>
</>
);
};
if (pagination) {
return tasksComponent();
}
return null;
}

View File

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

View File

@ -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<number>(0);
const navigate = useNavigate();
useEffect(() => {
setErrorMessage(null);
let newSelectedTabIndex = 0;
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
newSelectedTabIndex = 1;
}
setSelectedTabIndex(newSelectedTabIndex);
}, [location, setErrorMessage]);
return (
<>
<Tabs selectedIndex={selectedTabIndex}>
<TabList aria-label="List of tabs">
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
</TabList>
</Tabs>
<br />
<Routes>
<Route path="/" element={<MyTasks />} />
<Route path="my-tasks" element={<MyTasks />} />
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
<Route path="grouped" element={<GroupedTasks />} />
</Routes>
</>
);
}

View File

@ -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<PaginationObject | null>(null);

View File

@ -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<ProcessGroup | null>(null);
const [processModels, setProcessModels] = useState([]);
const [processGroups, setProcessGroups] = useState([]);
const [modelPagination, setModelPagination] = useState(null);
const [groupPagination, setGroupPagination] = useState(null);
const [modelPagination, setModelPagination] =
useState<PaginationObject | null>(null);
const [groupPagination, setGroupPagination] =
useState<PaginationObject | null>(null);
useEffect(() => {
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
@ -138,6 +140,7 @@ export default function ProcessGroupShow() {
['', `process_group:${processGroup.id}`],
]}
/>
<h1>Process Group: {processGroup.display_name}</h1>
<ul>
<Stack orientation="horizontal" gap={3}>
<Button
@ -160,22 +163,26 @@ export default function ProcessGroupShow() {
</Stack>
<br />
<br />
<PaginationForTable
page={page}
perPage={perPage}
pagination={modelPagination}
tableToDisplay={buildModelTable()}
path={`/admin/process-groups/${processGroup.id}`}
/>
{modelPagination && modelPagination.total > 0 && (
<PaginationForTable
page={page}
perPage={perPage}
pagination={modelPagination}
tableToDisplay={buildModelTable()}
path={`/admin/process-groups/${processGroup.id}`}
/>
)}
<br />
<br />
<PaginationForTable
page={page}
perPage={perPage}
pagination={groupPagination}
tableToDisplay={buildGroupTable()}
path={`/admin/process-groups/${processGroup.id}`}
/>
{groupPagination && groupPagination.total > 0 && (
<PaginationForTable
page={page}
perPage={perPage}
pagination={groupPagination}
tableToDisplay={buildGroupTable()}
path={`/admin/process-groups/${processGroup.id}`}
/>
)}
</ul>
</>
);

View File

@ -521,6 +521,7 @@ export default function ProcessInstanceShow() {
elements.push(resumeButton(processInstanceToUse));
elements.push(
<ButtonWithConfirmation
data-qa="process-instance-delete"
kind="ghost"
renderIcon={TrashCan}
iconDescription="Delete"

View File

@ -439,10 +439,11 @@ export default function ProcessModelShow() {
return null;
}
return (
<Grid fullWidth>
<Grid condensed fullWidth>
<Column md={5} lg={9} sm={3}>
<Accordion align="end">
<Accordion align="end" open>
<AccordionItem
open
data-qa="files-accordion"
title={
<Stack orientation="horizontal">
@ -532,11 +533,11 @@ export default function ProcessModelShow() {
<br />
<br />
{processInstanceResultTag()}
{processModelButtons()}
<br />
<br />
<h3>Process Instances</h3>
{processInstancesUl()}
<br />
{processModelButtons()}
</>
);
}