fix(agent-config): stabilize patch and target resolution

This commit is contained in:
vince 2026-06-19 12:21:15 +02:00
parent 9b7f20c486
commit b69de71029
2 changed files with 271 additions and 37 deletions

View file

@ -746,16 +746,12 @@ function connectDatasource(
);
}
const target = resolveTarget(document, operation.target);
if (!target) {
return failure(
"item_not_found",
`Target not found: ${operation.target.id}`,
operationIndex,
targetPath(operation.target),
);
const targetResolution = resolveTarget(document, operation.target, operationIndex);
if (!targetResolution.ok) {
return { ok: false, errors: targetResolution.errors };
}
const target = targetResolution.target;
(target as DatasourceAgentTarget).datasource = operation.datasource;
return { ok: true, document };
}
@ -798,16 +794,12 @@ function setStatusRule(
);
}
const target = resolveTarget(document, operation.target);
if (!target) {
return failure(
"item_not_found",
`Target not found: ${operation.target.id}`,
operationIndex,
targetPath(operation.target),
);
const targetResolution = resolveTarget(document, operation.target, operationIndex);
if (!targetResolution.ok) {
return { ok: false, errors: targetResolution.errors };
}
const target = targetResolution.target;
if (operation.thresholds && operation.target.kind === "telemetry") {
(target as TelemetryCard).thresholds = operation.thresholds;
}
@ -881,8 +873,8 @@ function removeItem(
}
if (operation.target.kind === "service") {
const location = findService(document, operation.target.id, operation.target.groupId);
if (!location) {
const matches = findServices(document, operation.target.id, operation.target.groupId);
if (!matches.length) {
return failure(
"item_not_found",
`Service not found: ${operation.target.id}`,
@ -890,7 +882,17 @@ function removeItem(
"/serviceGroups",
);
}
location.group.services = location.group.services.filter((service) => service.id !== operation.target.id);
if (!operation.target.groupId && matches.length > 1) {
return failure(
"ambiguous_target",
`Service exists in multiple groups: ${operation.target.id}`,
operationIndex,
"/serviceGroups",
);
}
const match = matches[0];
match.group.services = match.group.services.filter((service) => service.id !== operation.target.id);
return { ok: true, document };
}
@ -938,25 +940,83 @@ function removeItem(
function resolveTarget(
document: DashboardDocument,
target: ConnectDatasourceOperation["target"],
): ResolvedAgentTarget | null {
operationIndex: number,
): ResolveTargetResult {
if (target.kind === "telemetry") {
return document.telemetry.find((card) => card.id === target.id) || null;
const card = document.telemetry.find((item) => item.id === target.id);
if (!card) {
return resolutionFailure(
"item_not_found",
`Telemetry card not found: ${target.id}`,
operationIndex,
"/telemetry",
);
}
return { ok: true, target: card };
}
if (target.kind === "service") {
return findService(document, target.id, target.groupId)?.service || null;
const matches = findServices(document, target.id, target.groupId);
if (!matches.length) {
return resolutionFailure(
"item_not_found",
`Service not found: ${target.id}`,
operationIndex,
"/serviceGroups",
);
}
if (!target.groupId && matches.length > 1) {
return resolutionFailure(
"ambiguous_target",
`Service exists in multiple groups: ${target.id}`,
operationIndex,
"/serviceGroups",
);
}
return { ok: true, target: matches[0].service };
}
if (target.kind === "statusItem") {
const strips = target.stripId
? document.statusStrips.filter((strip) => strip.id === target.stripId)
: document.statusStrips;
return strips.flatMap((strip) => strip.items).find((item) => item.id === target.id) || null;
const matches = findStatusItems(document, target.id, target.stripId);
if (!matches.length) {
return resolutionFailure(
"item_not_found",
`Status item not found: ${target.id}`,
operationIndex,
"/statusStrips",
);
}
if (!target.stripId && matches.length > 1) {
return resolutionFailure(
"ambiguous_target",
`Status item exists in multiple strips: ${target.id}`,
operationIndex,
"/statusStrips",
);
}
return { ok: true, target: matches[0].item };
}
if (target.kind === "module") {
return (document.modules || []).find((module) => module.id === target.id) || null;
const module = (document.modules || []).find((item) => item.id === target.id);
if (!module) {
return resolutionFailure(
"item_not_found",
`Module not found: ${target.id}`,
operationIndex,
"/modules",
);
}
return { ok: true, target: module };
}
return null;
return resolutionFailure(
"item_not_found",
`Target not found: ${target.id}`,
operationIndex,
targetPath(target),
);
}
type ResolveTargetResult =
| { ok: true; target: ResolvedAgentTarget }
| { ok: false; errors: AgentConfigError[] };
type ResolvedAgentTarget = TelemetryCard | ServiceEntry | StatusItem | DashboardModule;
type DatasourceAgentTarget = TelemetryCard | ServiceEntry | DashboardModule;
type DetailAgentTarget = TelemetryCard | ServiceEntry | DashboardModule;
@ -995,15 +1055,15 @@ function supportsStringValue(
return kind === "statusItem" || kind === "module";
}
function findService(document: DashboardDocument, id: string, groupId?: string) {
function findServices(document: DashboardDocument, id: string, groupId?: string) {
const groups = groupId
? document.serviceGroups.filter((group) => group.id === groupId)
: document.serviceGroups;
for (const group of groups) {
const service = group.services.find((item) => item.id === id);
if (service) return { group, service };
}
return null;
return groups.flatMap((group) =>
group.services.flatMap((service) =>
service.id === id ? [{ group, service }] : [],
),
);
}
function findStatusItems(document: DashboardDocument, id: string, stripId?: string) {
@ -1011,10 +1071,29 @@ function findStatusItems(document: DashboardDocument, id: string, stripId?: stri
? document.statusStrips.filter((strip) => strip.id === stripId)
: document.statusStrips;
return strips.flatMap((strip) =>
strip.items.some((item) => item.id === id) ? [{ strip }] : [],
strip.items.flatMap((item) => (item.id === id ? [{ strip, item }] : [])),
);
}
function resolutionFailure(
code: string,
message: string,
operationIndex: number,
path: string,
): ResolveTargetResult {
return {
ok: false,
errors: [
{
code,
message,
operationIndex,
path,
},
],
};
}
function targetPath(target: ConnectDatasourceOperation["target"]): string {
if (target.kind === "telemetry") return "/telemetry";
if (target.kind === "service") return "/serviceGroups";
@ -1140,12 +1219,18 @@ function createJsonPatch(
return [{ op: "replace", path: path || "/", value: after }];
}
const operations: JsonPatchOperation[] = [];
const max = Math.max(before.length, after.length);
for (let index = 0; index < max; index += 1) {
const sharedLength = Math.min(before.length, after.length);
for (let index = 0; index < sharedLength; index += 1) {
operations.push(
...createJsonPatch(before[index], after[index], `${path}/${index}`),
);
}
for (let index = before.length - 1; index >= after.length; index -= 1) {
operations.push({ op: "remove", path: `${path}/${index}` });
}
for (let index = before.length; index < after.length; index += 1) {
operations.push({ op: "add", path: `${path}/${index}`, value: after[index] });
}
return operations;
}