fix(integrations): label sites with the console name Site Manager shows

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
zacharyandClaude Opus 5 committed 2026-09-13 22:21:43 +02:00
1 parent 44e9c22092
commit 8bdbc30376
2 files changed
+85 -5

No files matched your search

@@ -162,14 +162,14 @@ describe("mapSites", () => {
},
};
const host = { id: "host-1", reportedState: { deviceState: "updateAvailable" } };
const host = { id: "host-1", reportedState: { name: "Console A", deviceState: "updateAvailable" } };
test("maps a complete site", () => {
const [mapped] = mapSites([site], [host], [], new Map(), NOW);
expect(mapped).toMatchObject({
siteId: "site-1",
name: "Example site",
name: "Console A",
wanUptimePct: 99.5,
ispName: "Example ISP",
externalIps: ["203.0.113.10", "203.0.113.11"],
@@ -180,12 +180,51 @@ describe("mapSites", () => {
});
});
/**
* Le défaut relevé sur le compte réel : une console gardait la description de
* site par défaut alors que Site Manager l'affiche sous son propre nom. C'est
* le nom de console que le propriétaire reconnaît.
*/
test("labels a site with its console name rather than a differing site description", () => {
const [mapped] = mapSites([{ ...site, meta: { desc: "Default", name: "default" } }], [host], [], new Map(), NOW);
expect(mapped!.name).toBe("Console A");
});
/** Le nom de console seul ne distinguerait plus les deux lignes. */
test("composes the console name with the site description when one console carries several sites", () => {
const mapped = mapSites(
[
{ ...site, siteId: "site-1", meta: { desc: "Warehouse", name: "default" } },
{ ...site, siteId: "site-2", meta: { desc: "Loading dock", name: "dock" } },
],
[host],
[],
new Map(),
NOW,
);
expect(mapped.map((entry) => entry.name)).toEqual(["Console A · Warehouse", "Console A · Loading dock"]);
});
test("falls back to the site description when no host names the console", () => {
const [mapped] = mapSites([site], [], [], new Map(), NOW);
expect(mapped!.name).toBe("Example site");
});
test("falls back to meta.name when there is no description", () => {
const [mapped] = mapSites([{ ...site, meta: { name: "default" } }], [], [], new Map(), NOW);
expect(mapped!.name).toBe("default");
});
test("falls back to the site identifier when nothing is named", () => {
const [mapped] = mapSites([{ siteId: "site-1", meta: {} }], [], [], new Map(), NOW);
expect(mapped!.name).toBe("site-1");
});
/**
* `null` veut dire « aucun host ne correspond », ce qui n'est pas « la console
* est à jour ». Le widget doit pouvoir les distinguer.
@@ -52,6 +52,8 @@ export const siteManagerHostSchema = z.looseObject({
id: z.string(),
reportedState: z
.looseObject({
/** Le nom de la console, celui que Site Manager affiche. Voir `resolveSiteName`. */
name: z.string().optional(),
deviceState: z.string().optional(),
/**
* `.nullable().optional()` et non `.nullish()` : « présent et explicitement
@@ -292,14 +294,52 @@ const resolveConsoleUpdate = (host: SiteManagerHost | undefined): boolean | null
return null;
};
/**
* Le libellé d'une ligne est le nom de la CONSOLE, pas la description du site.
*
* Site Manager nomme ses lignes d'après `host.reportedState.name` : c'est ce que
* le propriétaire du compte lit chez Ubiquiti, donc ce qu'il reconnaît dans le
* widget. `meta.desc` est une autre chose — la description du site *à
* l'intérieur* de la console — qui ne coïncide que lorsqu'on a nommé les deux
* pareil. Sur le compte réel, une console garde le `meta.desc` par défaut
* « Default » alors que Site Manager l'affiche sous son propre nom : le widget
* étiquetait donc « Default » un site que son propriétaire identifie autrement.
* `meta.name` est pire encore — le slug interne, « default » sur les deux
* consoles.
*
* Une console peut porter plusieurs sites. Son nom ne distingue alors plus les
* lignes entre elles, et la description les départage.
*/
const resolveSiteName = (site: SiteManagerSite, host: SiteManagerHost | undefined, sitesOnConsole: number): string => {
const consoleName = host?.reportedState?.name?.trim();
if (consoleName === undefined || consoleName === "") return site.meta.desc ?? site.meta.name ?? site.siteId;
const description = site.meta.desc?.trim();
if (sitesOnConsole > 1 && description !== undefined && description !== "") return `${consoleName} · ${description}`;
return consoleName;
};
/** Compté une fois sur tout le compte, pas par ligne. */
const countSitesPerHost = (sites: SiteManagerSite[]): Map<string, number> => {
const counts = new Map<string, number>();
for (const site of sites) {
if (site.hostId === undefined) continue;
counts.set(site.hostId, (counts.get(site.hostId) ?? 0) + 1);
}
return counts;
};
export const mapSites = (
sites: SiteManagerSite[],
hosts: SiteManagerHost[],
metrics: IspMetricsEntry[],
sdWanBySite: Map<string, UnifiSiteSdWanStatus>,
now: Date,
): UnifiSiteManagerSite[] =>
sites.map((site) => {
): UnifiSiteManagerSite[] => {
const sitesPerHost = countSitesPerHost(sites);
return sites.map((site) => {
const statistics = site.statistics;
const counts = statistics?.counts;
const host = site.hostId === undefined ? undefined : hosts.find((candidate) => candidate.id === site.hostId);
@@ -310,7 +350,7 @@ export const mapSites = (
return {
siteId: site.siteId,
name: site.meta.desc ?? site.meta.name ?? site.siteId,
name: resolveSiteName(site, host, site.hostId === undefined ? 1 : (sitesPerHost.get(site.hostId) ?? 1)),
wanUptimePct: statistics?.percentages?.wanUptime ?? null,
ispName: statistics?.ispInfo?.name ?? null,
externalIps: Object.values(statistics?.wans ?? {})
@@ -327,3 +367,4 @@ export const mapSites = (
sdWan: sdWanBySite.get(site.siteId) ?? null,
};
});
};