feat(integrations): add PatchMon integration and widget (#6119)
Co-authored-by: Thomas <thomas@ajnart.fr>
This commit is contained in:
1 parent
174c5f98f8
commit
33395ae226
41 files changed
+2982
-17
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getGridCols, shouldShowComplianceHeroText } from "../layout-utils";
|
||||
|
||||
describe("getGridCols", () => {
|
||||
test("returns 1 column for narrow widths", () => {
|
||||
expect(getGridCols(128)).toBe(1);
|
||||
expect(getGridCols(219)).toBe(1);
|
||||
});
|
||||
|
||||
test("returns 2 columns at 220px breakpoint", () => {
|
||||
expect(getGridCols(220)).toBe(2);
|
||||
expect(getGridCols(256)).toBe(2);
|
||||
});
|
||||
|
||||
test("returns 3 columns at 380px breakpoint", () => {
|
||||
expect(getGridCols(380)).toBe(3);
|
||||
});
|
||||
|
||||
test("returns 4 columns at 500px breakpoint", () => {
|
||||
expect(getGridCols(500)).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldShowComplianceHeroText", () => {
|
||||
test("returns false for 1-column-wide layouts", () => {
|
||||
expect(shouldShowComplianceHeroText(128)).toBe(false);
|
||||
expect(shouldShowComplianceHeroText(219)).toBe(false);
|
||||
});
|
||||
|
||||
test("returns true for 2+-column-wide layouts", () => {
|
||||
expect(shouldShowComplianceHeroText(220)).toBe(true);
|
||||
expect(shouldShowComplianceHeroText(256)).toBe(true);
|
||||
expect(shouldShowComplianceHeroText(384)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
import { resolveStatColor } from "../stat-colors";
|
||||
|
||||
const defaultOptions = {
|
||||
enableThresholdColors: true,
|
||||
useCustomThresholds: false,
|
||||
showTotalHosts: true,
|
||||
showHostsNeedingUpdates: true,
|
||||
showSecurityUpdates: true,
|
||||
showUpToDateHosts: false,
|
||||
showHostsWithSecurityUpdates: false,
|
||||
showRecentUpdates24h: false,
|
||||
showTotalOutdatedPackages: false,
|
||||
showTotalRepos: false,
|
||||
showOsDistribution: false,
|
||||
osDistributionLimit: "0",
|
||||
showOsVersion: true,
|
||||
hostsNeedingUpdatesThresholdMode: "absolute",
|
||||
hostsNeedingUpdatesWarningAt: 1,
|
||||
hostsNeedingUpdatesCriticalAt: 5,
|
||||
securityUpdatesThresholdMode: "absolute",
|
||||
securityUpdatesWarningAt: 1,
|
||||
securityUpdatesCriticalAt: 10,
|
||||
hostsWithSecurityUpdatesThresholdMode: "absolute",
|
||||
hostsWithSecurityUpdatesWarningAt: 1,
|
||||
hostsWithSecurityUpdatesCriticalAt: 1,
|
||||
upToDateHostsThresholdMode: "percent",
|
||||
upToDateHostsWarningAt: 90,
|
||||
upToDateHostsCriticalAt: 90,
|
||||
totalOutdatedPackagesThresholdMode: "absolute",
|
||||
totalOutdatedPackagesWarningAt: 1,
|
||||
totalOutdatedPackagesCriticalAt: 50,
|
||||
showComplianceHero: true,
|
||||
osDisplayMode: "bars",
|
||||
} satisfies WidgetComponentProps<"patchmon">["options"];
|
||||
|
||||
const context = { totalHosts: 100 };
|
||||
|
||||
describe("resolveStatColor presets", () => {
|
||||
test("hostsNeedingUpdates uses green, yellow, and red boundaries", () => {
|
||||
expect(resolveStatColor("hostsNeedingUpdates", 0, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("hostsNeedingUpdates", 1, context, defaultOptions)).toBe("yellow");
|
||||
expect(resolveStatColor("hostsNeedingUpdates", 4, context, defaultOptions)).toBe("yellow");
|
||||
expect(resolveStatColor("hostsNeedingUpdates", 5, context, defaultOptions)).toBe("red");
|
||||
});
|
||||
|
||||
test("securityUpdates uses green and yellow for low counts", () => {
|
||||
expect(resolveStatColor("securityUpdates", 0, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("securityUpdates", 1, context, defaultOptions)).toBe("yellow");
|
||||
expect(resolveStatColor("securityUpdates", 10, context, defaultOptions)).toBe("red");
|
||||
});
|
||||
|
||||
test("hostsWithSecurityUpdates skips yellow", () => {
|
||||
expect(resolveStatColor("hostsWithSecurityUpdates", 0, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("hostsWithSecurityUpdates", 1, context, defaultOptions)).toBe("red");
|
||||
});
|
||||
|
||||
test("upToDateHosts uses percent of total hosts", () => {
|
||||
expect(resolveStatColor("upToDateHosts", 100, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("upToDateHosts", 95, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("upToDateHosts", 90, context, defaultOptions)).toBe("green");
|
||||
expect(resolveStatColor("upToDateHosts", 80, context, defaultOptions)).toBe("red");
|
||||
});
|
||||
|
||||
test("neutral stats ignore threshold coloring", () => {
|
||||
expect(resolveStatColor("totalHosts", 100, context, defaultOptions)).toBe("neutral");
|
||||
expect(resolveStatColor("totalRepos", 12, context, defaultOptions)).toBe("neutral");
|
||||
expect(resolveStatColor("recentUpdates24h", 34, context, defaultOptions)).toBe("neutral");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveStatColor options", () => {
|
||||
test("returns neutral when threshold colors are disabled", () => {
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 10, context, {
|
||||
...defaultOptions,
|
||||
enableThresholdColors: false,
|
||||
}),
|
||||
).toBe("neutral");
|
||||
});
|
||||
|
||||
test("supports custom absolute thresholds", () => {
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 4, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesWarningAt: 5,
|
||||
hostsNeedingUpdatesCriticalAt: 10,
|
||||
}),
|
||||
).toBe("green");
|
||||
});
|
||||
|
||||
test("supports custom percent thresholds for lower-is-better stats", () => {
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 5, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesThresholdMode: "percent",
|
||||
hostsNeedingUpdatesWarningAt: 10,
|
||||
hostsNeedingUpdatesCriticalAt: 20,
|
||||
}),
|
||||
).toBe("green");
|
||||
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 15, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesThresholdMode: "percent",
|
||||
hostsNeedingUpdatesWarningAt: 10,
|
||||
hostsNeedingUpdatesCriticalAt: 20,
|
||||
}),
|
||||
).toBe("yellow");
|
||||
});
|
||||
|
||||
test("handles zero total hosts for percent-based stats", () => {
|
||||
expect(resolveStatColor("upToDateHosts", 0, { totalHosts: 0 }, defaultOptions)).toBe("green");
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 1, { totalHosts: 0 }, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesThresholdMode: "percent",
|
||||
}),
|
||||
).toBe("red");
|
||||
});
|
||||
|
||||
test("clamps invalid percent thresholds when resolving colors", () => {
|
||||
expect(
|
||||
resolveStatColor("hostsNeedingUpdates", 50, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesThresholdMode: "percent",
|
||||
hostsNeedingUpdatesWarningAt: 40,
|
||||
hostsNeedingUpdatesCriticalAt: 200,
|
||||
}),
|
||||
).toBe("yellow");
|
||||
});
|
||||
|
||||
test("supports custom absolute thresholds for higher-is-better stats", () => {
|
||||
expect(
|
||||
resolveStatColor("upToDateHosts", 85, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
upToDateHostsThresholdMode: "absolute",
|
||||
upToDateHostsWarningAt: 80,
|
||||
upToDateHostsCriticalAt: 60,
|
||||
}),
|
||||
).toBe("green");
|
||||
|
||||
expect(
|
||||
resolveStatColor("upToDateHosts", 70, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
upToDateHostsThresholdMode: "absolute",
|
||||
upToDateHostsWarningAt: 80,
|
||||
upToDateHostsCriticalAt: 60,
|
||||
}),
|
||||
).toBe("yellow");
|
||||
|
||||
expect(
|
||||
resolveStatColor("upToDateHosts", 50, context, {
|
||||
...defaultOptions,
|
||||
useCustomThresholds: true,
|
||||
upToDateHostsThresholdMode: "absolute",
|
||||
upToDateHostsWarningAt: 80,
|
||||
upToDateHostsCriticalAt: 60,
|
||||
}),
|
||||
).toBe("red");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { clampPercentThreshold, patchmonOptionsSuperRefine } from "../threshold-validation";
|
||||
|
||||
const baseOptions = {
|
||||
enableThresholdColors: true,
|
||||
useCustomThresholds: true,
|
||||
hostsNeedingUpdatesThresholdMode: "percent",
|
||||
hostsNeedingUpdatesWarningAt: 10,
|
||||
hostsNeedingUpdatesCriticalAt: 20,
|
||||
securityUpdatesThresholdMode: "absolute",
|
||||
securityUpdatesWarningAt: 1,
|
||||
securityUpdatesCriticalAt: 10,
|
||||
hostsWithSecurityUpdatesThresholdMode: "absolute",
|
||||
hostsWithSecurityUpdatesWarningAt: 1,
|
||||
hostsWithSecurityUpdatesCriticalAt: 1,
|
||||
upToDateHostsThresholdMode: "percent",
|
||||
upToDateHostsWarningAt: 90,
|
||||
upToDateHostsCriticalAt: 90,
|
||||
totalOutdatedPackagesThresholdMode: "absolute",
|
||||
totalOutdatedPackagesWarningAt: 1,
|
||||
totalOutdatedPackagesCriticalAt: 50,
|
||||
};
|
||||
|
||||
const optionsSchema = z.object({
|
||||
hostsNeedingUpdatesWarningAt: z.number().int().min(0),
|
||||
hostsNeedingUpdatesCriticalAt: z.number().int().min(0),
|
||||
securityUpdatesWarningAt: z.number().int().min(0),
|
||||
securityUpdatesCriticalAt: z.number().int().min(0),
|
||||
hostsWithSecurityUpdatesWarningAt: z.number().int().min(0),
|
||||
hostsWithSecurityUpdatesCriticalAt: z.number().int().min(0),
|
||||
upToDateHostsWarningAt: z.number().int().min(0),
|
||||
upToDateHostsCriticalAt: z.number().int().min(0),
|
||||
totalOutdatedPackagesWarningAt: z.number().int().min(0),
|
||||
totalOutdatedPackagesCriticalAt: z.number().int().min(0),
|
||||
enableThresholdColors: z.boolean(),
|
||||
useCustomThresholds: z.boolean(),
|
||||
hostsNeedingUpdatesThresholdMode: z.enum(["absolute", "percent"]),
|
||||
securityUpdatesThresholdMode: z.enum(["absolute", "percent"]),
|
||||
hostsWithSecurityUpdatesThresholdMode: z.enum(["absolute", "percent"]),
|
||||
upToDateHostsThresholdMode: z.enum(["absolute", "percent"]),
|
||||
totalOutdatedPackagesThresholdMode: z.enum(["absolute", "percent"]),
|
||||
}).superRefine(patchmonOptionsSuperRefine);
|
||||
|
||||
describe("patchmonOptionsSuperRefine", () => {
|
||||
test("accepts percent thresholds within 0-100", () => {
|
||||
const result = optionsSchema.safeParse(baseOptions);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
test("rejects percent thresholds above 100", () => {
|
||||
const result = optionsSchema.safeParse({
|
||||
...baseOptions,
|
||||
hostsNeedingUpdatesWarningAt: 101,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.some((issue) => issue.path.includes("hostsNeedingUpdatesWarningAt"))).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("allows absolute thresholds above 100", () => {
|
||||
const result = optionsSchema.safeParse({
|
||||
...baseOptions,
|
||||
hostsNeedingUpdatesThresholdMode: "absolute",
|
||||
hostsNeedingUpdatesWarningAt: 150,
|
||||
hostsNeedingUpdatesCriticalAt: 200,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
test("skips validation when custom thresholds are disabled", () => {
|
||||
const result = optionsSchema.safeParse({
|
||||
...baseOptions,
|
||||
useCustomThresholds: false,
|
||||
hostsNeedingUpdatesWarningAt: 500,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clampPercentThreshold", () => {
|
||||
test("clamps percent values to 0-100", () => {
|
||||
expect(clampPercentThreshold(150, "percent")).toBe(100);
|
||||
expect(clampPercentThreshold(-5, "percent")).toBe(0);
|
||||
expect(clampPercentThreshold(42.9, "percent")).toBe(42);
|
||||
});
|
||||
|
||||
test("leaves absolute values unchanged", () => {
|
||||
expect(clampPercentThreshold(150, "absolute")).toBe(150);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user