Feature/metadata column display type (#534)
* some basics to set a display type for a metadata column when displaying w/ burnettk * added supuport for durations and some clean up * only display hours and days in duration if they are above 0 to keep it a little cleaner --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
ceb06cc227
commit
3276fdb579
|
@ -1,3 +1,4 @@
|
||||||
|
import { intervalToDuration } from 'date-fns';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -50,6 +51,7 @@ import {
|
||||||
REFRESH_TIMEOUT_SECONDS,
|
REFRESH_TIMEOUT_SECONDS,
|
||||||
titleizeString,
|
titleizeString,
|
||||||
truncateString,
|
truncateString,
|
||||||
|
isANumber,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
|
|
||||||
|
@ -71,6 +73,7 @@ import {
|
||||||
ErrorForDisplay,
|
ErrorForDisplay,
|
||||||
PermissionsToCheck,
|
PermissionsToCheck,
|
||||||
FilterOperatorMapping,
|
FilterOperatorMapping,
|
||||||
|
FilterDisplayTypeMapping,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import ProcessModelSearch from './ProcessModelSearch';
|
import ProcessModelSearch from './ProcessModelSearch';
|
||||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||||
|
@ -177,6 +180,11 @@ export default function ProcessInstanceListTable({
|
||||||
'Is Not Empty': { id: 'is_not_empty', requires_value: false },
|
'Is Not Empty': { id: 'is_not_empty', requires_value: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterDisplayTypes: FilterDisplayTypeMapping = {
|
||||||
|
date_time: 'Date / time',
|
||||||
|
duration: 'Duration',
|
||||||
|
};
|
||||||
|
|
||||||
const processInstanceListPathPrefix =
|
const processInstanceListPathPrefix =
|
||||||
variant === 'all' ? '/process-instances/all' : '/process-instances/for-me';
|
variant === 'all' ? '/process-instances/all' : '/process-instances/for-me';
|
||||||
const processInstanceShowPathPrefix =
|
const processInstanceShowPathPrefix =
|
||||||
|
@ -217,6 +225,8 @@ export default function ProcessInstanceListTable({
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [withOldestOpenTask, setWithOldestOpenTask] =
|
const [withOldestOpenTask, setWithOldestOpenTask] =
|
||||||
useState<boolean>(showActionsColumn);
|
useState<boolean>(showActionsColumn);
|
||||||
|
const [withRelationToMe, setwithRelationToMe] =
|
||||||
|
useState<boolean>(showActionsColumn);
|
||||||
const [systemReport, setSystemReport] = useState<string | null>(null);
|
const [systemReport, setSystemReport] = useState<string | null>(null);
|
||||||
const [selectedUserGroup, setSelectedUserGroup] = useState<string | null>(
|
const [selectedUserGroup, setSelectedUserGroup] = useState<string | null>(
|
||||||
null
|
null
|
||||||
|
@ -343,6 +353,7 @@ export default function ProcessInstanceListTable({
|
||||||
setEndToTime('');
|
setEndToTime('');
|
||||||
setProcessInitiatorSelection(null);
|
setProcessInitiatorSelection(null);
|
||||||
setWithOldestOpenTask(false);
|
setWithOldestOpenTask(false);
|
||||||
|
setwithRelationToMe(false);
|
||||||
setSystemReport(null);
|
setSystemReport(null);
|
||||||
setSelectedUserGroup(null);
|
setSelectedUserGroup(null);
|
||||||
if (updateRequiresRefilter) {
|
if (updateRequiresRefilter) {
|
||||||
|
@ -410,6 +421,8 @@ export default function ProcessInstanceListTable({
|
||||||
setProcessInitiatorSelection(reportFilter.field_value || '');
|
setProcessInitiatorSelection(reportFilter.field_value || '');
|
||||||
} else if (reportFilter.field_name === 'with_oldest_open_task') {
|
} else if (reportFilter.field_name === 'with_oldest_open_task') {
|
||||||
setWithOldestOpenTask(reportFilter.field_value);
|
setWithOldestOpenTask(reportFilter.field_value);
|
||||||
|
} else if (reportFilter.field_name === 'with_relation_to_me') {
|
||||||
|
setwithRelationToMe(reportFilter.field_value);
|
||||||
} else if (reportFilter.field_name === 'user_group_identifier') {
|
} else if (reportFilter.field_name === 'user_group_identifier') {
|
||||||
setSelectedUserGroup(reportFilter.field_value);
|
setSelectedUserGroup(reportFilter.field_value);
|
||||||
} else if (systemReportOptions.includes(reportFilter.field_name)) {
|
} else if (systemReportOptions.includes(reportFilter.field_name)) {
|
||||||
|
@ -779,6 +792,11 @@ export default function ProcessInstanceListTable({
|
||||||
'with_oldest_open_task',
|
'with_oldest_open_task',
|
||||||
withOldestOpenTask
|
withOldestOpenTask
|
||||||
);
|
);
|
||||||
|
insertOrUpdateFieldInReportMetadata(
|
||||||
|
newReportMetadata,
|
||||||
|
'with_relation_to_me',
|
||||||
|
withRelationToMe
|
||||||
|
);
|
||||||
insertOrUpdateFieldInReportMetadata(
|
insertOrUpdateFieldInReportMetadata(
|
||||||
newReportMetadata,
|
newReportMetadata,
|
||||||
'user_group_identifier',
|
'user_group_identifier',
|
||||||
|
@ -838,7 +856,11 @@ export default function ProcessInstanceListTable({
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DatePicker dateFormat={DATE_FORMAT_CARBON} datePickerType="single">
|
<DatePicker
|
||||||
|
id={`date-picker-parent-${name}`}
|
||||||
|
dateFormat={DATE_FORMAT_CARBON}
|
||||||
|
datePickerType="single"
|
||||||
|
>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
id={`date-picker-${name}`}
|
id={`date-picker-${name}`}
|
||||||
placeholder={DATE_FORMAT_FOR_DISPLAY}
|
placeholder={DATE_FORMAT_FOR_DISPLAY}
|
||||||
|
@ -860,7 +882,7 @@ export default function ProcessInstanceListTable({
|
||||||
</DatePicker>
|
</DatePicker>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
invalid={timeInvalid}
|
invalid={timeInvalid}
|
||||||
id="time-picker"
|
id={`time-picker-${name}`}
|
||||||
labelText="Select a time"
|
labelText="Select a time"
|
||||||
pattern="^([01]\d|2[0-3]):?([0-5]\d)$"
|
pattern="^([01]\d|2[0-3]):?([0-5]\d)$"
|
||||||
value={initialTime}
|
value={initialTime}
|
||||||
|
@ -1128,6 +1150,18 @@ export default function ProcessInstanceListTable({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setFilterDisplayType = (selectedItem: string) => {
|
||||||
|
if (reportColumnToOperateOn) {
|
||||||
|
const reportColumnToOperateOnCopy = {
|
||||||
|
...reportColumnToOperateOn,
|
||||||
|
};
|
||||||
|
const displayType = getKeyByValue(filterDisplayTypes, selectedItem);
|
||||||
|
reportColumnToOperateOnCopy.display_type = displayType;
|
||||||
|
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||||
|
setRequiresRefilter(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const reportColumnForm = () => {
|
const reportColumnForm = () => {
|
||||||
if (reportColumnFormMode === '') {
|
if (reportColumnFormMode === '') {
|
||||||
|
@ -1175,6 +1209,22 @@ export default function ProcessInstanceListTable({
|
||||||
/>,
|
/>,
|
||||||
]);
|
]);
|
||||||
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
||||||
|
formElements.push(
|
||||||
|
<Dropdown
|
||||||
|
titleText="Display type"
|
||||||
|
id="report-column-display-type"
|
||||||
|
items={[''].concat(Object.values(filterDisplayTypes))}
|
||||||
|
selectedItem={
|
||||||
|
reportColumnToOperateOn.display_type
|
||||||
|
? filterDisplayTypes[reportColumnToOperateOn.display_type]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onChange={(value: any) => {
|
||||||
|
setFilterDisplayType(value.selectedItem);
|
||||||
|
setRequiresRefilter(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
formElements.push(
|
formElements.push(
|
||||||
<Dropdown
|
<Dropdown
|
||||||
titleText="Operator"
|
titleText="Operator"
|
||||||
|
@ -1358,6 +1408,19 @@ export default function ProcessInstanceListTable({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
|
{variant === 'all' ? (
|
||||||
|
<Column md={4} lg={8} sm={2}>
|
||||||
|
<Checkbox
|
||||||
|
labelText="Include tasks for me"
|
||||||
|
id="with-relation-to-me"
|
||||||
|
checked={withRelationToMe}
|
||||||
|
onChange={(value: any) => {
|
||||||
|
setwithRelationToMe(value.target.checked);
|
||||||
|
setRequiresRefilter(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
) : null}
|
||||||
</Grid>
|
</Grid>
|
||||||
<div className="vertical-spacer-to-allow-combo-box-to-expand-in-modal" />
|
<div className="vertical-spacer-to-allow-combo-box-to-expand-in-modal" />
|
||||||
</>
|
</>
|
||||||
|
@ -1628,6 +1691,39 @@ export default function ProcessInstanceListTable({
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatDateTime = (_row: any, value: any) => {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let dateInSeconds = value;
|
||||||
|
if (!isANumber(value)) {
|
||||||
|
const timeArgs = value.split('T');
|
||||||
|
dateInSeconds = convertDateAndTimeStringsToSeconds(
|
||||||
|
timeArgs[0],
|
||||||
|
timeArgs[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (dateInSeconds) {
|
||||||
|
return convertSecondsToFormattedDateTime(dateInSeconds);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (_row: any, value: any) => {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const duration = intervalToDuration({ start: 0, end: value * 1000 });
|
||||||
|
let durationString = `${duration.minutes}m ${duration.seconds}s`;
|
||||||
|
if (duration.hours !== undefined && duration.hours > 0) {
|
||||||
|
durationString = `${duration.hours}h ${durationString}`;
|
||||||
|
}
|
||||||
|
if (duration.days !== undefined && duration.days > 0) {
|
||||||
|
durationString = `${duration.days}d ${durationString}`;
|
||||||
|
}
|
||||||
|
return durationString;
|
||||||
|
};
|
||||||
|
|
||||||
const formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
|
const formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
|
||||||
const reportColumnFormatters: Record<string, any> = {
|
const reportColumnFormatters: Record<string, any> = {
|
||||||
id: formatProcessInstanceId,
|
id: formatProcessInstanceId,
|
||||||
|
@ -1640,9 +1736,14 @@ export default function ProcessInstanceListTable({
|
||||||
task_updated_at_in_seconds: formatSecondsForDisplay,
|
task_updated_at_in_seconds: formatSecondsForDisplay,
|
||||||
last_milestone_bpmn_name: formatLastMilestone,
|
last_milestone_bpmn_name: formatLastMilestone,
|
||||||
};
|
};
|
||||||
|
const displayTypeFormatters: Record<string, any> = {
|
||||||
|
date_time: formatDateTime,
|
||||||
|
duration: formatDuration,
|
||||||
|
};
|
||||||
const columnAccessor = column.accessor as keyof ProcessInstance;
|
const columnAccessor = column.accessor as keyof ProcessInstance;
|
||||||
const formatter =
|
const formatter = column.display_type
|
||||||
reportColumnFormatters[columnAccessor] ?? defaultFormatter;
|
? displayTypeFormatters[column.display_type]
|
||||||
|
: reportColumnFormatters[columnAccessor] ?? defaultFormatter;
|
||||||
const value = row[columnAccessor];
|
const value = row[columnAccessor];
|
||||||
|
|
||||||
if (columnAccessor === 'status') {
|
if (columnAccessor === 'status') {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateString,
|
convertSecondsToFormattedDateString,
|
||||||
isInteger,
|
isANumber,
|
||||||
slugifyString,
|
slugifyString,
|
||||||
underscorizeString,
|
underscorizeString,
|
||||||
recursivelyChangeNullAndUndefined,
|
recursivelyChangeNullAndUndefined,
|
||||||
|
@ -24,11 +24,13 @@ test('it can keep the correct date when converting seconds to date', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it can validate numeric values', () => {
|
test('it can validate numeric values', () => {
|
||||||
expect(isInteger('11')).toEqual(true);
|
expect(isANumber('11')).toEqual(true);
|
||||||
expect(isInteger('hey')).toEqual(false);
|
expect(isANumber('hey')).toEqual(false);
|
||||||
expect(isInteger(' ')).toEqual(false);
|
expect(isANumber(' ')).toEqual(false);
|
||||||
expect(isInteger('1 2')).toEqual(false);
|
expect(isANumber('1 2')).toEqual(false);
|
||||||
expect(isInteger(2)).toEqual(true);
|
expect(isANumber(2)).toEqual(true);
|
||||||
|
expect(isANumber(2.0)).toEqual(true);
|
||||||
|
expect(isANumber('2.0')).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it can replace undefined values in object with null', () => {
|
test('it can replace undefined values in object with null', () => {
|
||||||
|
|
|
@ -351,8 +351,10 @@ export const setPageTitle = (items: Array<string>) => {
|
||||||
document.title = ['SpiffWorkflow'].concat(items).join(' - ');
|
document.title = ['SpiffWorkflow'].concat(items).join(' - ');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isInteger = (str: string | number) => {
|
// calling it isANumber to avoid confusion with other libraries
|
||||||
return /^\d+$/.test(str.toString());
|
// that have isNumber methods
|
||||||
|
export const isANumber = (str: string | number) => {
|
||||||
|
return /^\d+(\.\d+)?$/.test(str.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const encodeBase64 = (data: string) => {
|
export const encodeBase64 = (data: string) => {
|
||||||
|
|
|
@ -151,6 +151,10 @@ export interface FilterOperatorMapping {
|
||||||
[key: string]: FilterOperator;
|
[key: string]: FilterOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FilterDisplayTypeMapping {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProcessFile {
|
export interface ProcessFile {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
last_modified: string;
|
last_modified: string;
|
||||||
|
@ -228,6 +232,7 @@ export interface ReportColumn {
|
||||||
Header: string;
|
Header: string;
|
||||||
accessor: string;
|
accessor: string;
|
||||||
filterable: boolean;
|
filterable: boolean;
|
||||||
|
display_type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReportColumnForEditing extends ReportColumn {
|
export interface ReportColumnForEditing extends ReportColumn {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Button, Form, Stack, TextInput } from '@carbon/react';
|
import { Button, Form, Stack, TextInput } from '@carbon/react';
|
||||||
import { isInteger, modifyProcessIdentifierForPathParam } from '../helpers';
|
import { isANumber, modifyProcessIdentifierForPathParam } from '../helpers';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import ProcessInstanceListTabs from '../components/ProcessInstanceListTabs';
|
import ProcessInstanceListTabs from '../components/ProcessInstanceListTabs';
|
||||||
import { ProcessInstance } from '../interfaces';
|
import { ProcessInstance } from '../interfaces';
|
||||||
|
@ -42,7 +42,7 @@ export default function ProcessInstanceFindById() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProcessInstanceIdChange = (event: any) => {
|
const handleProcessInstanceIdChange = (event: any) => {
|
||||||
if (isInteger(event.target.value)) {
|
if (isANumber(event.target.value)) {
|
||||||
setProcessInstanceIdValid(true);
|
setProcessInstanceIdValid(true);
|
||||||
} else {
|
} else {
|
||||||
setProcessInstanceIdValid(false);
|
setProcessInstanceIdValid(false);
|
||||||
|
|
Loading…
Reference in New Issue