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;
|
||||
};
|
||||
|
||||
// Format a duration in milliseconds into a Prometheus duration string like "1d2h3m4s".
|
||||
export const formatPrometheusDuration = (d: number): string => {
|
||||
let ms = d;
|
||||
let r = "";
|
||||
if (ms === 0) {
|
||||
// Used by:
|
||||
// - formatPrometheusDuration() => "5d5m2s123ms"
|
||||
// - humanizeDuration() => "5d 5m 2.123s"
|
||||
const formatDuration = (
|
||||
d: number,
|
||||
componentSeparator?: string,
|
||||
showFractionalSeconds?: boolean
|
||||
): string => {
|
||||
if (d === 0) {
|
||||
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) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const v = Math.floor(ms / mult);
|
||||
if (v > 0) {
|
||||
r += `${v}${unit}`;
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sign + r.join(componentSeparator || "");
|
||||
};
|
||||
|
||||
// Only format years and weeks if the remainder is zero, as it is often
|
||||
// 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);
|
||||
f("h", 1000 * 60 * 60, false);
|
||||
f("m", 1000 * 60, false);
|
||||
f("s", 1000, false);
|
||||
f("ms", 1, false);
|
||||
|
||||
return r;
|
||||
// Format a duration in milliseconds into a Prometheus duration string like "1d2h3m4s".
|
||||
export const formatPrometheusDuration = (d: number): string => {
|
||||
return formatDuration(d);
|
||||
};
|
||||
|
||||
export function parseTime(timeText: string): number {
|
||||
|
@ -85,37 +103,7 @@ export function parseTime(timeText: string): number {
|
|||
export const now = (): number => dayjs().valueOf();
|
||||
|
||||
export const humanizeDuration = (milliseconds: number): string => {
|
||||
if (milliseconds === 0) {
|
||||
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(" ");
|
||||
return formatDuration(milliseconds, " ", true);
|
||||
};
|
||||
|
||||
export const humanizeDurationRelative = (
|
||||
|
|
|
@ -99,7 +99,7 @@ const MetricsExplorer: FC<MetricsExplorerProps> = ({
|
|||
{items.map((m) => (
|
||||
<Table.Tr key={m.original}>
|
||||
<Table.Td>
|
||||
<Group justify="space-between">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
{debouncedFilterText === "" ? (
|
||||
m.original
|
||||
) : (
|
||||
|
|
|
@ -305,10 +305,9 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th w="30%">Endpoint</Table.Th>
|
||||
<Table.Th w="25%">Endpoint</Table.Th>
|
||||
<Table.Th>Labels</Table.Th>
|
||||
<Table.Th w="10%">Last scrape</Table.Th>
|
||||
{/* <Table.Th w="10%">Scrape duration</Table.Th> */}
|
||||
<Table.Th w={230}>Last scrape</Table.Th>
|
||||
<Table.Th w={100}>State</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
|
@ -337,17 +336,12 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
/>
|
||||
</Table.Td>
|
||||
<Table.Td valign="top">
|
||||
<Group
|
||||
gap="xs"
|
||||
wrap="wrap"
|
||||
justify="space-between"
|
||||
>
|
||||
<Group gap="xs" wrap="wrap">
|
||||
<Tooltip
|
||||
label="Last target scrape"
|
||||
withArrow
|
||||
>
|
||||
<Badge
|
||||
w="max-content"
|
||||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{
|
||||
|
@ -369,7 +363,6 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
withArrow
|
||||
>
|
||||
<Badge
|
||||
w="max-content"
|
||||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{
|
||||
|
@ -390,7 +383,6 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
</Table.Td>
|
||||
<Table.Td valign="top">
|
||||
<Badge
|
||||
w="max-content"
|
||||
className={healthBadgeClass(target.health)}
|
||||
>
|
||||
{target.health}
|
||||
|
|
Loading…
Reference in a new issue