Merge pull request #80 from sartography/feature/process-model-search

Feature/process model search
This commit is contained in:
Kevin Burnett 2022-10-05 20:34:50 +00:00 committed by GitHub
commit 965fa2871e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 181 additions and 43 deletions

View File

@ -15,15 +15,14 @@ describe('process-groups', () => {
cy.createGroup(groupId, groupDisplayName);
cy.contains('Process Groups').click();
cy.contains(groupId);
cy.contains(groupId).click();
cy.contains(groupDisplayName).click();
cy.url().should('include', `process-groups/${groupId}`);
cy.contains(`Process Group: ${groupId}`);
cy.contains(`Process Group: ${groupDisplayName}`);
cy.contains('Edit process group').click();
cy.get('input[name=display_name]').clear().type(newGroupDisplayName);
cy.contains('Submit').click();
cy.contains(`Process Group: ${groupId}`);
cy.contains(`Process Group: ${newGroupDisplayName}`);
cy.contains('Edit process group').click();
cy.get('input[name=display_name]').should(

View File

@ -58,7 +58,7 @@ describe('process-instances', () => {
beforeEach(() => {
cy.login();
cy.navigateToProcessModel(
'acceptance-tests-group-one',
'Acceptance Tests Group One',
'acceptance-tests-model-1'
);
});
@ -165,7 +165,8 @@ describe('process-instances', () => {
cy.getBySel(`process-instance-status-${processStatus}`).contains(
processStatus
);
cy.get('button[aria-label=Remove]').click();
// there should really only be one, but in CI there are sometimes more
cy.get('button[aria-label=Remove]:first').click();
}
});

View File

@ -10,10 +10,11 @@ describe('process-models', () => {
const uuid = () => Cypress._.random(0, 1e6);
const id = uuid();
const groupId = 'acceptance-tests-group-one';
const groupDisplayName = 'Acceptance Tests Group One';
const modelDisplayName = `Test Model 2 ${id}`;
const newModelDisplayName = `${modelDisplayName} edited`;
const modelId = `test-model-2-${id}`;
cy.contains(groupId).click();
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
cy.contains(`Process Group: ${groupId}`).click();
cy.contains(modelId);
@ -44,6 +45,7 @@ describe('process-models', () => {
const uuid = () => Cypress._.random(0, 1e6);
const id = uuid();
const groupId = 'acceptance-tests-group-one';
const groupDisplayName = 'Acceptance Tests Group One';
const modelDisplayName = `Test Model 2 ${id}`;
const modelId = `test-model-2-${id}`;
@ -51,7 +53,7 @@ describe('process-models', () => {
const dmnFileName = `dmn_test_file_${id}`;
const jsonFileName = `json_test_file_${id}`;
cy.contains(groupId).click();
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
cy.contains(`Process Group: ${groupId}`).click();
cy.contains(modelId);
@ -119,10 +121,11 @@ describe('process-models', () => {
const uuid = () => Cypress._.random(0, 1e6);
const id = uuid();
const groupId = 'acceptance-tests-group-one';
const groupDisplayName = 'Acceptance Tests Group One';
const modelDisplayName = `Test Model 2 ${id}`;
const modelId = `test-model-2-${id}`;
cy.contains('Add a process group');
cy.contains(groupId).click();
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
// seeing if getBySel works better, because we are seeing tests fail in CI
@ -160,7 +163,17 @@ describe('process-models', () => {
});
it('can paginate items', () => {
cy.contains('acceptance-tests-group-one').click();
cy.contains('Acceptance Tests Group One').click();
cy.basicPaginationTest();
});
it('can allow searching for model', () => {
cy.get('[name=process-model-selection]').click();
cy.get('[name=process-model-selection]').type('model-3');
cy.get(
`[aria-label="acceptance-tests-group-one/acceptance-tests-model-3"]`
).click();
cy.contains('Process Instances').click();
});
});

View File

@ -22,12 +22,12 @@ describe('tasks', () => {
});
it('can complete and navigate a form', () => {
const groupId = 'acceptance-tests-group-one';
const groupDisplayName = 'Acceptance Tests Group One';
const modelId = `acceptance-tests-model-2`;
const completedTaskClassName = 'completed-task-highlight';
const activeTaskClassName = 'active-task-highlight';
cy.navigateToProcessModel(groupId, modelId);
cy.navigateToProcessModel(groupDisplayName, modelId);
// avoid reloading so we can click on the task link that appears on running the process instance
cy.runPrimaryBpmnFile(false);
@ -66,7 +66,7 @@ describe('tasks', () => {
);
cy.contains('Task: get_user_generated_number_four');
cy.navigateToProcessModel(groupId, modelId);
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -98,7 +98,7 @@ describe('tasks', () => {
);
cy.url().should('include', '/tasks');
cy.navigateToProcessModel(groupId, modelId);
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -110,7 +110,7 @@ describe('tasks', () => {
it('can paginate items', () => {
cy.navigateToProcessModel(
'acceptance-tests-group-one',
'Acceptance Tests Group One',
'acceptance-tests-model-2'
);

View File

@ -47,6 +47,10 @@ Cypress.Commands.add('login', (selector, ...args) => {
Cypress.Commands.add('logout', (selector, ...args) => {
cy.getBySel('logout-button').click();
// otherwise we can click logout, quickly load the next page, and the javascript
// doesn't have time to actually sign you out
cy.contains('Sign in to your account');
});
Cypress.Commands.add('createGroup', (groupId, groupDisplayName) => {
@ -58,7 +62,7 @@ Cypress.Commands.add('createGroup', (groupId, groupDisplayName) => {
cy.contains('Submit').click();
cy.url().should('include', `process-groups/${groupId}`);
cy.contains(`Process Group: ${groupId}`);
cy.contains(`Process Group: ${groupDisplayName}`);
});
Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
@ -83,15 +87,18 @@ Cypress.Commands.add('runPrimaryBpmnFile', (reload = true) => {
}
});
Cypress.Commands.add('navigateToProcessModel', (groupId, modelId) => {
cy.navigateToAdmin();
cy.contains(groupId).click();
cy.contains(`Process Group: ${groupId}`);
// https://stackoverflow.com/q/51254946/6090676
cy.getBySel('process-model-show-link').contains(modelId).click();
cy.url().should('include', `process-models/${groupId}/${modelId}`);
cy.contains(`Process Model: ${modelId}`);
});
Cypress.Commands.add(
'navigateToProcessModel',
(groupDisplayName, modelDisplayName) => {
cy.navigateToAdmin();
cy.contains(groupDisplayName).click();
cy.contains(`Process Group: ${groupDisplayName}`);
// https://stackoverflow.com/q/51254946/6090676
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
// cy.url().should('include', `process-models/${groupDisplayName}/${modelDisplayName}`);
cy.contains(`Process Model: ${modelDisplayName}`);
}
);
Cypress.Commands.add('basicPaginationTest', () => {
cy.get('#pagination-page-dropdown')

View File

@ -12,6 +12,20 @@ test('renders home link', () => {
expect(homeElement).toBeInTheDocument();
});
test('renders hotCrumbs', () => {
render(
<BrowserRouter>
<ProcessBreadcrumb
hotCrumbs={[['Process Groups', '/admin'], [`Process Group: hey`]]}
/>
</BrowserRouter>
);
const homeElement = screen.getByText(/Process Groups/);
expect(homeElement).toBeInTheDocument();
const nextElement = screen.getByText(/Process Group: hey/);
expect(nextElement).toBeInTheDocument();
});
test('renders process group when given processGroupId', async () => {
render(
<BrowserRouter>

View File

@ -1,20 +1,44 @@
import { Link } from 'react-router-dom';
import Breadcrumb from 'react-bootstrap/Breadcrumb';
import { BreadcrumbItem } from '../interfaces';
type OwnProps = {
processModelId?: string;
processGroupId?: string;
linkProcessModel?: boolean;
hotCrumbs?: BreadcrumbItem[];
};
export default function ProcessBreadcrumb({
processModelId,
processGroupId,
hotCrumbs,
linkProcessModel = false,
}: OwnProps) {
let processGroupBreadcrumb = null;
let processModelBreadcrumb = null;
if (hotCrumbs) {
const lastItem = hotCrumbs.pop();
if (lastItem === undefined) {
return null;
}
const lastCrumb = <Breadcrumb.Item active>{lastItem[0]}</Breadcrumb.Item>;
const leadingCrumbLinks = hotCrumbs.map((crumb) => {
const valueLabel = crumb[0];
const url = crumb[1];
return (
<Breadcrumb.Item key={valueLabel} linkAs={Link} linkProps={{ to: url }}>
{valueLabel}
</Breadcrumb.Item>
);
});
return (
<Breadcrumb>
{leadingCrumbLinks}
{lastCrumb}
</Breadcrumb>
);
}
if (processModelId) {
if (linkProcessModel) {
processModelBreadcrumb = (

View File

@ -9,3 +9,18 @@ export interface RecentProcessModel {
processModelIdentifier: string;
processModelDisplayName: string;
}
export interface ProcessGroup {
id: string;
display_name: string;
}
export interface ProcessModel {
id: string;
process_group_id: string;
display_name: string;
primary_file_name: string;
}
// tuple of display value and URL
export type BreadcrumbItem = [displayValue: string, url?: string];

View File

@ -18,7 +18,6 @@ export default function HomePage() {
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
);
console.log('perPageYo', perPage);
const setTasksFromResult = (result: any) => {
setTasks(result.results);
setPagination(result.pagination);

View File

@ -1,29 +1,50 @@
import { useEffect, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { Button, Table } from 'react-bootstrap';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Button, Form, InputGroup, Table } from 'react-bootstrap';
import { Typeahead } from 'react-bootstrap-typeahead';
import { Option } from 'react-bootstrap-typeahead/types/types';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
import { getPageInfoFromSearchParams } from '../helpers';
import { ProcessModel } from '../interfaces';
// Example process group json
// {'admin': False, 'display_name': 'Test Workflows', 'display_order': 0, 'id': 'test_process_group'}
// {'process_group_id': 'sure', 'display_name': 'Test Workflows', 'id': 'test_process_group'}
export default function ProcessGroupList() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [processGroups, setProcessGroups] = useState([]);
const [pagination, setPagination] = useState(null);
const [processModeleSelectionOptions, setProcessModelSelectionOptions] =
useState([]);
useEffect(() => {
const setProcessGroupsFromResult = (result: any) => {
setProcessGroups(result.results);
setPagination(result.pagination);
};
const processResultForProcessModels = (result: any) => {
const selectionArray = result.results.map((item: any) => {
const label = `${item.process_group_id}/${item.id}`;
Object.assign(item, { label });
return item;
});
setProcessModelSelectionOptions(selectionArray);
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
// for browsing
HttpService.makeCallToBackend({
path: `/process-groups?per_page=${perPage}&page=${page}`,
successCallback: setProcessGroupsFromResult,
});
// for search box
HttpService.makeCallToBackend({
path: `/process-models?per_page=1000`,
successCallback: processResultForProcessModels,
});
}, [searchParams]);
const buildTable = () => {
@ -58,13 +79,16 @@ export default function ProcessGroupList() {
let displayText = null;
if (processGroups?.length > 0) {
displayText = (
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination as any}
tableToDisplay={buildTable()}
path="/admin/process-groups"
/>
<>
<h3>Browse</h3>
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination as any}
tableToDisplay={buildTable()}
path="/admin/process-groups"
/>
</>
);
} else {
displayText = <p>No Groups To Display</p>;
@ -72,13 +96,48 @@ export default function ProcessGroupList() {
return displayText;
};
const processModelSearchArea = () => {
const processModelSearchOnChange = (selected: Option[]) => {
const processModel = selected[0] as ProcessModel;
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}`
);
};
return (
<form onSubmit={function hey() {}}>
<h3>Search</h3>
<Form.Group>
<InputGroup>
<InputGroup.Text className="text-nowrap">
Process Model:{' '}
</InputGroup.Text>
<Typeahead
style={{ width: 500 }}
id="process-model-selection"
labelKey="label"
onChange={processModelSearchOnChange}
// for cypress tests since data-qa does not work
inputProps={{
name: 'process-model-selection',
}}
options={processModeleSelectionOptions}
placeholder="Choose a process model..."
/>
</InputGroup>
</Form.Group>
</form>
);
};
if (pagination) {
return (
<>
<ProcessBreadcrumb />
<ProcessBreadcrumb hotCrumbs={[['Process Groups']]} />
<Button href="/admin/process-groups/new">Add a process group</Button>
<br />
<br />
{processModelSearchArea()}
<br />
{processGroupsDisplayArea()}
</>
);

View File

@ -5,12 +5,13 @@ import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
import { getPageInfoFromSearchParams } from '../helpers';
import { ProcessGroup } from '../interfaces';
export default function ProcessGroupShow() {
const params = useParams();
const [searchParams] = useSearchParams();
const [processGroup, setProcessGroup] = useState(null);
const [processGroup, setProcessGroup] = useState<ProcessGroup | null>(null);
const [processModels, setProcessModels] = useState([]);
const [pagination, setPagination] = useState(null);
@ -35,14 +36,15 @@ export default function ProcessGroupShow() {
}, [params, searchParams]);
const buildTable = () => {
if (processGroup === null) {
return null;
}
const rows = processModels.map((row) => {
return (
<tr key={(row as any).id}>
<td>
<Link
to={`/admin/process-models/${(processGroup as any).id}/${
(row as any).id
}`}
to={`/admin/process-models/${processGroup.id}/${(row as any).id}`}
data-qa="process-model-show-link"
>
{(row as any).id}
@ -72,7 +74,12 @@ export default function ProcessGroupShow() {
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
return (
<>
<ProcessBreadcrumb processGroupId={(processGroup as any).id} />
<ProcessBreadcrumb
hotCrumbs={[
['Process Groups', '/admin'],
[`Process Group: ${processGroup.display_name}`],
]}
/>
<ul>
<Stack direction="horizontal" gap={3}>
<Button