generated from coulomb/repo-seed
feat: resolve graph explorer zones from definitions
This commit is contained in:
@@ -988,10 +988,230 @@ def graph_explorer_page() -> str:
|
||||
.split("")
|
||||
.reduce((total, character) => (total * 31 + character.charCodeAt(0)) >>> 0, 0);
|
||||
|
||||
const normalizeDeploymentZoneValue = (value) => {
|
||||
const raw = String(value || "").trim();
|
||||
const normalized = raw.toLowerCase();
|
||||
if (!normalized || normalized === "all") return "";
|
||||
if (normalized === "dev") return "dev-tegwick";
|
||||
if (normalized === "test" || normalized === "staging") return "test";
|
||||
if (normalized === "prod") return "prod";
|
||||
return raw;
|
||||
};
|
||||
|
||||
const defaultZoneDefinitions = {
|
||||
deploymentEnvironment: [
|
||||
{
|
||||
id: "deploymentEnvironment:dev-tegwick",
|
||||
label: "dev-tegwick",
|
||||
field: "deploymentEnvironment",
|
||||
value: "dev-tegwick",
|
||||
rank: 0,
|
||||
membership: {
|
||||
field: "deploymentEnvironment",
|
||||
op: "equals",
|
||||
value: "dev-tegwick",
|
||||
normalize: "deploymentEnvironment",
|
||||
},
|
||||
presentation: {height: 10, color: "#0f766e", fill: "rgba(15, 118, 110, .07)"},
|
||||
},
|
||||
{
|
||||
id: "deploymentEnvironment:test",
|
||||
label: "test",
|
||||
field: "deploymentEnvironment",
|
||||
value: "test",
|
||||
rank: 1,
|
||||
membership: {
|
||||
field: "deploymentEnvironment",
|
||||
op: "equals",
|
||||
value: "test",
|
||||
normalize: "deploymentEnvironment",
|
||||
},
|
||||
presentation: {height: 20, color: "#2563eb", fill: "rgba(37, 99, 235, .07)"},
|
||||
},
|
||||
{
|
||||
id: "deploymentEnvironment:prod",
|
||||
label: "prod",
|
||||
field: "deploymentEnvironment",
|
||||
value: "prod",
|
||||
rank: 2,
|
||||
membership: {
|
||||
field: "deploymentEnvironment",
|
||||
op: "equals",
|
||||
value: "prod",
|
||||
normalize: "deploymentEnvironment",
|
||||
},
|
||||
presentation: {height: 30, color: "#be123c", fill: "rgba(190, 18, 60, .07)"},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const zoneElementData = (element) => {
|
||||
if (!element) return {};
|
||||
if (typeof element.data === "function") return element.data();
|
||||
return element.data && typeof element.data === "object" ? element.data : element;
|
||||
};
|
||||
|
||||
const zoneFieldValue = (data, field) => {
|
||||
if (!field) return "";
|
||||
let current = data || {};
|
||||
String(field).split(".").forEach((part) => {
|
||||
current = current && typeof current === "object" ? current[part] : undefined;
|
||||
});
|
||||
return current;
|
||||
};
|
||||
|
||||
const zoneRuleValue = (data, rule) => {
|
||||
const value = zoneFieldValue(data, rule.field);
|
||||
if (rule.normalize === "deploymentEnvironment") return normalizeDeploymentZoneValue(value);
|
||||
return hasValue(value) ? String(value).trim() : "";
|
||||
};
|
||||
|
||||
const zoneRuleMatches = (data, rule, emptyMatches = false) => {
|
||||
if (!rule || !Object.keys(rule).length) return emptyMatches;
|
||||
if (Array.isArray(rule.all)) {
|
||||
return rule.all.every((child) => zoneRuleMatches(data, child));
|
||||
}
|
||||
if (Array.isArray(rule.any)) {
|
||||
return rule.any.some((child) => zoneRuleMatches(data, child));
|
||||
}
|
||||
if (Array.isArray(rule.rules)) {
|
||||
return rule.rules.every((child) => zoneRuleMatches(data, child));
|
||||
}
|
||||
const actual = zoneRuleValue(data, rule);
|
||||
if (rule.op === "exists") return hasValue(actual);
|
||||
if (rule.op === "in") {
|
||||
const expected = Array.isArray(rule.value) ? rule.value : [rule.value];
|
||||
return expected.map((value) => String(value)).includes(String(actual));
|
||||
}
|
||||
return String(actual) === String(rule.value ?? "");
|
||||
};
|
||||
|
||||
const zoneDescriptorFromDefinition = (definition) => ({
|
||||
field: definition.field,
|
||||
id: definition.id,
|
||||
label: definition.label || definition.id,
|
||||
value: definition.value || definition.label || definition.id,
|
||||
rank: definition.rank,
|
||||
presentation: definition.presentation || {},
|
||||
membership: definition.membership || {},
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
const accessZoneDefinition = (value) => {
|
||||
const label = String(value || "").trim();
|
||||
if (!label) return null;
|
||||
return {
|
||||
id: `accessZone:${label}`,
|
||||
label,
|
||||
field: "accessZone",
|
||||
value: label,
|
||||
rank: 10 + hashString(label) % 20,
|
||||
membership: {field: "accessZone", op: "equals", value: label},
|
||||
presentation: {},
|
||||
};
|
||||
};
|
||||
|
||||
const zoneDefinitionsForData = (data, grouping = activeZoneGrouping) => {
|
||||
if (grouping === "deploymentEnvironment") return defaultZoneDefinitions.deploymentEnvironment;
|
||||
if (grouping === "accessZone") {
|
||||
const definition = accessZoneDefinition(zoneFieldValue(data, "accessZone"));
|
||||
return definition ? [definition] : [];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const zoneDefinitionsForElements = (elements, grouping = activeZoneGrouping) => {
|
||||
if (grouping === "deploymentEnvironment") return defaultZoneDefinitions.deploymentEnvironment;
|
||||
if (grouping === "accessZone") {
|
||||
const values = new Set();
|
||||
elements.forEach((element) => {
|
||||
const value = String(zoneFieldValue(zoneElementData(element), "accessZone") || "").trim();
|
||||
if (value) values.add(value);
|
||||
});
|
||||
return Array.from(values)
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
.map(accessZoneDefinition)
|
||||
.filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const chooseZoneCandidate = (candidates) => candidates
|
||||
.slice()
|
||||
.sort((left, right) => {
|
||||
const leftHeight = Number(left.definition.presentation?.height) || 0;
|
||||
const rightHeight = Number(right.definition.presentation?.height) || 0;
|
||||
return rightHeight - leftHeight || left.index - right.index || left.definition.id.localeCompare(right.definition.id);
|
||||
})[0];
|
||||
|
||||
const resolveZoneInstances = (elements, definitions) => {
|
||||
const zones = new Map();
|
||||
definitions.forEach((definition, index) => {
|
||||
zones.set(definition.id, {
|
||||
...zoneDescriptorFromDefinition(definition),
|
||||
definition,
|
||||
definitionIndex: index,
|
||||
nodes: [],
|
||||
elements: [],
|
||||
nodeIds: new Set(),
|
||||
elementIds: new Set(),
|
||||
diagnostics: [],
|
||||
});
|
||||
});
|
||||
const assignments = new Map();
|
||||
const addElementToZone = (zoneId, element) => {
|
||||
const zone = zones.get(zoneId);
|
||||
if (!zone) return;
|
||||
const elementId = element.id ? element.id() : zoneElementData(element).id;
|
||||
if (elementId && zone.elementIds.has(elementId)) return;
|
||||
if (elementId) zone.elementIds.add(elementId);
|
||||
zone.elements.push(element);
|
||||
};
|
||||
elements.filter((element) => element.isNode && element.isNode()).forEach((node) => {
|
||||
const data = zoneElementData(node);
|
||||
const candidates = definitions
|
||||
.map((definition, index) => ({definition, index}))
|
||||
.filter((candidate) => zoneRuleMatches(data, candidate.definition.membership));
|
||||
if (!candidates.length) return;
|
||||
if (candidates.length > 1) {
|
||||
const diagnostic = {
|
||||
severity: "warning",
|
||||
code: "ZONE_NODE_SEEDED_BY_MULTIPLE_ZONES",
|
||||
message: `${elementLabel(node)} matched multiple zone definitions.`,
|
||||
};
|
||||
candidates.forEach((candidate) => zones.get(candidate.definition.id)?.diagnostics.push(diagnostic));
|
||||
}
|
||||
const chosen = chooseZoneCandidate(candidates).definition;
|
||||
const zone = zones.get(chosen.id);
|
||||
if (!zone) return;
|
||||
assignments.set(node.id(), chosen.id);
|
||||
zone.nodeIds.add(node.id());
|
||||
zone.nodes.push(node);
|
||||
addElementToZone(chosen.id, node);
|
||||
});
|
||||
elements.filter((element) => element.isEdge && element.isEdge()).forEach((edge) => {
|
||||
const data = zoneElementData(edge);
|
||||
const zoneIds = new Set(
|
||||
definitions
|
||||
.filter((definition) => zoneRuleMatches(data, definition.membership))
|
||||
.map((definition) => definition.id)
|
||||
);
|
||||
const sourceZoneId = assignments.get(edge.data("source"));
|
||||
const targetZoneId = assignments.get(edge.data("target"));
|
||||
if (sourceZoneId) zoneIds.add(sourceZoneId);
|
||||
if (targetZoneId) zoneIds.add(targetZoneId);
|
||||
zoneIds.forEach((zoneId) => addElementToZone(zoneId, edge));
|
||||
});
|
||||
return zones;
|
||||
};
|
||||
|
||||
const zoneStyle = (zone) =>
|
||||
zonePalette[zone.id] || fallbackZonePalette[hashString(zone.id) % fallbackZonePalette.length];
|
||||
zone.presentation?.color
|
||||
? {color: zone.presentation.color, fill: zone.presentation.fill || zonePalette[zone.id]?.fill || "rgba(37, 99, 235, .07)"}
|
||||
: zonePalette[zone.id] || fallbackZonePalette[hashString(zone.id) % fallbackZonePalette.length];
|
||||
|
||||
const zoneRank = (zone) => {
|
||||
if (Number.isFinite(zone.rank)) return zone.rank;
|
||||
if (zone.id === "deploymentEnvironment:dev-tegwick") return 0;
|
||||
if (zone.id === "deploymentEnvironment:test") return 1;
|
||||
if (zone.id === "deploymentEnvironment:prod") return 2;
|
||||
@@ -1011,36 +1231,9 @@ def graph_explorer_page() -> str:
|
||||
};
|
||||
|
||||
const zoneForData = (data, grouping = activeZoneGrouping) => {
|
||||
if (grouping === "deploymentEnvironment") {
|
||||
const value = String(data.deploymentEnvironment || "").trim();
|
||||
if (!value) return null;
|
||||
const normalized = value.toLowerCase();
|
||||
if (normalized === "all") return null;
|
||||
const label = normalized === "dev"
|
||||
? "dev-tegwick"
|
||||
: normalized === "test" || normalized === "staging"
|
||||
? "test"
|
||||
: normalized === "prod"
|
||||
? normalized
|
||||
: value;
|
||||
return {
|
||||
field: "deploymentEnvironment",
|
||||
id: `deploymentEnvironment:${label}`,
|
||||
label,
|
||||
value,
|
||||
};
|
||||
}
|
||||
if (grouping === "accessZone") {
|
||||
const value = String(data.accessZone || "").trim();
|
||||
if (!value) return null;
|
||||
return {
|
||||
field: "accessZone",
|
||||
id: `accessZone:${value}`,
|
||||
label: value,
|
||||
value,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
const definition = zoneDefinitionsForData(data, grouping)
|
||||
.find((candidate) => zoneRuleMatches(data, candidate.membership));
|
||||
return definition ? zoneDescriptorFromDefinition(definition) : null;
|
||||
};
|
||||
|
||||
const renderedNodeBox = (node) => {
|
||||
@@ -1091,30 +1284,22 @@ def graph_explorer_page() -> str:
|
||||
};
|
||||
|
||||
const collectZoneSummaries = () => {
|
||||
const groups = new Map();
|
||||
if (!cy) return groups;
|
||||
cy.elements().filter((element) => element.style("display") !== "none").forEach((element) => {
|
||||
const zone = zoneForData(element.data());
|
||||
if (!zone) return;
|
||||
if (!groups.has(zone.id)) {
|
||||
groups.set(zone.id, {...zone, nodes: [], elements: []});
|
||||
}
|
||||
const group = groups.get(zone.id);
|
||||
group.elements.push(element);
|
||||
if (element.isNode()) group.nodes.push(element);
|
||||
});
|
||||
return groups;
|
||||
if (!cy) return new Map();
|
||||
const visibleElements = cy.elements()
|
||||
.filter((element) => element.style("display") !== "none")
|
||||
.toArray();
|
||||
return resolveZoneInstances(
|
||||
visibleElements,
|
||||
zoneDefinitionsForElements(visibleElements, activeZoneGrouping)
|
||||
);
|
||||
};
|
||||
|
||||
const zoneCountsForField = (elements, field) => {
|
||||
if (field !== "deploymentEnvironment" && field !== "accessZone") return groupCounts(elements, field);
|
||||
const groups = new Map();
|
||||
elements.forEach((element) => {
|
||||
const zone = zoneForData(element.data(), field);
|
||||
if (!zone) return;
|
||||
groups.set(zone.label, (groups.get(zone.label) || 0) + 1);
|
||||
});
|
||||
return Array.from(groups.entries())
|
||||
const zones = resolveZoneInstances(elements, zoneDefinitionsForElements(elements, field));
|
||||
return Array.from(zones.values())
|
||||
.filter((zone) => zone.elements.length > 0)
|
||||
.map((zone) => [zone.label, zone.elements.length])
|
||||
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user