diff --git a/content/develop/clients/jedis/error-handling.md b/content/develop/clients/jedis/error-handling.md index 528a6ed082..5a25870496 100644 --- a/content/develop/clients/jedis/error-handling.md +++ b/content/develop/clients/jedis/error-handling.md @@ -16,20 +16,22 @@ app reliability. Jedis organizes exceptions in a hierarchy rooted at `JedisException`, which extends `RuntimeException`. All Jedis exceptions are unchecked exceptions: -``` -JedisException -├── JedisDataException -│ ├── JedisRedirectionException -│ │ ├── JedisMovedDataException -│ │ └── JedisAskDataException -│ ├── AbortedTransactionException -│ ├── JedisAccessControlException -│ └── JedisNoScriptException -├── JedisClusterException -│ ├── JedisClusterOperationException -│ ├── JedisConnectionException -│ └── JedisValidationException -└── InvalidURIException +```hierarchy {type="exception"} +"JedisException": + _meta: + description: "Base class for all Jedis exceptions" + "JedisDataException": + "JedisRedirectionException": + "JedisMovedDataException": + "JedisAskDataException": + "AbortedTransactionException": + "JedisAccessControlException": + "JedisNoScriptException": + "JedisClusterException": + "JedisClusterOperationException": + "JedisConnectionException": + "JedisValidationException": + "InvalidURIException": ``` ### Key exceptions diff --git a/content/develop/clients/redis-py/error-handling.md b/content/develop/clients/redis-py/error-handling.md index 52b85c1cde..14bd34b61f 100644 --- a/content/develop/clients/redis-py/error-handling.md +++ b/content/develop/clients/redis-py/error-handling.md @@ -17,18 +17,23 @@ app reliability. ## Exception hierarchy -redis-py organizes exceptions in a hierarchy. The base exception is `redis.RedisError`, with specific subclasses for different error types: - -``` -RedisError (base) -├── ConnectionError -│ ├── TimeoutError -│ └── BusyLoadingError -├── ResponseError -├── InvalidResponse -├── DataError -├── PubSubError -└── ... (others) +redis-py organizes exceptions in a hierarchy. The base exception is `redis.RedisError`, with specific subclasses for different error types, as shown below: + +```hierarchy {type="exception"} +"RedisError": + _meta: + description: "Base class for all redis-py exceptions" + "ConnectionError": + "TimeoutError": + "BusyLoadingError": + "ResponseError": + "InvalidResponse": + "DataError": + "PubSubError": + "...": + _meta: + ellipsis: true + description: "Other exception types" ``` ### Key exceptions diff --git a/content/integrate/redis-data-integration/data-pipelines/_index.md b/content/integrate/redis-data-integration/data-pipelines/_index.md index 26942b2e5b..b39bcd1286 100644 --- a/content/integrate/redis-data-integration/data-pipelines/_index.md +++ b/content/integrate/redis-data-integration/data-pipelines/_index.md @@ -108,10 +108,28 @@ section. ### 2. Configure the pipeline RDI uses a set of [YAML](https://en.wikipedia.org/wiki/YAML) -files to configure each pipeline. The following diagram shows the folder -structure of the configuration: - -{{< image filename="images/rdi/ingest/ingest-config-folders.webp" width="600px" >}} +files to configure each pipeline. The folder structure of the +configuration is shown below: + +```hierarchy {type="filesystem"} +"(root)": + "config.yaml": + _meta: + description: "\"config.yaml\" is the main pipeline configuration file." + "jobs": + _meta: + description: "The 'jobs' folder containing optional job files." + "default-job.yaml": + _meta: + description: "A default job." + "job1.yaml": + _meta: + description: "Each job file must have a unique name." + "...": + _meta: + ellipsis: true + description: "Other job files, if required." +``` The main configuration for the pipeline is in the `config.yaml` file. This specifies the connection details for the source database (such diff --git a/layouts/_default/_markup/render-codeblock-hierarchy.html b/layouts/_default/_markup/render-codeblock-hierarchy.html new file mode 100644 index 0000000000..21629cbc0f --- /dev/null +++ b/layouts/_default/_markup/render-codeblock-hierarchy.html @@ -0,0 +1,5 @@ +{{- $type := .Attributes.type | default "generic" -}} +{{- $noIcons := .Attributes.noicons | default "" -}} +
{{ .Inner | htmlEscape | safeHTML }}
+{{ .Page.Store.Set "hasHierarchy" true }} + diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index fac415cc8f..45b5319792 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -99,5 +99,10 @@ {{ if .Page.Store.Get "hasChecklist" }} {{ end }} + + + {{ if .Page.Store.Get "hasHierarchy" }} + + {{ end }} diff --git a/static/js/hierarchy.js b/static/js/hierarchy.js new file mode 100644 index 0000000000..e768a394b0 --- /dev/null +++ b/static/js/hierarchy.js @@ -0,0 +1,404 @@ +document.addEventListener('DOMContentLoaded', () => { + const hierarchies = document.querySelectorAll('pre.hierarchy-source'); + console.log('Found', hierarchies.length, 'hierarchy(ies)'); + + hierarchies.forEach(pre => { + const hierarchyType = pre.getAttribute('data-hierarchy-type'); + const noIconsAttr = pre.getAttribute('data-no-icons'); + const noIcons = noIconsAttr && noIconsAttr !== ''; + const yamlContent = pre.textContent; + console.log('Processing hierarchy:', hierarchyType, 'noIcons attr:', noIconsAttr, 'noIcons bool:', noIcons); + + createHierarchyFromYAML(yamlContent, hierarchyType, pre, noIcons); + }); +}); + +// Simple YAML parser for hierarchy format +function parseYAML(yamlText) { + const lines = yamlText.split('\n'); + const root = {}; + const stack = [{ node: root, indent: -1 }]; + const skipLines = new Set(); // Track lines to skip + + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + + if (skipLines.has(lineIndex)) continue; + if (!line.trim() || line.trim().startsWith('#')) continue; + + const indent = line.search(/\S/); + const content = line.trim(); + + // Remove trailing colon if present + const keyMatch = content.match(/^"([^"]+)"|^([^:]+)/); + if (!keyMatch) continue; + + const key = keyMatch[1] || keyMatch[2]; + const isMetaKey = key === '_meta'; + + // Pop stack until we find the right parent + while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { + stack.pop(); + } + + const parent = stack[stack.length - 1].node; + + if (isMetaKey) { + // Parse metadata object + parent._meta = {}; + let i = lineIndex + 1; + + while (i < lines.length) { + const nextLine = lines[i]; + if (!nextLine.trim()) { + i++; + continue; + } + const nextIndent = nextLine.search(/\S/); + if (nextIndent <= indent) break; + + const metaMatch = nextLine.trim().match(/^([^:]+):\s*(.+)$/); + if (metaMatch) { + const metaKey = metaMatch[1].trim(); + let metaValue = metaMatch[2].trim(); + + // Remove surrounding quotes if present + if ((metaValue.startsWith('"') && metaValue.endsWith('"')) || + (metaValue.startsWith("'") && metaValue.endsWith("'"))) { + metaValue = metaValue.slice(1, -1); + // Unescape escaped quotes + metaValue = metaValue.replace(/\\"/g, '"'); + metaValue = metaValue.replace(/\\\\/g, '\\'); + } else { + // Parse boolean values + if (metaValue === 'true') metaValue = true; + else if (metaValue === 'false') metaValue = false; + else if (!isNaN(metaValue)) metaValue = parseInt(metaValue); + } + + parent._meta[metaKey] = metaValue; + } + skipLines.add(i); // Mark this line to skip in main loop + i++; + } + } else { + // Regular node + parent[key] = {}; + stack.push({ node: parent[key], indent: indent }); + } + } + + return root; +} + +function createHierarchyFromYAML(yamlText, hierarchyType, preElement, noIcons) { + const data = parseYAML(yamlText); + const rootKey = Object.keys(data)[0]; + + if (!rootKey) return; + + // Build flat list of items with depth info + const items = []; + flattenHierarchy(rootKey, data[rootKey], 0, items); + + // Determine if we should show icons (filesystem type and not disabled) + const showIcons = hierarchyType === 'filesystem' && !noIcons; + + // Calculate SVG dimensions + const lineHeight = 24; + const charWidth = 8; + const leftMargin = 20; + const topMargin = 10; + const indentWidth = 20; + const commentGap = 40; // Gap between item name and comment + const iconSize = 16; // Size of icon + const iconGap = 6; // Gap between icon and text + + // Find max depth and max text width + let maxDepth = 0; + let maxTextWidth = 0; + let maxCommentWidth = 0; + items.forEach(item => { + maxDepth = Math.max(maxDepth, item.depth); + maxTextWidth = Math.max(maxTextWidth, item.name.length); + if (item.description) { + maxCommentWidth = Math.max(maxCommentWidth, item.description.length); + } + }); + + const iconOffset = showIcons ? iconSize + iconGap : 0; + const svgWidth = leftMargin + (maxDepth + 1) * indentWidth + iconOffset + maxTextWidth * charWidth + commentGap + maxCommentWidth * charWidth + 20; + const svgHeight = topMargin + items.length * lineHeight + 10; + + // Create SVG + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('width', svgWidth); + svg.setAttribute('height', svgHeight); + svg.setAttribute('class', 'hierarchy-diagram'); + svg.style.marginTop = '1em'; + svg.style.marginBottom = '1em'; + svg.style.fontFamily = '"Space Mono", monospace'; + svg.style.fontSize = '14px'; + + // Draw items and tree lines + items.forEach((item, index) => { + const y = topMargin + index * lineHeight + 12; // Adjusted for middle alignment + const x = leftMargin + item.depth * indentWidth; + + // Draw tree structure lines + if (item.depth > 0) { + drawTreeLines(svg, item, items, index, leftMargin, topMargin, lineHeight, indentWidth); + } else if (item.depth === 0 && items.length > 1) { + // For root item, draw a short horizontal line and pip if there are children + drawRootConnector(svg, item, items, index, leftMargin, topMargin, lineHeight, indentWidth); + } + + // Draw icon if enabled + if (showIcons) { + drawIcon(svg, item, x + 20, y, iconSize); + } + + // Draw text for item name + const textX = x + 20 + iconOffset; + const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', textX); + text.setAttribute('y', y); + text.setAttribute('font-family', '"Space Mono", monospace'); + text.setAttribute('font-size', '14'); + text.setAttribute('fill', '#333'); + text.setAttribute('dominant-baseline', 'middle'); + text.textContent = item.name; + svg.appendChild(text); + + // Draw description/comment if available + if (item.description) { + const comment = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + comment.setAttribute('x', textX + item.name.length * charWidth + commentGap); + comment.setAttribute('y', y); + comment.setAttribute('font-family', '"Space Mono", monospace'); + comment.setAttribute('font-size', '12'); + comment.setAttribute('fill', '#999'); + comment.setAttribute('font-style', 'italic'); + comment.setAttribute('dominant-baseline', 'middle'); + comment.textContent = item.description; + svg.appendChild(comment); + } + }); + + // Replace the
 element with the SVG
+    preElement.replaceWith(svg);
+}
+
+function cleanDescription(text) {
+    // The YAML parser already handles quote removal and unescaping,
+    // so just return the text as-is
+    return text || null;
+}
+
+function flattenHierarchy(name, nodeData, depth, items) {
+    const isEllipsis = nodeData._meta?.ellipsis === true;
+
+    items.push({
+        name: name,
+        depth: depth,
+        description: cleanDescription(nodeData._meta?.description),
+        isEllipsis: isEllipsis
+    });
+
+    // Only process children if this is not an ellipsis item
+    if (!isEllipsis) {
+        const children = Object.keys(nodeData).filter(k => k !== '_meta');
+        children.forEach(childKey => {
+            flattenHierarchy(childKey, nodeData[childKey], depth + 1, items);
+        });
+    }
+}
+
+function drawTreeLines(svg, item, items, itemIndex, leftMargin, topMargin, lineHeight, indentWidth) {
+    const y = topMargin + itemIndex * lineHeight + 12; // Middle of text
+    const x = leftMargin + item.depth * indentWidth;
+    const parentX = x - indentWidth;
+    const connectorX = parentX + 10; // Line comes from under first letter of parent
+
+    // Find parent item to get its y position
+    let parentY = null;
+    for (let i = itemIndex - 1; i >= 0; i--) {
+        if (items[i].depth === item.depth - 1) {
+            parentY = topMargin + i * lineHeight + 12;
+            break;
+        }
+    }
+
+    // Check if this is the last child at this depth
+    let isLastChild = true;
+    let nextSiblingY = null;
+    for (let i = itemIndex + 1; i < items.length; i++) {
+        if (items[i].depth < item.depth) {
+            isLastChild = true;
+            break;
+        }
+        if (items[i].depth === item.depth) {
+            isLastChild = false;
+            nextSiblingY = topMargin + i * lineHeight + 12;
+            break;
+        }
+    }
+
+    // Draw vertical line from parent (connecting all siblings)
+    if (itemIndex > 0) {
+        // Find first sibling at this depth
+        let firstSiblingY = y;
+        for (let i = itemIndex - 1; i >= 0; i--) {
+            if (items[i].depth < item.depth) break;
+            if (items[i].depth === item.depth) {
+                firstSiblingY = topMargin + i * lineHeight + 12;
+            }
+        }
+
+        // Draw vertical line connecting all siblings, extending up to parent
+        const verticalStartY = parentY !== null ? parentY : Math.min(firstSiblingY, y);
+        const verticalEndY = nextSiblingY || y;
+
+        const vline = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+        vline.setAttribute('x1', connectorX);
+        vline.setAttribute('y1', verticalStartY);
+        vline.setAttribute('x2', connectorX);
+        vline.setAttribute('y2', verticalEndY);
+        vline.setAttribute('stroke', '#999');
+        vline.setAttribute('stroke-width', '1');
+        svg.appendChild(vline);
+    }
+
+    // Draw horizontal line from vertical to item, protruding slightly past the text
+    const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+    line.setAttribute('x1', connectorX);
+    line.setAttribute('y1', y);
+    line.setAttribute('x2', x + 16); // Protrude slightly to point at text with gap
+    line.setAttribute('y2', y);
+    line.setAttribute('stroke', '#999');
+    line.setAttribute('stroke-width', '1');
+    svg.appendChild(line);
+
+    // For ellipsis items, draw a dotted vertical line segment
+    if (item.isEllipsis) {
+        const dotLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+        dotLine.setAttribute('x1', connectorX);
+        dotLine.setAttribute('y1', y);
+        dotLine.setAttribute('x2', connectorX);
+        dotLine.setAttribute('y2', y + lineHeight / 2);
+        dotLine.setAttribute('stroke', '#999');
+        dotLine.setAttribute('stroke-width', '1');
+        dotLine.setAttribute('stroke-dasharray', '2,2');
+        svg.appendChild(dotLine);
+    }
+}
+
+function drawRootConnector(svg, item, items, itemIndex, leftMargin, topMargin, lineHeight, indentWidth) {
+    const y = topMargin + itemIndex * lineHeight + 12; // Middle of text
+    const x = leftMargin + item.depth * indentWidth;
+    const connectorX = x + 10; // Vertical line position
+
+    // Find the first child to determine where the vertical line should extend
+    let firstChildY = null;
+    for (let i = itemIndex + 1; i < items.length; i++) {
+        if (items[i].depth > item.depth) {
+            firstChildY = topMargin + i * lineHeight + 12;
+            break;
+        }
+    }
+
+    if (firstChildY !== null) {
+        // Draw vertical line from root down to first child
+        const vline = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+        vline.setAttribute('x1', connectorX);
+        vline.setAttribute('y1', y);
+        vline.setAttribute('x2', connectorX);
+        vline.setAttribute('y2', firstChildY);
+        vline.setAttribute('stroke', '#999');
+        vline.setAttribute('stroke-width', '1');
+        svg.appendChild(vline);
+
+        // Draw a small horizontal line from root to point at its text
+        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+        line.setAttribute('x1', connectorX);
+        line.setAttribute('y1', y);
+        line.setAttribute('x2', x + 16);
+        line.setAttribute('y2', y);
+        line.setAttribute('stroke', '#999');
+        line.setAttribute('stroke-width', '1');
+        svg.appendChild(line);
+    }
+}
+
+function drawIcon(svg, item, x, y, size) {
+    if (item.name === '(root)') {
+        // Don't draw icon for root
+        return;
+    }
+
+    if (item.isEllipsis) {
+        // Don't draw icon for ellipsis items (the "..." text is enough)
+        return;
+    } else if (item.name.includes('.')) {
+        // File icon (has extension)
+        drawFileIcon(svg, x, y, size);
+    } else {
+        // Folder icon
+        drawFolderIcon(svg, x, y, size);
+    }
+}
+
+function drawFileIcon(svg, x, y, size) {
+    // File icon with folded corner using a path
+    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+
+    // Create path: start at top-left, go right, then down with folded corner, then left, then up
+    const w = size * 0.6;
+    const h = size;
+    const cornerSize = size * 0.25;
+
+    const pathData = `
+        M ${x} ${y - h / 2}
+        L ${x + w - cornerSize} ${y - h / 2}
+        L ${x + w} ${y - h / 2 + cornerSize}
+        L ${x + w} ${y + h / 2}
+        L ${x} ${y + h / 2}
+        Z
+    `;
+
+    path.setAttribute('d', pathData);
+    path.setAttribute('fill', 'none');
+    path.setAttribute('stroke', '#c84c4c');
+    path.setAttribute('stroke-width', '1');
+    path.setAttribute('stroke-linejoin', 'miter');
+    svg.appendChild(path);
+}
+
+function drawFolderIcon(svg, x, y, size) {
+    // Folder icon as single continuous outline using a path
+    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+
+    const tabWidth = size * 0.5;
+    const tabHeight = size * 0.35;
+    const bodyWidth = size;
+
+    // Create path: tab outline, then body outline
+    const pathData = `
+        M ${x} ${y - size / 2 + tabHeight}
+        L ${x} ${y - size / 2}
+        L ${x + tabWidth} ${y - size / 2}
+        L ${x + tabWidth} ${y - size / 2 + tabHeight}
+        L ${x + bodyWidth} ${y - size / 2 + tabHeight}
+        L ${x + bodyWidth} ${y + size / 2}
+        L ${x} ${y + size / 2}
+        Z
+    `;
+
+    path.setAttribute('d', pathData);
+    path.setAttribute('fill', 'none');
+    path.setAttribute('stroke', '#c84c4c');
+    path.setAttribute('stroke-width', '1');
+    path.setAttribute('stroke-linejoin', 'miter');
+    svg.appendChild(path);
+}
+