diff --git a/webapp/src/components/backstage/metrics/metrics_row.tsx b/webapp/src/components/backstage/metrics/metrics_row.tsx
index ed26cfcb1e..6070da176f 100644
--- a/webapp/src/components/backstage/metrics/metrics_row.tsx
+++ b/webapp/src/components/backstage/metrics/metrics_row.tsx
@@ -68,7 +68,7 @@ const Cell = ({type, value, target}: CellProps) => {
let diff = prefix + Math.abs(value - target);
if (type === MetricType.MetricDuration) {
val =
{formatDuration(valueAsDuration)}
;
- diff = prefix + formatDuration(Duration.fromMillis(target).minus(valueAsDuration));
+ diff = formatDuration(Duration.fromMillis(target).minus(valueAsDuration));
}
return (
diff --git a/webapp/src/components/formatted_duration.test.tsx b/webapp/src/components/formatted_duration.test.tsx
index 00a182e3fb..ef0d757932 100644
--- a/webapp/src/components/formatted_duration.test.tsx
+++ b/webapp/src/components/formatted_duration.test.tsx
@@ -42,6 +42,7 @@ describe('formatDuration', () => {
[{months: 1}, '30d', '30 days'],
[{months: 2}, '60d', '60 days'],
[{years: 2, days: 6, minutes: 12}, '2y, 6d, 12m', '2 years, 6 days, 12 minutes'],
+ [{days: -1, hours: -2, minutes: -5}, '-1d, 2h, 5m', '-1 day, 2 hours, 5 minutes'],
])('should format %p as %p and %p', (durationObj, expectedNarrow, expectedLong) => {
const duration = Duration.fromObject(durationObj);
expect(formatDuration(duration)).toEqual(expectedNarrow);
diff --git a/webapp/src/components/formatted_duration.tsx b/webapp/src/components/formatted_duration.tsx
index 5775e54286..fcfe49f1e1 100644
--- a/webapp/src/components/formatted_duration.tsx
+++ b/webapp/src/components/formatted_duration.tsx
@@ -30,20 +30,29 @@ interface DurationProps {
const UNITS: DurationUnit[] = ['years', 'days', 'hours', 'minutes'];
export const formatDuration = (value: Duration, style: FormatStyle = 'narrow', truncate: TruncateBehavior = 'none') => {
- if (value.as('seconds') < 60) {
- return value
+ let localValue = value;
+
+ const isNegative = value.toMillis() < 0;
+ if (isNegative) {
+ localValue = value.negate();
+ }
+
+ if (localValue.as('seconds') < 60) {
+ const str = localValue
.shiftTo('seconds')
.mapUnits(Math.floor)
.toHuman({unitDisplay: style});
+ return isNegative ? `-${str}` : str;
}
- const duration = value.shiftTo(...UNITS).normalize();
+ const duration = localValue.shiftTo(...UNITS).normalize();
const formatUnits = truncate === 'truncate' ? [UNITS.find((unit) => duration.get(unit) > 0)!] : UNITS.filter((unit) => duration.get(unit) > 0);
- return duration
+ const str = duration
.shiftTo(...formatUnits)
.mapUnits(Math.floor)
.toHuman({unitDisplay: style});
+ return isNegative ? `-${str}` : str;
};
const FormattedDuration = ({from, to = 0, style, truncate}: DurationProps) => {