Files
firewallissues/web/js/product-tree-model.js
T

145 lines
5.0 KiB
JavaScript

import { getSortedKeys } from './sort.js';
export function expandFilePrefixLevel(root) {
if (!root || typeof root !== 'object') {
return root;
}
return transformNode(root);
}
function transformNode(node) {
if (!node || typeof node !== 'object' || Array.isArray(node)) {
return node;
}
const hasIssueArrays = Object.prototype.hasOwnProperty.call(node, 'addressed') ||
Object.prototype.hasOwnProperty.call(node, 'known');
const childKeys = Object.keys(node).filter(key => key !== 'addressed' && key !== 'known');
const transformedChildren = Object.fromEntries(
childKeys.map(key => [key, transformNode(node[key])])
);
if (!hasIssueArrays) {
return transformedChildren;
}
const addressed = Array.isArray(node.addressed) ? node.addressed : [];
const known = Array.isArray(node.known) ? node.known : [];
if (addressed.length + known.length === 0) {
return childKeys.length > 0 ? transformedChildren : { addressed: [], known: [] };
}
const groupedByPrefix = {};
for (const [issueType, files] of [['addressed', addressed], ['known', known]]) {
for (const fileName of files) {
const prefix = getFilePrefix(fileName);
groupedByPrefix[prefix] ??= { addressed: [], known: [] };
groupedByPrefix[prefix][issueType].push(fileName);
}
}
const transformedIssueFiles = groupHotfixPrefixes(groupedByPrefix);
return childKeys.length === 0
? transformedIssueFiles
: mergeTreeNodes(transformedChildren, transformedIssueFiles);
}
function groupHotfixPrefixes(groupedByPrefix) {
const byFamily = {};
for (const prefix of Object.keys(groupedByPrefix)) {
const family = getReleaseFamily(prefix);
byFamily[family] ??= [];
byFamily[family].push(prefix);
}
const result = {};
for (const [family, members] of Object.entries(byFamily)) {
const hasHotfixes = members.length > 1 || members.some(member => member !== family);
if (!hasHotfixes && members[0] === family) {
result[family] = groupedByPrefix[family];
} else {
result[family] = Object.fromEntries(
members.map(member => [member, groupedByPrefix[member]])
);
}
}
return result;
}
function getReleaseFamily(prefix) {
return String(prefix || '').replace(/-h\d+$/i, '');
}
function getFilePrefix(fileName) {
const baseName = String(fileName || '');
const underscoreIndex = baseName.indexOf('_');
if (underscoreIndex <= 0) {
return baseName.replace(/\.json$/i, '') || 'unknown';
}
return baseName.slice(0, underscoreIndex);
}
function mergeTreeNodes(left, right) {
if (!isObjectNode(left)) return right;
if (!isObjectNode(right)) return left;
const merged = { ...left };
for (const [key, rightValue] of Object.entries(right)) {
const leftValue = merged[key];
if (key === 'addressed' || key === 'known') {
const leftFiles = Array.isArray(leftValue) ? leftValue : [];
const rightFiles = Array.isArray(rightValue) ? rightValue : [];
merged[key] = Array.from(new Set([...leftFiles, ...rightFiles]));
} else if (isObjectNode(leftValue) && isObjectNode(rightValue)) {
merged[key] = mergeTreeNodes(leftValue, rightValue);
} else {
merged[key] = rightValue;
}
}
return merged;
}
function isObjectNode(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
export function toTreeNodes(value, path = []) {
return getSortedKeys(value)
.filter(key => key !== 'addressed' && key !== 'known')
.map(key => {
const childValue = value[key];
const childPath = [...path, key];
const isObject = childValue && typeof childValue === 'object';
const hasFiles = isObject && ('addressed' in childValue || 'known' in childValue);
const children = isObject ? toTreeNodes(childValue, childPath) : [];
return {
id: JSON.stringify(childPath),
label: key,
selectable: hasFiles || children.length > 0,
metadata: {
path: childPath,
addressed: hasFiles && Array.isArray(childValue.addressed) ? childValue.addressed : [],
known: hasFiles && Array.isArray(childValue.known) ? childValue.known : []
},
...(children.length > 0 ? { children } : {})
};
});
}
export function enumerateTreePaths(nodes) {
const paths = [];
function visit(children, parentPath = '') {
for (const node of children) {
const path = parentPath ? `${parentPath}>${node.id}` : node.id;
paths.push(path);
visit(node.children ?? [], path);
}
}
visit(nodes);
return paths;
}
export function buildProductTree(products) {
return toTreeNodes(expandFilePrefixLevel(products));
}