chore: optimize formatDuration

This commit is contained in:
Gareth George
2025-05-05 00:26:50 -07:00
parent 18354c8269
commit 9982761aa8
2 changed files with 53 additions and 45 deletions
+24 -30
View File
@@ -60,49 +60,43 @@ export const formatDate = (time: number | string | Date) => {
return isoStr.substring(0, 10);
};
const durationUnits = ["seconds", "minutes", "hours", "days"] as const;
type DurationUnit = typeof durationUnits[number];
const durationSteps = [1000, 60, 60, 24, Number.MAX_VALUE];
const durationFactors = [1, 1000, 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000];
const shortDurationUnits = ["ms", "s", "m", "h", "d"];
type DurationUnit = typeof shortDurationUnits[number];
export interface FormatDurationOptions {
minUnit?: DurationUnit;
maxUnit?: DurationUnit;
}
export const formatDuration = (ms: number, options?: FormatDurationOptions) => {
const minUnitIndex = durationUnits.indexOf(options?.minUnit || "seconds");
const maxUnitIndex = durationUnits.indexOf(options?.maxUnit || "hours");
if (!ms && ms !== 0) return "";
const seconds = Math.ceil(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (!options && ms < 60 * 1000) {
// If no options and less than a minute, show seconds
// Performance optimization
return `${Math.round(ms / 1000)}s`;
}
let parts: string[] = [];
const minUnitIndex = options?.minUnit ? shortDurationUnits.indexOf(options.minUnit) : 1; // Don't show ms by default
const maxUnitIndex = options?.maxUnit ? shortDurationUnits.indexOf(options.maxUnit) : shortDurationUnits.length - 1;
if (maxUnitIndex >= 3 && days > 0) {
parts.push(`${days}d`);
}
if (maxUnitIndex >= 2 && minUnitIndex <= 2) {
const h = maxUnitIndex === 2 ? hours : (hours % 24);
if (h > 0) {
parts.push(`${h}h`);
}
}
if (maxUnitIndex >= 1 && minUnitIndex <= 1) {
const m = maxUnitIndex === 1 ? minutes : (minutes % 60);
if (m > 0) {
parts.push(`${m}m`);
}
}
if (maxUnitIndex >= 0) {
const s = maxUnitIndex === 0 ? seconds : (seconds % 60);
if (s > 0 || parts.length === 0) {
parts.push(`${s}s`);
const absMs = Math.abs(ms);
let result = "";
for (let i = maxUnitIndex; i >= minUnitIndex; i--) {
const value = Math.floor(absMs / durationFactors[i]) % durationSteps[i];
if (value > 0) {
result += `${value}${shortDurationUnits[i]}`;
}
}
return parts.join("");
if (!result) {
result = `0${shortDurationUnits[minUnitIndex]}`;
}
return ms < 0 ? `-${result}` : result;
};
export const normalizeSnapshotId = (id: string) => {
+29 -15
View File
@@ -22,7 +22,11 @@ import {
Schedule_Clock,
type Plan,
} from "../../gen/ts/v1/config_pb";
import { CalculatorOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import {
CalculatorOutlined,
MinusCircleOutlined,
PlusOutlined,
} from "@ant-design/icons";
import { URIAutocomplete } from "../components/URIAutocomplete";
import { formatErrorAlert, useAlertApi } from "../components/Alerts";
import { namePattern, validateForm } from "../lib/formutil";
@@ -551,7 +555,10 @@ const RetentionPolicyView = () => {
const retention = Form.useWatch("retention", { form, preserve: true }) as any;
// If the first value in the cron expression (minutes) is not just a plain number (e.g. 30), the
// cron will hit more than once per hour (e.g. "*/15" "1,30" and "*").
const cronIsSubHourly = useMemo(() => schedule?.cron && !/^\d+ /.test(schedule.cron), [schedule?.cron]);
const cronIsSubHourly = useMemo(
() => schedule?.cron && !/^\d+ /.test(schedule.cron),
[schedule?.cron]
);
// Translates the number of snapshots retained to a retention duration for cron schedules.
const minRetention = useMemo(() => {
const keepLastN = retention?.policyTimeBucketed?.keepLastN;
@@ -567,11 +574,12 @@ const RetentionPolicyView = () => {
} else if (schedule?.maxFrequencyDays) {
duration = schedule.maxFrequencyDays * (keepLastN - 1) * msPerDay;
} else if (schedule?.cron && retention.policyTimeBucketed?.keepLastN) {
duration = getMinimumCronDuration(schedule.cron, retention.policyTimeBucketed?.keepLastN);
duration = getMinimumCronDuration(
schedule.cron,
retention.policyTimeBucketed?.keepLastN
);
}
return duration
? formatDuration(duration, { maxUnit: "days", minUnit: "minutes" })
: null;
return duration ? formatDuration(duration, { minUnit: "h" }) : null;
}, [schedule, retention?.policyTimeBucketed?.keepLastN]);
const determineMode = () => {
@@ -703,7 +711,8 @@ const RetentionPolicyView = () => {
throw new Error("Specify a number greater than 1");
}
},
message: "Your schedule runs more than once per hour; choose how many snapshots to keep before handing off to the retention policy.",
message:
"Your schedule runs more than once per hour; choose how many snapshots to keep before handing off to the retention policy.",
},
]}
>
@@ -711,15 +720,20 @@ const RetentionPolicyView = () => {
type="number"
min={0}
addonAfter={
<Tooltip title={minRetention
? `${retention?.policyTimeBucketed?.keepLastN} snapshots represents an expected retention duration of at least
<Tooltip
title={
minRetention
? `${retention?.policyTimeBucketed?.keepLastN} snapshots represents an expected retention duration of at least
${minRetention}, but this may vary with manual backups or if intermittently online.`
: "Choose how many snapshots to retain, then use the calculator to see the expected duration they would cover."
}>
<CalculatorOutlined style={{
padding: ".5em",
margin: "0 -.5em"
}} />
: "Choose how many snapshots to retain, then use the calculator to see the expected duration they would cover."
}
>
<CalculatorOutlined
style={{
padding: ".5em",
margin: "0 -.5em",
}}
/>
</Tooltip>
}
/>