diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json
index f2b3d4fa0..ca6d5de51 100644
--- a/spiffworkflow-frontend/package-lock.json
+++ b/spiffworkflow-frontend/package-lock.json
@@ -11354,9 +11354,12 @@
}
},
"node_modules/date-fns": {
- "version": "2.29.3",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
- "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
"engines": {
"node": ">=0.11"
},
@@ -40545,9 +40548,12 @@
}
},
"date-fns": {
- "version": "2.29.3",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
- "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "requires": {
+ "@babel/runtime": "^7.21.0"
+ }
},
"dayjs": {
"version": "1.11.7",
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 4cddd5530..5b33c1185 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1,4 +1,3 @@
-import { intervalToDuration } from 'date-fns';
import React, {
useCallback,
useEffect,
@@ -52,6 +51,7 @@ import {
titleizeString,
truncateString,
isANumber,
+ formatDurationForDisplay,
} from '../helpers';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
@@ -1667,10 +1667,16 @@ export default function ProcessInstanceListTable({
const formatProcessInstanceId = (_row: ProcessInstance, id: number) => {
return {id};
};
- const formatProcessModelIdentifier = (_row: any, identifier: any) => {
+ const formatProcessModelIdentifier = (
+ _row: ProcessInstance,
+ identifier: any
+ ) => {
return {identifier};
};
- const formatProcessModelDisplayName = (_row: any, identifier: any) => {
+ const formatProcessModelDisplayName = (
+ _row: ProcessInstance,
+ identifier: any
+ ) => {
return {identifier};
};
const formatLastMilestone = (
@@ -1684,14 +1690,14 @@ export default function ProcessInstanceListTable({
return {truncatedValue};
};
- const formatSecondsForDisplay = (_row: any, seconds: any) => {
+ const formatSecondsForDisplay = (_row: ProcessInstance, seconds: any) => {
return convertSecondsToFormattedDateTime(seconds) || '-';
};
- const defaultFormatter = (_row: any, value: any) => {
+ const defaultFormatter = (_row: ProcessInstance, value: any) => {
return value;
};
- const formatDateTime = (_row: any, value: any) => {
+ const formatDateTime = (_row: ProcessInstance, value: any) => {
if (value === undefined) {
return undefined;
}
@@ -1709,21 +1715,6 @@ export default function ProcessInstanceListTable({
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 reportColumnFormatters: Record = {
id: formatProcessInstanceId,
@@ -1738,7 +1729,7 @@ export default function ProcessInstanceListTable({
};
const displayTypeFormatters: Record = {
date_time: formatDateTime,
- duration: formatDuration,
+ duration: formatDurationForDisplay,
};
const columnAccessor = column.accessor as keyof ProcessInstance;
const formatter = column.display_type
diff --git a/spiffworkflow-frontend/src/helpers.test.tsx b/spiffworkflow-frontend/src/helpers.test.tsx
index d359012d8..042c8dcc5 100644
--- a/spiffworkflow-frontend/src/helpers.test.tsx
+++ b/spiffworkflow-frontend/src/helpers.test.tsx
@@ -4,6 +4,7 @@ import {
slugifyString,
underscorizeString,
recursivelyChangeNullAndUndefined,
+ formatDurationForDisplay,
} from './helpers';
test('it can slugify a string', () => {
@@ -101,3 +102,13 @@ test('it can replace null values in object with undefined', () => {
expect(result.contacts.awesome).toEqual(false);
expect(result.contacts.info).toEqual('');
});
+
+test('it can properly format a duration', () => {
+ expect(formatDurationForDisplay(null, '0')).toEqual('0s');
+ expect(formatDurationForDisplay(null, '60')).toEqual('1m');
+ expect(formatDurationForDisplay(null, '65')).toEqual('1m 5s');
+ expect(formatDurationForDisplay(null, 65)).toEqual('1m 5s');
+ expect(formatDurationForDisplay(null, 86500)).toEqual('1d 1m 40s');
+ expect(formatDurationForDisplay(null, 2629746)).toEqual('30d 10h 29m 6s');
+ expect(formatDurationForDisplay(null, 31536765)).toEqual('365d 12m 45s');
+});
diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx
index 4bb0f879b..2a79d0620 100644
--- a/spiffworkflow-frontend/src/helpers.tsx
+++ b/spiffworkflow-frontend/src/helpers.tsx
@@ -1,4 +1,4 @@
-import { format } from 'date-fns';
+import { Duration, format } from 'date-fns';
import { Buffer } from 'buffer';
import {
@@ -399,3 +399,43 @@ export const getLastMilestoneFromProcessInstance = (
}
return [valueToUse, truncatedValue];
};
+
+// logic from https://stackoverflow.com/a/28510323/6090676
+export const secondsToDuration = (secNum: number) => {
+ const days = Math.floor(secNum / 86400);
+ const hours = Math.floor(secNum / 3600) % 24;
+ const minutes = Math.floor(secNum / 60) % 60;
+ const seconds = secNum % 60;
+
+ const duration: Duration = {
+ days,
+ hours,
+ minutes,
+ seconds,
+ };
+ return duration;
+};
+
+export const formatDurationForDisplay = (_row: any, value: any) => {
+ if (value === undefined) {
+ return undefined;
+ }
+ const duration = secondsToDuration(parseInt(value, 10));
+ const durationTimes = [];
+ if (duration.seconds !== undefined && duration.seconds > 0) {
+ durationTimes.unshift(`${duration.seconds}s`);
+ }
+ if (duration.minutes !== undefined && duration.minutes > 0) {
+ durationTimes.unshift(`${duration.minutes}m`);
+ }
+ if (duration.hours !== undefined && duration.hours > 0) {
+ durationTimes.unshift(`${duration.hours}h`);
+ }
+ if (duration.days !== undefined && duration.days > 0) {
+ durationTimes.unshift(`${duration.days}d`);
+ }
+ if (durationTimes.length < 1) {
+ durationTimes.push('0s');
+ }
+ return durationTimes.join(' ');
+};