Skip to content

Commit 5cdda2e

Browse files
chore: replace date-fns by dayjs (#18022)
This change replaces date-fns with dayjs throughout the codebase for more consistent date/time handling and to reduce bundle size. It also tries to make the formatting and usage consistent. **Why dayjs over date-fns?** Just because we were using dayjs more broadly. Its formatting capabilities, were also easier to extend.
1 parent a605c09 commit 5cdda2e

File tree

20 files changed

+241
-178
lines changed

20 files changed

+241
-178
lines changed

site/e2e/api.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import type { Page } from "@playwright/test";
22
import { expect } from "@playwright/test";
33
import { API, type DeploymentConfig } from "api/api";
44
import type { SerpentOption } from "api/typesGenerated";
5-
import { formatDuration, intervalToDuration } from "date-fns";
5+
import dayjs from "dayjs";
6+
import duration from "dayjs/plugin/duration";
7+
import relativeTime from "dayjs/plugin/relativeTime";
8+
9+
dayjs.extend(duration);
10+
dayjs.extend(relativeTime);
11+
import { humanDuration } from "utils/time";
612
import { coderPort, defaultPassword } from "./constants";
713
import { type LoginOptions, findSessionToken, randomName } from "./helpers";
814

@@ -237,13 +243,6 @@ export async function verifyConfigFlagString(
237243
await expect(configOption).toHaveText(opt.value as any);
238244
}
239245

240-
export async function verifyConfigFlagEmpty(page: Page, flag: string) {
241-
const configOption = page.locator(
242-
`div.options-table .option-${flag} .option-value-empty`,
243-
);
244-
await expect(configOption).toHaveText("Not set");
245-
}
246-
247246
export async function verifyConfigFlagArray(
248247
page: Page,
249248
config: DeploymentConfig,
@@ -290,19 +289,15 @@ export async function verifyConfigFlagDuration(
290289
flag: string,
291290
) {
292291
const opt = findConfigOption(config, flag);
292+
if (typeof opt.value !== "number") {
293+
throw new Error(
294+
`Option with env ${flag} should be a number, but got ${typeof opt.value}.`,
295+
);
296+
}
293297
const configOption = page.locator(
294298
`div.options-table .option-${flag} .option-value-string`,
295299
);
296-
//
297-
await expect(configOption).toHaveText(
298-
formatDuration(
299-
// intervalToDuration takes ms, so convert nanoseconds to ms
300-
intervalToDuration({
301-
start: 0,
302-
end: (opt.value as number) / 1e6,
303-
}),
304-
),
305-
);
300+
await expect(configOption).toHaveText(humanDuration(opt.value / 1e6));
306301
}
307302

308303
export function findConfigOption(

site/e2e/tests/deployment/observability.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
verifyConfigFlagArray,
66
verifyConfigFlagBoolean,
77
verifyConfigFlagDuration,
8-
verifyConfigFlagEmpty,
98
verifyConfigFlagString,
109
} from "../../api";
1110
import { login } from "../../helpers";
@@ -28,7 +27,11 @@ test("enabled observability settings", async ({ page }) => {
2827
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
2928
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
3029
await verifyConfigFlagDuration(page, config, "health-check-refresh");
31-
await verifyConfigFlagEmpty(page, "health-check-threshold-database");
30+
await verifyConfigFlagDuration(
31+
page,
32+
config,
33+
"health-check-threshold-database",
34+
);
3235
await verifyConfigFlagString(page, config, "log-human");
3336
await verifyConfigFlagString(page, config, "prometheus-address");
3437
await verifyConfigFlagArray(

site/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@
8585
"color-convert": "2.0.1",
8686
"cron-parser": "4.9.0",
8787
"cronstrue": "2.50.0",
88-
"date-fns": "2.30.0",
8988
"dayjs": "1.11.13",
9089
"emoji-mart": "5.6.0",
9190
"file-saver": "2.0.5",
9291
"formik": "2.4.6",
9392
"front-matter": "4.0.2",
93+
"humanize-duration": "3.32.2",
9494
"jszip": "3.10.1",
9595
"lodash": "4.17.21",
9696
"lucide-react": "0.474.0",
@@ -149,6 +149,7 @@
149149
"@types/color-convert": "2.0.4",
150150
"@types/express": "4.17.17",
151151
"@types/file-saver": "2.0.7",
152+
"@types/humanize-duration": "3.27.4",
152153
"@types/jest": "29.5.14",
153154
"@types/lodash": "4.17.15",
154155
"@types/node": "20.17.16",

site/pnpm-lock.yaml

Lines changed: 16 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/components/LastSeen/LastSeen.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { useTheme } from "@emotion/react";
22
import dayjs from "dayjs";
3-
import relativeTime from "dayjs/plugin/relativeTime";
43
import type { FC, HTMLAttributes } from "react";
54
import { cn } from "utils/cn";
6-
7-
dayjs.extend(relativeTime);
5+
import { isAfter, relativeTime, subtractTime } from "utils/time";
86

97
interface LastSeenProps
108
extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
@@ -15,21 +13,25 @@ interface LastSeenProps
1513
export const LastSeen: FC<LastSeenProps> = ({ at, className, ...attrs }) => {
1614
const theme = useTheme();
1715
const t = dayjs(at);
18-
const now = dayjs();
16+
const now = new Date();
17+
const oneHourAgo = subtractTime(now, 1, "hour");
18+
const threeDaysAgo = subtractTime(now, 3, "day");
19+
const oneMonthAgo = subtractTime(now, 1, "month");
20+
const centuryAgo = subtractTime(now, 100, "year");
1921

20-
let message = t.fromNow();
22+
let message = relativeTime(at);
2123
let color = theme.palette.text.secondary;
2224

23-
if (t.isAfter(now.subtract(1, "hour"))) {
25+
if (isAfter(at, oneHourAgo)) {
2426
// Since the agent reports on a 10m interval,
2527
// the last_used_at can be inaccurate when recent.
2628
message = "Now";
2729
color = theme.roles.success.fill.solid;
28-
} else if (t.isAfter(now.subtract(3, "day"))) {
30+
} else if (isAfter(at, threeDaysAgo)) {
2931
color = theme.experimental.l2.text;
30-
} else if (t.isAfter(now.subtract(1, "month"))) {
32+
} else if (isAfter(at, oneMonthAgo)) {
3133
color = theme.roles.warning.fill.solid;
32-
} else if (t.isAfter(now.subtract(100, "year"))) {
34+
} else if (isAfter(at, centuryAgo)) {
3335
color = theme.roles.error.fill.solid;
3436
} else {
3537
message = "Never";

site/src/components/Timeline/utils.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
import formatRelative from "date-fns/formatRelative";
2-
import subDays from "date-fns/subDays";
1+
import dayjs from "dayjs";
2+
import calendar from "dayjs/plugin/calendar";
3+
4+
dayjs.extend(calendar);
35

46
export const createDisplayDate = (
57
date: Date,
68
base: Date = new Date(),
79
): string => {
8-
const lastWeek = subDays(base, 7);
10+
const lastWeek = dayjs(base).subtract(7, "day").toDate();
911
if (date >= lastWeek) {
10-
return formatRelative(date, base).split(" at ")[0];
12+
return dayjs(date).calendar(dayjs(base), {
13+
sameDay: "[Today]",
14+
lastDay: "[Yesterday]",
15+
lastWeek: "[last] dddd",
16+
sameElse: "MM/DD/YYYY",
17+
});
1118
}
1219
return date.toLocaleDateString();
1320
};

site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import Tooltip from "@mui/material/Tooltip";
22
import type { Workspace } from "api/typesGenerated";
33
import { Badge } from "components/Badge/Badge";
4-
import { formatDistanceToNow } from "date-fns";
54
import type { FC } from "react";
5+
import {
6+
DATE_FORMAT,
7+
formatDateTime,
8+
relativeTimeWithoutSuffix,
9+
} from "utils/time";
610

711
export type WorkspaceDormantBadgeProps = {
812
workspace: Workspace;
@@ -11,25 +15,14 @@ export type WorkspaceDormantBadgeProps = {
1115
export const WorkspaceDormantBadge: FC<WorkspaceDormantBadgeProps> = ({
1216
workspace,
1317
}) => {
14-
const formatDate = (dateStr: string): string => {
15-
const date = new Date(dateStr);
16-
return date.toLocaleDateString(undefined, {
17-
month: "long",
18-
day: "numeric",
19-
year: "numeric",
20-
hour: "numeric",
21-
minute: "numeric",
22-
});
23-
};
24-
2518
return workspace.deleting_at ? (
2619
<Tooltip
2720
title={
2821
<>
2922
This workspace has not been used for{" "}
30-
{formatDistanceToNow(Date.parse(workspace.last_used_at))} and has been
23+
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
3124
marked dormant. It is scheduled to be deleted on{" "}
32-
{formatDate(workspace.deleting_at)}.
25+
{formatDateTime(workspace.deleting_at, DATE_FORMAT.FULL_DATETIME)}.
3326
</>
3427
}
3528
>
@@ -42,7 +35,7 @@ export const WorkspaceDormantBadge: FC<WorkspaceDormantBadgeProps> = ({
4235
title={
4336
<>
4437
This workspace has not been used for{" "}
45-
{formatDistanceToNow(Date.parse(workspace.last_used_at))} and has been
38+
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
4639
marked dormant. It is not scheduled for auto-deletion but will become
4740
a candidate if auto-deletion is enabled on this template.
4841
</>

site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseCard.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { GetLicensesResponse } from "api/api";
55
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
66
import { Pill } from "components/Pill/Pill";
77
import { Stack } from "components/Stack/Stack";
8-
import { compareAsc } from "date-fns";
98
import dayjs from "dayjs";
109
import { type FC, useState } from "react";
1110

@@ -92,10 +91,7 @@ export const LicenseCard: FC<LicenseCardProps> = ({
9291
alignItems="center"
9392
width="134px" // standardize width of date column
9493
>
95-
{compareAsc(
96-
new Date(license.claims.license_expires * 1000),
97-
new Date(),
98-
) < 1 ? (
94+
{dayjs(license.claims.license_expires * 1000).isBefore(dayjs()) ? (
9995
<Pill css={styles.expiredBadge} type="error">
10096
Expired
10197
</Pill>

site/src/pages/DeploymentSettingsPage/optionValue.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { SerpentOption } from "api/typesGenerated";
2-
import { formatDuration, intervalToDuration } from "date-fns";
2+
import { humanDuration } from "utils/time";
33

44
// optionValue is a helper function to format the value of a specific deployment options
55
export function optionValue(
@@ -14,13 +14,7 @@ export function optionValue(
1414
}
1515
switch (k) {
1616
case "format_duration":
17-
return formatDuration(
18-
// intervalToDuration takes ms, so convert nanoseconds to ms
19-
intervalToDuration({
20-
start: 0,
21-
end: (option.value as number) / 1e6,
22-
}),
23-
);
17+
return humanDuration((option.value as number) / 1e6);
2418
// Add additional cases here as needed.
2519
}
2620
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy