Merge pull request #31 from sartography/feature/home_page_redesign
Feature/home page redesign
This commit is contained in:
commit
498c0b41ba
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers';
|
||||
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||
|
||||
export default function GroupedTasks() {
|
||||
return (
|
||||
<>
|
||||
<TasksForMyOpenProcesses />
|
||||
<br />
|
||||
<MyTasksForProcessesStartedByOthers />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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);
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue