mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Merge pull request #14914 from prometheus/julius/new-ui-improvements
New UI: Better time formatting + tests, better styling
This commit is contained in:
commit
2c87817e40
104
web/ui/mantine-ui/src/lib/formatTime.test.ts
Normal file
104
web/ui/mantine-ui/src/lib/formatTime.test.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { humanizeDuration, formatPrometheusDuration } from "./formatTime";
|
||||||
|
|
||||||
|
describe("formatPrometheusDuration", () => {
|
||||||
|
test('returns "0s" for 0 milliseconds', () => {
|
||||||
|
expect(formatPrometheusDuration(0)).toBe("0s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats milliseconds correctly", () => {
|
||||||
|
expect(formatPrometheusDuration(1)).toBe("1ms");
|
||||||
|
expect(formatPrometheusDuration(999)).toBe("999ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats seconds correctly", () => {
|
||||||
|
expect(formatPrometheusDuration(1000)).toBe("1s");
|
||||||
|
expect(formatPrometheusDuration(1500)).toBe("1s500ms");
|
||||||
|
expect(formatPrometheusDuration(59999)).toBe("59s999ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats minutes correctly", () => {
|
||||||
|
expect(formatPrometheusDuration(60000)).toBe("1m");
|
||||||
|
expect(formatPrometheusDuration(120000)).toBe("2m");
|
||||||
|
expect(formatPrometheusDuration(3599999)).toBe("59m59s999ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats hours correctly", () => {
|
||||||
|
expect(formatPrometheusDuration(3600000)).toBe("1h");
|
||||||
|
expect(formatPrometheusDuration(7200000)).toBe("2h");
|
||||||
|
expect(formatPrometheusDuration(86399999)).toBe("23h59m59s999ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats days correctly", () => {
|
||||||
|
expect(formatPrometheusDuration(86400000)).toBe("1d");
|
||||||
|
expect(formatPrometheusDuration(172800000)).toBe("2d");
|
||||||
|
expect(formatPrometheusDuration(86400000 * 365 - 1)).toBe(
|
||||||
|
"364d23h59m59s999ms"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles negative durations", () => {
|
||||||
|
expect(formatPrometheusDuration(-1000)).toBe("-1s");
|
||||||
|
expect(formatPrometheusDuration(-86400000)).toBe("-1d");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("combines multiple units correctly", () => {
|
||||||
|
expect(
|
||||||
|
formatPrometheusDuration(86400000 + 3600000 + 60000 + 1000 + 1)
|
||||||
|
).toBe("1d1h1m1s1ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits zero values", () => {
|
||||||
|
expect(formatPrometheusDuration(86400000 + 1000)).toBe("1d1s");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("humanizeDuration", () => {
|
||||||
|
test('returns "0s" for 0 milliseconds', () => {
|
||||||
|
expect(humanizeDuration(0)).toBe("0s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats milliseconds correctly", () => {
|
||||||
|
expect(humanizeDuration(1)).toBe("1ms");
|
||||||
|
expect(humanizeDuration(999)).toBe("999ms");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats seconds correctly", () => {
|
||||||
|
expect(humanizeDuration(1000)).toBe("1s");
|
||||||
|
expect(humanizeDuration(1500)).toBe("1.5s");
|
||||||
|
expect(humanizeDuration(59999)).toBe("59.999s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats minutes correctly", () => {
|
||||||
|
expect(humanizeDuration(60000)).toBe("1m");
|
||||||
|
expect(humanizeDuration(120000)).toBe("2m");
|
||||||
|
expect(humanizeDuration(3599999)).toBe("59m 59.999s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats hours correctly", () => {
|
||||||
|
expect(humanizeDuration(3600000)).toBe("1h");
|
||||||
|
expect(humanizeDuration(7200000)).toBe("2h");
|
||||||
|
expect(humanizeDuration(86399999)).toBe("23h 59m 59.999s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("formats days correctly", () => {
|
||||||
|
expect(humanizeDuration(86400000)).toBe("1d");
|
||||||
|
expect(humanizeDuration(172800000)).toBe("2d");
|
||||||
|
expect(humanizeDuration(86400000 * 365 - 1)).toBe("364d 23h 59m 59.999s");
|
||||||
|
expect(humanizeDuration(86400000 * 365 - 1)).toBe("364d 23h 59m 59.999s");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles negative durations", () => {
|
||||||
|
expect(humanizeDuration(-1000)).toBe("-1s");
|
||||||
|
expect(humanizeDuration(-86400000)).toBe("-1d");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("combines multiple units correctly", () => {
|
||||||
|
expect(humanizeDuration(86400000 + 3600000 + 60000 + 1000 + 1)).toBe(
|
||||||
|
"1d 1h 1m 1.001s"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits zero values", () => {
|
||||||
|
expect(humanizeDuration(86400000 + 1000)).toBe("1d 1s");
|
||||||
|
});
|
||||||
|
});
|
|
@ -45,37 +45,55 @@ export const parsePrometheusDuration = (durationStr: string): number | null => {
|
||||||
return dur;
|
return dur;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format a duration in milliseconds into a Prometheus duration string like "1d2h3m4s".
|
// Used by:
|
||||||
export const formatPrometheusDuration = (d: number): string => {
|
// - formatPrometheusDuration() => "5d5m2s123ms"
|
||||||
let ms = d;
|
// - humanizeDuration() => "5d 5m 2.123s"
|
||||||
let r = "";
|
const formatDuration = (
|
||||||
if (ms === 0) {
|
d: number,
|
||||||
|
componentSeparator?: string,
|
||||||
|
showFractionalSeconds?: boolean
|
||||||
|
): string => {
|
||||||
|
if (d === 0) {
|
||||||
return "0s";
|
return "0s";
|
||||||
}
|
}
|
||||||
|
|
||||||
const f = (unit: string, mult: number, exact: boolean) => {
|
const sign = d < 0 ? "-" : "";
|
||||||
|
let ms = Math.abs(d);
|
||||||
|
const r: string[] = [];
|
||||||
|
|
||||||
|
for (const { unit, mult, exact } of [
|
||||||
|
// Only format years and weeks if the remainder is zero, as it is often
|
||||||
|
// easier to read 90d than 12w6d.
|
||||||
|
{ unit: "y", mult: 1000 * 60 * 60 * 24 * 365, exact: true },
|
||||||
|
{ unit: "w", mult: 1000 * 60 * 60 * 24 * 7, exact: true },
|
||||||
|
{ unit: "d", mult: 1000 * 60 * 60 * 24, exact: false },
|
||||||
|
{ unit: "h", mult: 1000 * 60 * 60, exact: false },
|
||||||
|
{ unit: "m", mult: 1000 * 60, exact: false },
|
||||||
|
{ unit: "s", mult: 1000, exact: false },
|
||||||
|
{ unit: "ms", mult: 1, exact: false },
|
||||||
|
]) {
|
||||||
if (exact && ms % mult !== 0) {
|
if (exact && ms % mult !== 0) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
const v = Math.floor(ms / mult);
|
const v = Math.floor(ms / mult);
|
||||||
if (v > 0) {
|
if (v > 0) {
|
||||||
r += `${v}${unit}`;
|
|
||||||
ms -= v * mult;
|
ms -= v * mult;
|
||||||
|
if (showFractionalSeconds && unit === "s" && ms > 0) {
|
||||||
|
// Show "2.34s" instead of "2s 340ms".
|
||||||
|
r.push(`${parseFloat((v + ms / 1000).toFixed(3))}s`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
r.push(`${v}${unit}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Only format years and weeks if the remainder is zero, as it is often
|
return sign + r.join(componentSeparator || "");
|
||||||
// easier to read 90d than 12w6d.
|
};
|
||||||
f("y", 1000 * 60 * 60 * 24 * 365, true);
|
|
||||||
f("w", 1000 * 60 * 60 * 24 * 7, true);
|
|
||||||
|
|
||||||
f("d", 1000 * 60 * 60 * 24, false);
|
// Format a duration in milliseconds into a Prometheus duration string like "1d2h3m4s".
|
||||||
f("h", 1000 * 60 * 60, false);
|
export const formatPrometheusDuration = (d: number): string => {
|
||||||
f("m", 1000 * 60, false);
|
return formatDuration(d);
|
||||||
f("s", 1000, false);
|
|
||||||
f("ms", 1, false);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseTime(timeText: string): number {
|
export function parseTime(timeText: string): number {
|
||||||
|
@ -85,37 +103,7 @@ export function parseTime(timeText: string): number {
|
||||||
export const now = (): number => dayjs().valueOf();
|
export const now = (): number => dayjs().valueOf();
|
||||||
|
|
||||||
export const humanizeDuration = (milliseconds: number): string => {
|
export const humanizeDuration = (milliseconds: number): string => {
|
||||||
if (milliseconds === 0) {
|
return formatDuration(milliseconds, " ", true);
|
||||||
return "0s";
|
|
||||||
}
|
|
||||||
|
|
||||||
const sign = milliseconds < 0 ? "-" : "";
|
|
||||||
const duration = dayjs.duration(Math.abs(milliseconds), "ms");
|
|
||||||
const ms = Math.floor(duration.milliseconds());
|
|
||||||
const s = Math.floor(duration.seconds());
|
|
||||||
const m = Math.floor(duration.minutes());
|
|
||||||
const h = Math.floor(duration.hours());
|
|
||||||
const d = Math.floor(duration.asDays());
|
|
||||||
const parts: string[] = [];
|
|
||||||
if (d !== 0) {
|
|
||||||
parts.push(`${d}d`);
|
|
||||||
}
|
|
||||||
if (h !== 0) {
|
|
||||||
parts.push(`${h}h`);
|
|
||||||
}
|
|
||||||
if (m !== 0) {
|
|
||||||
parts.push(`${m}m`);
|
|
||||||
}
|
|
||||||
if (s !== 0) {
|
|
||||||
if (ms !== 0) {
|
|
||||||
parts.push(`${s}.${ms}s`);
|
|
||||||
} else {
|
|
||||||
parts.push(`${s}s`);
|
|
||||||
}
|
|
||||||
} else if (milliseconds !== 0) {
|
|
||||||
parts.push(`${milliseconds.toFixed(3)}ms`);
|
|
||||||
}
|
|
||||||
return sign + parts.join(" ");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const humanizeDurationRelative = (
|
export const humanizeDurationRelative = (
|
||||||
|
|
|
@ -99,7 +99,7 @@ const MetricsExplorer: FC<MetricsExplorerProps> = ({
|
||||||
{items.map((m) => (
|
{items.map((m) => (
|
||||||
<Table.Tr key={m.original}>
|
<Table.Tr key={m.original}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
{debouncedFilterText === "" ? (
|
{debouncedFilterText === "" ? (
|
||||||
m.original
|
m.original
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -305,10 +305,9 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
||||||
<Table>
|
<Table>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th w="30%">Endpoint</Table.Th>
|
<Table.Th w="25%">Endpoint</Table.Th>
|
||||||
<Table.Th>Labels</Table.Th>
|
<Table.Th>Labels</Table.Th>
|
||||||
<Table.Th w="10%">Last scrape</Table.Th>
|
<Table.Th w={230}>Last scrape</Table.Th>
|
||||||
{/* <Table.Th w="10%">Scrape duration</Table.Th> */}
|
|
||||||
<Table.Th w={100}>State</Table.Th>
|
<Table.Th w={100}>State</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
|
@ -337,17 +336,12 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
||||||
/>
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td valign="top">
|
<Table.Td valign="top">
|
||||||
<Group
|
<Group gap="xs" wrap="wrap">
|
||||||
gap="xs"
|
|
||||||
wrap="wrap"
|
|
||||||
justify="space-between"
|
|
||||||
>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="Last target scrape"
|
label="Last target scrape"
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
w="max-content"
|
|
||||||
variant="light"
|
variant="light"
|
||||||
className={badgeClasses.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{
|
styles={{
|
||||||
|
@ -369,7 +363,6 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
w="max-content"
|
|
||||||
variant="light"
|
variant="light"
|
||||||
className={badgeClasses.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{
|
styles={{
|
||||||
|
@ -390,7 +383,6 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td valign="top">
|
<Table.Td valign="top">
|
||||||
<Badge
|
<Badge
|
||||||
w="max-content"
|
|
||||||
className={healthBadgeClass(target.health)}
|
className={healthBadgeClass(target.health)}
|
||||||
>
|
>
|
||||||
{target.health}
|
{target.health}
|
||||||
|
|
Loading…
Reference in a new issue