Skip to content

Commit 8f4746b

Browse files
authored
Update duration formatting to use Intl.DurationFormat (#857)
1 parent 0f7702e commit 8f4746b

26 files changed

+164
-293
lines changed

package-lock.json

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

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
},
3232
"homepage": "https://github.com/jenkinsci/pipeline-graph-view-plugin/#readme",
3333
"dependencies": {
34+
"@formatjs/intl-durationformat": "^0.7.4",
3435
"@tippyjs/react": "^4.2.6",
3536
"react": "^19.1.0",
3637
"react-dom": "^19.1.0",
@@ -56,6 +57,6 @@
5657
"vitest": "^3.1.2"
5758
},
5859
"engines": {
59-
"node": ">=22"
60+
"node": ">=23"
6061
}
6162
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<!-- Baseline Jenkins version you use to build the plugin. Users must have this version or newer to run. -->
3636
<jenkins.baseline>2.479</jenkins.baseline>
3737
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
38-
<node.version>22.15.0</node.version>
38+
<node.version>24.2.0</node.version>
3939
<npm.version>11.3.0</npm.version>
4040
<spotless.check.skip>false</spotless.check.skip>
4141
</properties>

src/main/frontend/common/i18n/i18n-provider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
useState,
99
} from "react";
1010

11-
import { DEFAULT_LOCALE, LocaleContext } from "./locale-provider.tsx";
11+
import { DEFAULT_LOCALE, useLocale } from "./locale-provider.tsx";
1212
import {
1313
defaultMessages,
1414
getMessages,
@@ -29,7 +29,7 @@ export const I18NProvider: FunctionComponent<I18NProviderProps> = ({
2929
children,
3030
bundles,
3131
}) => {
32-
const locale = useContext(LocaleContext);
32+
const locale = useLocale();
3333

3434
const [messages, setMessages] = useState<Messages>(defaultMessages(locale));
3535

src/main/frontend/common/i18n/locale-provider.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { Context, createContext, FunctionComponent, ReactNode } from "react";
1+
import {
2+
Context,
3+
createContext,
4+
FunctionComponent,
5+
ReactNode,
6+
useContext,
7+
} from "react";
28

39
export const DEFAULT_LOCALE = "en";
4-
export const LocaleContext: Context<string> = createContext(DEFAULT_LOCALE);
10+
const LocaleContext: Context<string> = createContext(DEFAULT_LOCALE);
511

612
interface LocaleProviderProps {
713
children: ReactNode;
@@ -18,3 +24,7 @@ export const LocaleProvider: FunctionComponent<LocaleProviderProps> = ({
1824
</LocaleContext.Provider>
1925
);
2026
};
27+
28+
export const useLocale = (): string => {
29+
return useContext(LocaleContext);
30+
};

src/main/frontend/common/i18n/messages.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,9 @@ describe("Messages", () => {
5858

5959
const messages = await getMessages("en", [ResourceBundleName.messages]);
6060

61-
expect(messages.format(LocalizedMessageKey.second, { 0: 5 })).toEqual(
62-
"5 sec",
63-
);
64-
expect(messages.format(LocalizedMessageKey.day, { 0: 1 })).toEqual(
65-
"1 day",
66-
);
67-
expect(messages.format(LocalizedMessageKey.day, { 0: 2 })).toEqual(
68-
"2 days",
69-
);
61+
expect(
62+
messages.format(LocalizedMessageKey.startedAgo, { 0: "5s" }),
63+
).toEqual("Started 5s ago");
7064
expect(messages.format("A.property")).toEqual("");
7165
});
7266
});

src/main/frontend/common/i18n/messages.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,8 @@ export async function getMessages(
6969
export type MessageKeyType = LocalizedMessageKey | string;
7070

7171
export enum LocalizedMessageKey {
72-
millisecond = "timings.millisecond",
73-
second = "timings.second",
74-
minute = "timings.minute",
75-
hour = "timings.hour",
76-
day = "timings.day",
77-
month = "timings.month",
78-
year = "timings.year",
7972
startedAgo = "startedAgo",
73+
queued = "queued",
8074
noBuilds = "noBuilds",
8175
start = "node.start",
8276
end = "node.end",
@@ -88,14 +82,8 @@ export enum LocalizedMessageKey {
8882
}
8983

9084
const DEFAULT_MESSAGES: ResourceBundle = {
91-
[LocalizedMessageKey.millisecond]: "{0} ms",
92-
[LocalizedMessageKey.second]: "{0} sec",
93-
[LocalizedMessageKey.minute]: "{0} min",
94-
[LocalizedMessageKey.hour]: "{0} hr",
95-
[LocalizedMessageKey.day]: "{0} {0,choice,0#days|1#day|1<days}",
96-
[LocalizedMessageKey.month]: "{0} mo",
97-
[LocalizedMessageKey.year]: "{0} yr",
9885
[LocalizedMessageKey.startedAgo]: "Started {0} ago",
86+
[LocalizedMessageKey.queued]: "Queued {0}",
9987
[LocalizedMessageKey.noBuilds]: "No builds",
10088
[LocalizedMessageKey.start]: "Start",
10189
[LocalizedMessageKey.end]: "End",

src/main/frontend/common/utils/timings.spec.tsx

Lines changed: 26 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,56 @@
33
import { render } from "@testing-library/react";
44
import { vi } from "vitest";
55

6-
import { I18NContext, LocalizedMessageKey, Messages } from "../i18n/index.ts";
76
import { Paused, Started, Total } from "./timings.tsx";
87

98
describe("Timings", () => {
10-
const translations = new Messages(
11-
{
12-
[LocalizedMessageKey.year]: "{0} yr",
13-
[LocalizedMessageKey.month]: "{0} mo",
14-
[LocalizedMessageKey.day]: "{0} day",
15-
[LocalizedMessageKey.hour]: "{0} hr",
16-
[LocalizedMessageKey.minute]: "{0} min",
17-
[LocalizedMessageKey.second]: "{0} sec",
18-
[LocalizedMessageKey.millisecond]: "{0} ms",
19-
[LocalizedMessageKey.startedAgo]: "Started {0} ago",
20-
},
21-
"en",
22-
);
23-
24-
function process(child: any) {
25-
return render(
26-
<I18NContext.Provider value={translations}>{child}</I18NContext.Provider>,
27-
);
28-
}
29-
309
describe("Total", () => {
3110
function getTotal(ms: number) {
32-
return process(<Total ms={ms} />);
11+
return render(<Total ms={ms} />);
3312
}
3413

3514
it("should format milliseconds to hours, minutes, and seconds", () => {
3615
// First check 359 days.
37-
expect(getTotal(31_017_600_000).getByText("11 mo")).toBeInTheDocument();
16+
expect(getTotal(31_017_600_000).getByText("11 mths")).toBeInTheDocument();
3817
// And 362 days.
39-
expect(getTotal(31_276_800_000).getByText("12 mo")).toBeInTheDocument();
18+
expect(getTotal(31_276_800_000).getByText("12 mths")).toBeInTheDocument();
4019
// 11.25 years - Check that if the first unit has 2 or more digits, a second unit isn't used.
41-
expect(getTotal(354_780_000_000).getByText("11 yr")).toBeInTheDocument();
20+
expect(getTotal(354_780_000_000).getByText("11y")).toBeInTheDocument();
4221
// 9.25 years - Check that if the first unit has only 1 digit, a second unit is used.
43-
expect(
44-
getTotal(291_708_000_000).getByText("9 yr 3 mo"),
45-
).toBeInTheDocument();
22+
expect(getTotal(291_708_000_000).getByText("9y 3m")).toBeInTheDocument();
4623
// 3 months 14 days
47-
expect(
48-
getTotal(8_985_600_000).getByText("3 mo 14 day"),
49-
).toBeInTheDocument();
24+
expect(getTotal(8_985_600_000).getByText("3m 14d")).toBeInTheDocument();
5025
// 2 day 4 hours
51-
expect(getTotal(187_200_000).getByText("2 day 4 hr")).toBeInTheDocument();
26+
expect(getTotal(187_200_000).getByText("2d 4h")).toBeInTheDocument();
5227
// 8 hours 46 minutes
53-
expect(getTotal(31_560_000).getByText("8 hr 46 min")).toBeInTheDocument();
28+
expect(getTotal(31_560_000).getByText("8h 46m")).toBeInTheDocument();
5429
// 67 seconds -> 1 minute 7 seconds
55-
expect(getTotal(67_000).getByText("1 min 7 sec")).toBeInTheDocument();
30+
expect(getTotal(67_000).getByText("1m 7s")).toBeInTheDocument();
5631
// 17 seconds - Check that times less than a minute only use seconds.
57-
expect(getTotal(17_000).getByText("17 sec")).toBeInTheDocument();
32+
expect(getTotal(17_000).getByText("17s")).toBeInTheDocument();
5833
// 1712ms -> 1.7sec
59-
expect(getTotal(1_712).getByText("1.7 sec")).toBeInTheDocument();
34+
expect(getTotal(1_712).getByText("1.7s")).toBeInTheDocument();
6035
// 171ms -> 0.17sec
61-
expect(getTotal(171).getByText("0.17 sec")).toBeInTheDocument();
62-
// 101ms -> 0.10sec
63-
expect(getTotal(101).getByText("0.1 sec")).toBeInTheDocument();
36+
expect(getTotal(171).getByText("0.17s")).toBeInTheDocument();
37+
// 101ms -> 0.1sec
38+
expect(getTotal(101).getByText("0.1s")).toBeInTheDocument();
6439
// 17ms
65-
expect(getTotal(17).getByText("17 ms")).toBeInTheDocument();
40+
expect(getTotal(17).getByText("17ms")).toBeInTheDocument();
6641
// 1ms
67-
expect(getTotal(1).getByText("1 ms")).toBeInTheDocument();
42+
expect(getTotal(1).getByText("1ms")).toBeInTheDocument();
6843
});
6944
});
7045

7146
describe("paused", () => {
7247
function getPaused(since: number) {
73-
return process(<Paused since={since} />);
48+
return render(<Paused since={since} />);
7449
}
7550

7651
it("should prefix the time with Queued", () => {
77-
expect(getPaused(1000).getByText("Queued 1 sec")).toBeInTheDocument();
78-
expect(getPaused(100).getByText("Queued 0.1 sec")).toBeInTheDocument();
79-
expect(getPaused(10).getByText("Queued 10 ms")).toBeInTheDocument();
80-
expect(getPaused(1).getByText("Queued 1 ms")).toBeInTheDocument();
52+
expect(getPaused(1000).getByText("Queued 1s")).toBeInTheDocument();
53+
expect(getPaused(100).getByText("Queued 0.1s")).toBeInTheDocument();
54+
expect(getPaused(10).getByText("Queued 10ms")).toBeInTheDocument();
55+
expect(getPaused(1).getByText("Queued 1ms")).toBeInTheDocument();
8156
});
8257
});
8358

@@ -93,7 +68,7 @@ describe("Timings", () => {
9368
});
9469

9570
function getStarted(since: number) {
96-
return process(<Started since={since} />);
71+
return render(<Started since={since} />);
9772
}
9873

9974
it("should return empty element if since is 0", () => {
@@ -102,16 +77,16 @@ describe("Timings", () => {
10277

10378
it("should prefix the time with Started and end with ago", () => {
10479
expect(
105-
getStarted(now - 1000).getByText("Started 1 sec ago"),
80+
getStarted(now - 1000).getByText("Started 1s ago"),
10681
).toBeInTheDocument();
10782
expect(
108-
getStarted(now - 100).getByText("Started 0.1 sec ago"),
83+
getStarted(now - 100).getByText("Started 0.1s ago"),
10984
).toBeInTheDocument();
11085
expect(
111-
getStarted(now - 10).getByText("Started 10 ms ago"),
86+
getStarted(now - 10).getByText("Started 10ms ago"),
11287
).toBeInTheDocument();
11388
expect(
114-
getStarted(now - 1).getByText("Started 1 ms ago"),
89+
getStarted(now - 1).getByText("Started 1ms ago"),
11590
).toBeInTheDocument();
11691
});
11792
});

0 commit comments

Comments
 (0)