added links to filtered tables on homepage and added advanced options modal to use them w/ burnettk

This commit is contained in:
jasquat 2023-05-01 12:25:22 -04:00
parent 96c60a90f3
commit 607abf97c2
4 changed files with 281 additions and 140 deletions

View File

@ -1,23 +1,19 @@
// TODO: import React, {
// add drop down for user_group_identifier and one for the 2 system reports: useCallback,
// with_tasks_completed_by_me useEffect,
// with_tasks_i_can_complete useMemo,
// add checkbox to show with_oldest_open_task useRef,
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; useState,
import { } from 'react';
Link, import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
// @ts-ignore import { Close, AddAlt, ArrowRight } from '@carbon/icons-react';
import { Close, AddAlt } from '@carbon/icons-react';
import { import {
Button, Button,
ButtonSet, ButtonSet,
DatePicker, DatePicker,
DatePickerInput, DatePickerInput,
Dropdown,
Table, Table,
Grid, Grid,
Column, Column,
@ -32,7 +28,7 @@ import {
ComboBox, ComboBox,
TextInput, TextInput,
FormLabel, FormLabel,
// @ts-ignore Checkbox,
} from '@carbon/react'; } from '@carbon/react';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { import {
@ -102,6 +98,7 @@ type OwnProps = {
canCompleteAllTasks?: boolean; canCompleteAllTasks?: boolean;
showActionsColumn?: boolean; showActionsColumn?: boolean;
showLinkToReport?: boolean; showLinkToReport?: boolean;
headerElement?: React.ReactElement;
}; };
interface dateParameters { interface dateParameters {
@ -123,13 +120,13 @@ export default function ProcessInstanceListTable({
canCompleteAllTasks = false, canCompleteAllTasks = false,
showActionsColumn = false, showActionsColumn = false,
showLinkToReport = false, showLinkToReport = false,
headerElement,
}: OwnProps) { }: OwnProps) {
let processInstanceApiSearchPath = '/process-instances/for-me'; let processInstanceApiSearchPath = '/process-instances/for-me';
if (variant === 'all') { if (variant === 'all') {
processInstanceApiSearchPath = '/process-instances'; processInstanceApiSearchPath = '/process-instances';
} }
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { addError, removeError } = useAPIError(); const { addError, removeError } = useAPIError();
@ -147,8 +144,6 @@ export default function ProcessInstanceListTable({
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>(); const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
const [pagination, setPagination] = useState<PaginationObject | null>(null); const [pagination, setPagination] = useState<PaginationObject | null>(null);
const oneHourInSeconds = 3600;
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
const [startFromDate, setStartFromDate] = useState<string>(''); const [startFromDate, setStartFromDate] = useState<string>('');
const [startToDate, setStartToDate] = useState<string>(''); const [startToDate, setStartToDate] = useState<string>('');
const [endFromDate, setEndFromDate] = useState<string>(''); const [endFromDate, setEndFromDate] = useState<string>('');
@ -157,12 +152,13 @@ export default function ProcessInstanceListTable({
const [startToTime, setStartToTime] = useState<string>(''); const [startToTime, setStartToTime] = useState<string>('');
const [endFromTime, setEndFromTime] = useState<string>(''); const [endFromTime, setEndFromTime] = useState<string>('');
const [endToTime, setEndToTime] = useState<string>(''); const [endToTime, setEndToTime] = useState<string>('');
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
const [startFromTimeInvalid, setStartFromTimeInvalid] = const [startFromTimeInvalid, setStartFromTimeInvalid] =
useState<boolean>(false); useState<boolean>(false);
const [startToTimeInvalid, setStartToTimeInvalid] = useState<boolean>(false); const [startToTimeInvalid, setStartToTimeInvalid] = useState<boolean>(false);
const [endFromTimeInvalid, setEndFromTimeInvalid] = useState<boolean>(false); const [endFromTimeInvalid, setEndFromTimeInvalid] = useState<boolean>(false);
const [endToTimeInvalid, setEndToTimeInvalid] = useState<boolean>(false); const [endToTimeInvalid, setEndToTimeInvalid] = useState<boolean>(false);
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
const [requiresRefilter, setRequiresRefilter] = useState<boolean>(false); const [requiresRefilter, setRequiresRefilter] = useState<boolean>(false);
const [lastColumnFilter, setLastColumnFilter] = useState<string>(''); const [lastColumnFilter, setLastColumnFilter] = useState<string>('');
@ -212,6 +208,18 @@ export default function ProcessInstanceListTable({
string | null string | null
>(null); >(null);
const [showAdvancedOptions, setShowAdvancedOptions] =
useState<boolean>(false);
const [withOldestOpenTask, setWithOldestOpenTask] = useState<boolean>(false);
const [systemReport, setSystemReport] = useState<string | null>(null);
const [selectedUserGroup, setSelectedUserGroup] = useState<string | null>(
null
);
const [userGroups, setUserGroups] = useState<string[]>([]);
const systemReportOptions: string[] = useMemo(() => {
return ['with_tasks_i_can_complete', 'with_tasks_completed_by_me'];
}, []);
const [reportHash, setReportHash] = useState<string | null>(null); const [reportHash, setReportHash] = useState<string | null>(null);
const [ const [
@ -274,19 +282,6 @@ export default function ProcessInstanceListTable({
250 250
); );
const parametersToGetFromSearchParams = useMemo(() => {
const figureOutProcessInitiator = (processInitiatorSearchText: string) => {
searchForProcessInitiator(processInitiatorSearchText);
};
return {
process_model_identifier: null,
process_status: null,
process_initiator_username: figureOutProcessInitiator,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const setProcessInstancesFromResult = useCallback((result: any) => { const setProcessInstancesFromResult = useCallback((result: any) => {
setRequiresRefilter(false); setRequiresRefilter(false);
const processInstancesFromApi = result.results; const processInstancesFromApi = result.results;
@ -319,19 +314,15 @@ export default function ProcessInstanceListTable({
} }
}, []); }, []);
// eslint-disable-next-line sonarjs/cognitive-complexity // we apparently cannot use a state set in a useEffect from within that same useEffect
useEffect(() => { // so use a variable instead
if (!permissionsLoaded) { const processModelSelectionItemsForUseEffect = useRef<ProcessModel[]>([]);
return undefined;
}
// we apparently cannot use a state set in a useEffect from within that same useEffect const getProcessInstances = useCallback(
// so use a variable instead (
let processModelSelectionItemsForUseEffect: ProcessModel[] = [];
function getProcessInstances(
processInstanceReport: ProcessInstanceReport | null = null processInstanceReport: ProcessInstanceReport | null = null
) { // eslint-disable-next-line sonarjs/cognitive-complexity
) => {
if (listHasBeenFiltered) { if (listHasBeenFiltered) {
return; return;
} }
@ -359,6 +350,12 @@ export default function ProcessInstanceListTable({
); );
} else if (reportFilter.field_name === 'process_initiator_username') { } else if (reportFilter.field_name === 'process_initiator_username') {
setProcessInitiatorSelection(reportFilter.field_value || ''); setProcessInitiatorSelection(reportFilter.field_value || '');
} else if (reportFilter.field_name === 'with_oldest_open_task') {
setWithOldestOpenTask(reportFilter.field_value);
} else if (reportFilter.field_name === 'user_group_identifier') {
setSelectedUserGroup(reportFilter.field_value);
} else if (systemReportOptions.includes(reportFilter.field_name)) {
setSystemReport(reportFilter.field_name);
} else if (reportFilter.field_name === 'process_model_identifier') { } else if (reportFilter.field_name === 'process_model_identifier') {
selectedProcessModelIdentifier = selectedProcessModelIdentifier =
reportFilter.field_value || undefined; reportFilter.field_value || undefined;
@ -380,7 +377,7 @@ export default function ProcessInstanceListTable({
} }
} }
); );
processModelSelectionItemsForUseEffect.forEach( processModelSelectionItemsForUseEffect.current.forEach(
(processModel: ProcessModel) => { (processModel: ProcessModel) => {
if (processModel.id === selectedProcessModelIdentifier) { if (processModel.id === selectedProcessModelIdentifier) {
setProcessModelSelection(processModel); setProcessModelSelection(processModel);
@ -412,6 +409,13 @@ export default function ProcessInstanceListTable({
}); });
} }
if (filtersEnabled) {
HttpService.makeCallToBackend({
path: `/user-groups/for-current-user`,
successCallback: setUserGroups,
});
}
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `${processInstanceApiSearchPath}?${queryParamString}`, path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult, successCallback: setProcessInstancesFromResult,
@ -422,7 +426,28 @@ export default function ProcessInstanceListTable({
report_metadata: reportMetadataBodyToUse, report_metadata: reportMetadataBodyToUse,
}, },
}); });
},
[
additionalReportFilters,
dateParametersToAlwaysFilterBy,
filtersEnabled,
listHasBeenFiltered,
paginationQueryParamPrefix,
perPageOptions,
processInstanceApiSearchPath,
processModelFullIdentifier,
searchParams,
setProcessInstancesFromResult,
stopRefreshing,
systemReportOptions,
]
);
useEffect(() => {
if (!permissionsLoaded) {
return undefined;
} }
function getReportMetadataWithReportHash() { function getReportMetadataWithReportHash() {
if (listHasBeenFiltered) { if (listHasBeenFiltered) {
return; return;
@ -453,7 +478,7 @@ export default function ProcessInstanceListTable({
Object.assign(item, { label }); Object.assign(item, { label });
return item; return item;
}); });
processModelSelectionItemsForUseEffect = selectionArray; processModelSelectionItemsForUseEffect.current = selectionArray;
setProcessModelAvailableItems(selectionArray); setProcessModelAvailableItems(selectionArray);
const processStatusAllOptionsArray = PROCESS_STATUSES.map( const processStatusAllOptionsArray = PROCESS_STATUSES.map(
@ -489,23 +514,12 @@ export default function ProcessInstanceListTable({
return undefined; return undefined;
}, [ }, [
autoReload, autoReload,
searchParams,
params,
oneMonthInSeconds,
oneHourInSeconds,
dateParametersToAlwaysFilterBy,
parametersToGetFromSearchParams,
filtersEnabled, filtersEnabled,
paginationQueryParamPrefix, getProcessInstances,
processModelFullIdentifier,
perPageOptions,
reportIdentifier,
additionalReportFilters,
processInstanceApiSearchPath,
permissionsLoaded,
listHasBeenFiltered, listHasBeenFiltered,
setProcessInstancesFromResult, permissionsLoaded,
stopRefreshing, reportIdentifier,
searchParams,
]); ]);
const processInstanceReportSaveTag = () => { const processInstanceReportSaveTag = () => {
@ -620,7 +634,7 @@ export default function ProcessInstanceListTable({
const insertOrUpdateFieldInReportMetadata = ( const insertOrUpdateFieldInReportMetadata = (
reportMetadataToUse: ReportMetadata, reportMetadataToUse: ReportMetadata,
fieldName: string, fieldName: string,
fieldValue: string fieldValue: any
) => { ) => {
removeFieldFromReportMetadata(reportMetadataToUse, fieldName); removeFieldFromReportMetadata(reportMetadataToUse, fieldName);
if (fieldValue) { if (fieldValue) {
@ -644,7 +658,7 @@ export default function ProcessInstanceListTable({
return null; return null;
} }
let newReportMetadata = null; let newReportMetadata: ReportMetadata | null = null;
if (reportMetadata) { if (reportMetadata) {
newReportMetadata = { ...reportMetadata }; newReportMetadata = { ...reportMetadata };
} }
@ -677,41 +691,44 @@ export default function ProcessInstanceListTable({
endToSeconds endToSeconds
); );
if (processStatusSelection.length > 0) { insertOrUpdateFieldInReportMetadata(
insertOrUpdateFieldInReportMetadata( newReportMetadata,
newReportMetadata, 'process_status',
'process_status', processStatusSelection.length > 0
processStatusSelection.join(',') ? processStatusSelection.join(',')
); : null
} else { );
removeFieldFromReportMetadata(newReportMetadata, 'process_status'); insertOrUpdateFieldInReportMetadata(
} newReportMetadata,
'process_model_identifier',
processModelSelection ? processModelSelection.id : null
);
insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'process_initiator_username',
processInitiatorSelection
);
if (processModelSelection) { insertOrUpdateFieldInReportMetadata(
insertOrUpdateFieldInReportMetadata( newReportMetadata,
newReportMetadata, 'with_oldest_open_task',
'process_model_identifier', withOldestOpenTask
processModelSelection.id );
); insertOrUpdateFieldInReportMetadata(
} else { newReportMetadata,
removeFieldFromReportMetadata( 'user_group_identifier',
newReportMetadata, selectedUserGroup
'process_model_identifier' );
); systemReportOptions.forEach((systemReportOption: string) => {
} if (newReportMetadata) {
insertOrUpdateFieldInReportMetadata(
newReportMetadata,
systemReportOption,
systemReport === systemReportOption
);
}
});
if (processInitiatorSelection) {
insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'process_initiator_username',
processInitiatorSelection
);
} else {
removeFieldFromReportMetadata(
newReportMetadata,
'process_initiator_username'
);
}
return newReportMetadata; return newReportMetadata;
}; };
@ -843,14 +860,19 @@ export default function ProcessInstanceListTable({
clearFilters(); clearFilters();
const selectedReport = selection.selectedItem; const selectedReport = selection.selectedItem;
setProcessInstanceReportSelection(selectedReport); setProcessInstanceReportSelection(selectedReport);
removeError();
setProcessInstanceReportJustSaved(mode || null);
setListHasBeenFiltered(false);
let queryParamString = ''; let queryParamString = '';
if (selectedReport) { if (selectedReport) {
queryParamString = `?report_id=${selectedReport.id}`; queryParamString = `?report_id=${selectedReport.id}`;
}
removeError(); HttpService.makeCallToBackend({
setProcessInstanceReportJustSaved(mode || null); path: `/process-instances/report-metadata${queryParamString}`,
successCallback: getProcessInstances,
});
}
navigate(`${processInstanceListPathPrefix}${queryParamString}`); navigate(`${processInstanceListPathPrefix}${queryParamString}`);
}; };
@ -1181,6 +1203,75 @@ export default function ProcessInstanceListTable({
return null; return null;
}; };
const handleAdvancedOptionsClose = () => {
setShowAdvancedOptions(false);
};
const advancedOptionsModal = () => {
if (!showAdvancedOptions) {
return null;
}
const formElements = (
<>
<Grid fullWidth>
<Column md={4} lg={8} sm={2}>
<Dropdown
id="system-report-dropdown"
titleText="System Report"
items={['', ...systemReportOptions]}
itemToString={(item: any) => item}
selectedItem={systemReport}
onChange={(value: any) => {
setSystemReport(value.selectedItem);
setRequiresRefilter(true);
}}
/>
</Column>
<Column md={4} lg={8} sm={2}>
<Dropdown
id="user-group-dropdown"
titleText="User Group"
items={['', ...userGroups]}
itemToString={(item: any) => item}
selectedItem={selectedUserGroup}
onChange={(value: any) => {
setSelectedUserGroup(value.selectedItem);
setRequiresRefilter(true);
}}
/>
</Column>
</Grid>
<br />
<Grid fullWidth>
<Column md={4} lg={8} sm={2}>
<Checkbox
labelText="Include oldest open task information"
id="with-oldest-open-task-checkbox"
checked={withOldestOpenTask}
onChange={(value: any) => {
setWithOldestOpenTask(value.target.checked);
setRequiresRefilter(true);
}}
/>
</Column>
</Grid>
<div className="vertical-spacer-to-allow-combo-box-to-expand-in-modal" />
</>
);
return (
<Modal
open={showAdvancedOptions}
modalHeading="Advanced filter options"
primaryButtonText="Close"
onRequestSubmit={handleAdvancedOptionsClose}
onRequestClose={handleAdvancedOptionsClose}
hasScrollingContent
>
{formElements}
</Modal>
);
};
const filterOptions = () => { const filterOptions = () => {
if (!showFilterOptions) { if (!showFilterOptions) {
return null; return null;
@ -1366,10 +1457,20 @@ export default function ProcessInstanceListTable({
</Button> </Button>
</ButtonSet> </ButtonSet>
</Column> </Column>
<Column sm={4} md={4} lg={8}> <Column sm={3} md={3} lg={7}>
{saveAsReportComponent()} {saveAsReportComponent()}
{deleteReportComponent()} {deleteReportComponent()}
</Column> </Column>
<Column sm={1} md={1} lg={1}>
<Button
kind="ghost"
onClick={() => setShowAdvancedOptions(true)}
data-qa="advanced-options-filters"
className="narrow-button button-link float-right"
>
Advanced
</Button>
</Column>
</Grid> </Grid>
</> </>
); );
@ -1567,12 +1668,39 @@ export default function ProcessInstanceListTable({
return null; return null;
}; };
const linkToReport = () => { const tableTitleLine = () => {
if (!showLinkToReport) { if (!showLinkToReport && !headerElement) {
return null; return null;
} }
let filterButtonLink = null;
if (showLinkToReport) {
filterButtonLink = (
<Column
sm={{ span: 1, offset: 3 }}
md={{ span: 1, offset: 7 }}
lg={{ span: 1, offset: 15 }}
>
<Button
data-qa="process-instance-list-link"
kind="ghost"
renderIcon={ArrowRight}
iconDescription="Go to Filterable List"
hasIconOnly
size="lg"
onClick={() =>
navigate(`/admin/process-instances?report_hash=${reportHash}`)
}
/>
</Column>
);
}
return ( return (
<Link to={`/admin/process-instances?report_hash=${reportHash}`}>Hey</Link> <Grid fullWidth condensed>
<Column sm={{ span: 3 }} md={{ span: 4 }} lg={{ span: 3 }}>
{headerElement}
</Column>
{filterButtonLink}
</Grid>
); );
}; };
@ -1623,8 +1751,9 @@ export default function ProcessInstanceListTable({
return ( return (
<> <>
{reportColumnForm()} {reportColumnForm()}
{advancedOptionsModal()}
{processInstanceReportSaveTag()} {processInstanceReportSaveTag()}
{linkToReport()} {tableTitleLine()}
<Filters <Filters
filterOptions={filterOptions} filterOptions={filterOptions}
showFilterOptions={showFilterOptions} showFilterOptions={showFilterOptions}

View File

@ -105,10 +105,12 @@ h2 {
} }
.cds--btn--ghost.button-link:hover { .cds--btn--ghost.button-link:hover {
color: #0062fe; color: #0062fe;
background-color: white;
padding-left: 0; padding-left: 0;
} }
.cds--btn--ghost.button-link:visited:hover { .cds--btn--ghost.button-link:visited:hover {
color: #0062fe; color: #0062fe;
background-color: white;
padding-left: 0; padding-left: 0;
} }
@ -449,3 +451,7 @@ svg.notification-icon {
.user_instructions_4 { .user_instructions_4 {
filter: opacity(10%); filter: opacity(10%);
} }
.float-right {
float: right;
}

View File

@ -163,7 +163,8 @@ export interface MessageInstance {
export interface ReportFilter { export interface ReportFilter {
field_name: string; field_name: string;
field_value: string | null; // using any here so we can use this as a string and boolean
field_value: any;
operator?: string; operator?: string;
} }
@ -335,3 +336,5 @@ export interface ProcessModelCaller {
display_name: string; display_name: string;
process_model_id: string; process_model_id: string;
} }
export interface UserGroup {}

View File

@ -20,46 +20,55 @@ export default function InProgressInstances() {
return userGroups.map((userGroup: string) => { return userGroups.map((userGroup: string) => {
const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`; const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`;
const headerElement = (
<h2 title={titleText} className="process-instance-table-header">
Waiting for <strong>{userGroup}</strong>
</h2>
);
return ( return (
<> <ProcessInstanceListTable
<h2 title={titleText} className="process-instance-table-header"> headerElement={headerElement}
Waiting for <strong>{userGroup}</strong> showLinkToReport
</h2> filtersEnabled={false}
<ProcessInstanceListTable paginationQueryParamPrefix={`waiting_for_${slugifyString(
filtersEnabled={false} userGroup
paginationQueryParamPrefix={`waiting_for_${slugifyString( ).replace('-', '_')}`}
userGroup paginationClassName="with-large-bottom-margin"
).replace('-', '_')}`} perPageOptions={[2, 5, 25]}
paginationClassName="with-large-bottom-margin" reportIdentifier="system_report_in_progress_instances_with_tasks_for_my_group"
perPageOptions={[2, 5, 25]} showReports={false}
reportIdentifier="system_report_in_progress_instances_with_tasks_for_my_group" textToShowIfEmpty="This group has no instances waiting on it at this time."
showReports={false} additionalReportFilters={[
textToShowIfEmpty="This group has no instances waiting on it at this time." { field_name: 'user_group_identifier', field_value: userGroup },
additionalReportFilters={[ ]}
{ field_name: 'user_group_identifier', field_value: userGroup }, canCompleteAllTasks
]} showActionsColumn
canCompleteAllTasks autoReload={false}
showActionsColumn />
autoReload={false}
/>
</>
); );
}); });
}; };
const startedByMeTitleText = const startedByMeTitleText =
'This is a list of open instances that you started.'; 'This is a list of open instances that you started.';
const startedByMeHeaderElement = (
<h2 title={startedByMeTitleText} className="process-instance-table-header">
Started by me
</h2>
);
const waitingForMeTitleText = const waitingForMeTitleText =
'This is a list of instances that have tasks that you can complete.'; 'This is a list of instances that have tasks that you can complete.';
const waitingForMeHeaderElement = (
<h2 title={waitingForMeTitleText} className="process-instance-table-header">
Waiting for me
</h2>
);
return ( return (
<> <>
<h2
title={startedByMeTitleText}
className="process-instance-table-header"
>
Started by me
</h2>
<ProcessInstanceListTable <ProcessInstanceListTable
headerElement={startedByMeHeaderElement}
filtersEnabled={false} filtersEnabled={false}
paginationQueryParamPrefix="open_instances_started_by_me" paginationQueryParamPrefix="open_instances_started_by_me"
perPageOptions={[2, 5, 25]} perPageOptions={[2, 5, 25]}
@ -71,14 +80,9 @@ export default function InProgressInstances() {
showActionsColumn showActionsColumn
autoReload={false} autoReload={false}
/> />
{/*
<h2
title={waitingForMeTitleText}
className="process-instance-table-header"
>
Waiting for me
</h2>
<ProcessInstanceListTable <ProcessInstanceListTable
headerElement={waitingForMeHeaderElement}
showLinkToReport
filtersEnabled={false} filtersEnabled={false}
paginationQueryParamPrefix="waiting_for_me" paginationQueryParamPrefix="waiting_for_me"
perPageOptions={[2, 5, 25]} perPageOptions={[2, 5, 25]}
@ -91,7 +95,6 @@ export default function InProgressInstances() {
autoReload autoReload
/> />
{groupTableComponents()} {groupTableComponents()}
*/}
</> </>
); );
} }