UNPKG

@redpanda-data/docs-extensions-and-macros

Version:

Antora extensions and macros developed for Redpanda documentation.

979 lines (917 loc) 59.8 kB
'use strict'; /** * Registers macros for use in Redpanda Connect contexts in the Redpanda documentation. * @param {Registry} registry - The Antora registry where this block macro is registered. * @param {Object} context - The Antora context that provides access to configuration data, such as parsed CSV content. */ module.exports.register = function (registry, context) { function filterComponentTable() { const nameInputElement = document.getElementById('componentTableSearch'); const nameInput = nameInputElement ? nameInputElement.value.trim().toLowerCase() : ''; const typeFilter = Array.from(document.querySelectorAll('#typeFilterMenu input[type="checkbox"]:checked')).map(checkbox => checkbox.value); // Check for the existence of support and enterprise license filters (optional) const supportFilterElement = document.querySelector('#supportFilterMenu'); const supportFilter = supportFilterElement ? Array.from(supportFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; // Check for cloud support filter (optional) const cloudSupportFilterElement = document.querySelector('#cloudSupportFilterMenu'); const cloudSupportFilter = cloudSupportFilterElement ? Array.from(cloudSupportFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; // Check for enterprise license filter (optional) const enterpriseFilterElement = document.querySelector('#enterpriseFilterMenu'); const enterpriseFilter = enterpriseFilterElement ? Array.from(enterpriseFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; const params = getQueryParams(); const enterpriseSupportFilter = params.support === 'enterprise'; // Check if 'support=enterprise' is in the URL const cloudSupportFilterFromUrl = params.support === 'cloud'; // Check if 'support=cloud' is in the URL const table = document.getElementById('componentTable'); if (!table) return; // Exit early if table doesn't exist const trs = table.getElementsByTagName('tr'); if (!trs || trs.length === 0) return; // Exit early if no rows found for (let i = 1; i < trs.length; i++) { const row = trs[i]; const nameTd = row.querySelector('td[id^="componentName-"]'); const typeTd = row.querySelector('td[id^="componentType-"]'); const supportTd = row.querySelector('td[id^="componentSupport-"]'); // Support column, if present const enterpriseSupportTd = row.querySelector('td[id^="componentLicense-"]'); // Enterprise License column, if present const cloudSupportTd = row.querySelector('td[id^="componentCloud-"]'); // Cloud support column, if present if (typeTd) { // Ensure that at least the Type column is present const nameText = nameTd ? nameTd.textContent.trim().toLowerCase() : ''; const typeText = typeTd.textContent.trim().toLowerCase().split(', ').map(item => item.trim()); const supportText = supportTd ? supportTd.textContent.trim().toLowerCase() : ''; const enterpriseSupportText = enterpriseSupportTd ? enterpriseSupportTd.textContent.trim().toLowerCase() : ''; // Yes or No const cloudSupportText = cloudSupportTd ? cloudSupportTd.textContent.trim().toLowerCase() : ''; // Yes or No // Check cloud support filter let cloudSupportMatch = true; if (cloudSupportFilter.length > 0 && !cloudSupportFilter.includes('')) { // If specific options are selected (not "All") cloudSupportMatch = cloudSupportFilter.some(value => { if (value === 'yes') return cloudSupportText === 'yes' || cloudSupportText.includes('yes'); if (value === 'no') return cloudSupportText === 'no' || !cloudSupportText.includes('yes'); return true; }); } // Check enterprise license filter let enterpriseLicenseMatch = true; if (enterpriseFilter.length > 0 && !enterpriseFilter.includes('')) { // If specific options are selected (not "All") enterpriseLicenseMatch = enterpriseFilter.some(value => { if (value === 'yes') return enterpriseSupportText === 'yes' || enterpriseSupportText.includes('yes'); if (value === 'no') return enterpriseSupportText === 'no' || !enterpriseSupportText.includes('yes'); return true; }); } // Determine if the row should be shown const showRow = ((!nameInput || nameText.includes(nameInput)) && // Filter by name if present (typeFilter.length === 0 || typeFilter.some(value => typeText.includes(value))) && // Filter by type (!supportTd || supportFilter.length === 0 || supportFilter.some(value => supportText.includes(value))) && // Filter by support if present (!enterpriseSupportFilter || !enterpriseSupportTd || supportText.includes('enterprise') || enterpriseSupportText === 'yes') && // Filter by enterprise support if 'support=enterprise' is in the URL (!cloudSupportFilterFromUrl || !cloudSupportTd || supportText.includes('cloud') || cloudSupportText === 'yes') && // Filter by cloud support if 'support=cloud' is in the URL cloudSupportMatch && // Filter by cloud support dropdown enterpriseLicenseMatch // Filter by enterprise license dropdown ); row.style.display = showRow ? '' : 'none'; } else { row.style.display = 'none'; // Hide row if the Type column is missing } } // Update dropdown text based on selections updateDropdownText('typeFilter', 'All Types Selected', 'Types Selected'); const supportMenu = document.getElementById('supportFilterMenu'); if (supportMenu) { updateDropdownText('supportFilter', 'All Support Levels Selected', 'Support Levels Selected'); } const cloudSupportMenu = document.getElementById('cloudSupportFilterMenu'); if (cloudSupportMenu) { updateDropdownText('cloudSupportFilter', 'All Options Selected', 'Options Selected'); } const enterpriseMenu = document.getElementById('enterpriseFilterMenu'); if (enterpriseMenu) { updateDropdownText('enterpriseFilter', 'All Options Selected', 'Options Selected'); } // Update URL parameters based on current filter selections updateURLParameters(); } function updateURLParameters() { const params = new URLSearchParams(); // Get current filter values const nameInputElement = document.getElementById('componentTableSearch'); const nameInput = nameInputElement ? nameInputElement.value.trim() : ''; const typeFilter = Array.from(document.querySelectorAll('#typeFilterMenu input[type="checkbox"]:checked')).map(checkbox => checkbox.value); const supportFilterElement = document.querySelector('#supportFilterMenu'); const supportFilter = supportFilterElement ? Array.from(supportFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; const cloudSupportFilterElement = document.querySelector('#cloudSupportFilterMenu'); const cloudSupportFilter = cloudSupportFilterElement ? Array.from(cloudSupportFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; const enterpriseFilterElement = document.querySelector('#enterpriseFilterMenu'); const enterpriseFilter = enterpriseFilterElement ? Array.from(enterpriseFilterElement.querySelectorAll('input[type="checkbox"]:checked')).map(checkbox => checkbox.value) : []; // Add parameters to URL if they have values if (nameInput) params.set('search', nameInput); if (typeFilter.length > 0) params.set('type', typeFilter.join(',')); if (supportFilter.length > 0) params.set('support', supportFilter.join(',')); if (cloudSupportFilter.length > 0 && !cloudSupportFilter.includes('')) { params.set('cloud', cloudSupportFilter.join(',')); } if (enterpriseFilter.length > 0 && !enterpriseFilter.includes('')) { params.set('enterprise', enterpriseFilter.join(',')); } // Update the URL without refreshing the page const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); window.history.replaceState({}, '', newURL); } /** * Gets the first URL (either Redpanda Connect or Redpanda Cloud) for a given connector from the typesArray. * If the cloud option is enabled (`isCloud = true`), it prefers the Redpanda Cloud URL; otherwise, it returns the Redpanda Connect URL. * * @param {Array} typesArray - An array of types where each type has a list of commercial names with URLs. * @param {boolean} isCloud - A flag to indicate if Cloud URLs should be prioritized. * @returns {string} - The first found URL (either Redpanda Connect or Cloud), or an empty string if no URL is available. */ function getFirstUrlFromTypesArray(typesArray, isCloud) { for (const [type, commercialNames] of typesArray) { for (const commercialName in commercialNames) { const { urls = {} } = commercialNames[commercialName]; const redpandaConnectUrl = urls.redpandaConnectUrl || ''; const redpandaCloudUrl = urls.redpandaCloudUrl || ''; // Return Cloud URL if isCloud is true and Cloud URL exists if (isCloud && redpandaCloudUrl) { return redpandaCloudUrl; } // Return Connect URL if isCloud is false or no Cloud URL exists if (!isCloud && redpandaConnectUrl) { return redpandaConnectUrl; } // If Cloud URL exists but isCloud is false, fallback to Cloud URL if no Connect URL exists if (!isCloud && redpandaCloudUrl) { return redpandaCloudUrl; } } } return ''; // Return an empty string if no URL is found } const capitalize = s => s && s[0].toUpperCase() + s.slice(1); /** * Processes the parsed CSV data and returns a data structure organized by connector. * * This function processes each row in the CSV data to create a nested object where the key is the connector name. * Each connector contains: * - `types`: A Map of connector types, with associated URLs for Redpanda Connect and Redpanda Cloud. * - Each type maps to commercial names and stores information on URLs, support level, and cloud support. * - `supportLevels`: A Map of support levels containing commercial names and whether the type supports cloud. * - `isLicensed`: A boolean flag indicating whether the connector requires an enterprise license. * - `isCloudConnectorSupported`: A boolean flag indicating whether any type of this connector supports Redpanda Cloud. * * Expected structure of the returned data: * * { * "connectorName": { * "types": Map { * "Input": { // Connector Type * "commercial_name": { * urls: { * redpandaConnectUrl: "/redpanda-connect/components/inputs/connectorName/", * redpandaCloudUrl: "/redpanda-cloud/develop/connect/components/inputs/connectorName/" * }, * supportLevel: "certified", // Support level for this commercial name * isCloudSupported: true // Whether this type supports cloud * }, * ... * }, * "Output": { // Another Connector Type * "commercial_name": { * urls: { * redpandaConnectUrl: "/redpanda-connect/components/outputs/connectorName/", * redpandaCloudUrl: "/redpanda-cloud/develop/connect/components/outputs/connectorName/" * }, * supportLevel: "community", // Support level for this commercial name * isCloudSupported: false // Whether this type supports cloud * }, * ... * }, * ... * }, * "isLicensed": "Yes" or "No", // Indicates if the connector requires an Enterprise license. * "isCloudConnectorSupported": true or false // Indicates if any type for this connector supports Redpanda Cloud. * }, * ... * } * * Notes: * - For each connector, `types` is a `Map` that contains multiple connector types. * - For each type, there may be multiple commercial names. Each commercial name contains URLs, support levels, and cloud support flags. * - The `isCloudConnectorSupported` flag is set to `true` if any of the types for the connector support cloud. * * @param {object} parsedData - The CSV data parsed into an object. * @returns {object} - The processed connectors data structure. */ function processConnectors(parsedData) { return parsedData.data.reduce((connectors, row) => { const { connector, commercial_name, type, support_level, is_cloud_supported, is_licensed, redpandaConnectUrl, redpandaCloudUrl } = row; const isCloudSupported = is_cloud_supported === 'y'; // Initialize the connector if it's not already in the map if (!connectors[connector]) { connectors[connector] = { types: new Map(), isLicensed: is_licensed, isCloudConnectorSupported: false }; } // Ensure type exists for the connector if (!connectors[connector].types.has(type)) { connectors[connector].types.set(type, {}); } // Store the commercial name under the type if (!connectors[connector].types.get(type)[commercial_name]) { connectors[connector].types.get(type)[commercial_name] = { urls: { redpandaConnectUrl: redpandaConnectUrl || '', redpandaCloudUrl: redpandaCloudUrl || '' }, supportLevel: support_level, isCloudSupported: isCloudSupported }; } // Check at the connector level if any commercial name supports cloud if (isCloudSupported) { connectors[connector].isCloudConnectorSupported = true; } return connectors; }, {}); } /** * Processes parsed CSV data and groups SQL drivers by their support level. * * This function extracts the SQL drivers from the parsed CSV data, grouping * them into two categories: "certified" and "community". Each driver is also * associated with a flag indicating whether it supports cloud. * * @param {Object} parsedData - The parsed CSV data containing driver information. * The expected structure of each row should contain at least the following: * { * connector: string, // The name of the connector * commercial_name: string, // The commercial name of the SQL driver * support_level: string, // The support level ('certified', 'community') * is_cloud_supported: string // 'y' or 'n', indicating if the driver supports cloud * } * * @returns {Object} - An object with two properties: * - `certified`: An array of SQL drivers with 'certified' support level. Each driver contains: * - `commercialName`: The trimmed commercial name of the driver (e.g., 'PostgreSQL'). * - `isCloudSupported`: A boolean indicating whether the driver supports cloud. * - `community`: An array of SQL drivers with 'community' support level. Each driver contains: * - `commercialName`: The trimmed commercial name of the driver (e.g., 'Trino'). * - `isCloudSupported`: A boolean indicating whether the driver supports cloud. * * Example return structure: * { * certified: [ * { commercialName: 'PostgreSQL', isCloudSupported: true }, * { commercialName: 'MySQL', isCloudSupported: true }, * ], * community: [ * { commercialName: 'Trino', isCloudSupported: false }, * { commercialName: 'ClickHouse', isCloudSupported: false }, * ] * } */ function processSqlDrivers(parsedData) { const sqlDrivers = { certified: [], community: [] }; parsedData.data.forEach(row => { const { connector: driverName, commercial_name, support_level, is_cloud_supported } = row; const isCloudSupported = is_cloud_supported === 'y'; const supportLevel = support_level.toLowerCase(); // Only process SQL drivers if (driverName.startsWith('sql_driver')) { const driverData = { commercialName: commercial_name.trim(), isCloudSupported: isCloudSupported }; // Group drivers based on their support level if (supportLevel === 'certified') { sqlDrivers.certified.push(driverData); } else if (supportLevel === 'community') { sqlDrivers.community.push(driverData); } } }); return sqlDrivers; } /** * Generates an HTML table for the list of connectors, including their types, support levels, and cloud support. * * This function iterates over the provided connectors and generates an HTML table row for each connector. * It includes type-specific information, support level (including SQL driver details), licensing, and cloud support. * * @param {Object} connectors - An object containing the connector data, where each key is a connector name and * each value contains details about its types, licensing, and cloud support. * { * types: Map - A map of connector types (e.g., Input, Output, Processor), with associated commercial names. * isLicensed: 'Yes' or 'No' - Indicates if the connector requires an enterprise license. * isCloudConnectorSupported: true or false - Indicates if any type for this connector supports Redpanda Cloud. * } * @param {Object} sqlDrivers - An object containing the SQL driver support data, separated by support level: * { * certified: Array<{ commercialName: string, isCloudSupported: boolean }>, * community: Array<{ commercialName: string, isCloudSupported: boolean }> * } * @param {boolean} isCloud - A flag indicating whether to filter by cloud support. If true, only cloud-supported connectors are shown. * @param {boolean} showAllInfo - A flag indicating whether to show all information or limit the data displayed (e.g., for cloud-only views). * * @returns {string} - A string containing the generated HTML for the connectors table rows. * The output is a string of HTML rows with the following columns: * - Connector name * - Connector types (linked to Redpanda Connect or Cloud documentation URLs) * - Support levels (including SQL drivers if applicable) * - Enterprise licensing information * - Cloud support status (Yes/No with a link if applicable) */ function generateConnectorsHTMLTable(connectors, sqlDrivers, isCloud, showAllInfo) { return Object.entries(connectors) .filter(([_, details]) => { // If isCloud is true, filter out rows that do not support cloud return !isCloud || details.isCloudConnectorSupported; }) .map(([connector, details], id) => { const { types, isCloudConnectorSupported, isLicensed } = details; // Generate the type and commercial name links for each connector const typesArray = Array.from(types.entries()) .map(([type, commercialNames]) => { const uniqueCommercialNames = Object.keys(commercialNames); const urlsArray = []; uniqueCommercialNames.forEach(commercialName => { const { urls = {}, isCloudSupported } = commercialNames[commercialName]; const redpandaConnectUrl = urls.redpandaConnectUrl || ''; const redpandaCloudUrl = urls.redpandaCloudUrl || ''; if (isCloud && !showAllInfo) { // Only show Cloud URLs in the Cloud table if (redpandaCloudUrl) { urlsArray.push(`<a href="${redpandaCloudUrl}">${capitalize(type)}</a>`); } } else { // Show Connect URLs in non-cloud tables if (redpandaConnectUrl) { urlsArray.push(`<a href="${redpandaConnectUrl}">${capitalize(type)}</a>`); } else if (redpandaCloudUrl) { // Fallback to Cloud URL if available urlsArray.push(`<a href="${redpandaCloudUrl}">${capitalize(type)}</a>`); } } }); // Filter out duplicates in URLs array for unique types const uniqueUrls = [...new Set(urlsArray)]; return uniqueUrls.join(', '); // Return the types as a string of links }) .filter(item => item !== '') // Remove any empty entries .join(', '); // Join them into a single string let supportLevelStr = ''; // Initialize the variable // Generate the support level string const supportLevels = Array.from(types.entries()) .reduce((supportLevelMap, [type, commercialNames]) => { Object.entries(commercialNames).forEach(([commercialName, { supportLevel }]) => { if (!supportLevelMap[supportLevel]) { supportLevelMap[supportLevel] = { types: new Set(), commercialNames: new Map() // To track commercial names for each type }; } supportLevelMap[supportLevel].types.add(type); // Add the type to the Set (automatically removes duplicates) // Add the commercial name to the type (only if it's not the connector name) if (!supportLevelMap[supportLevel].commercialNames.has(type)) { supportLevelMap[supportLevel].commercialNames.set(type, new Set()); } if (commercialName.toLowerCase() !== connector.toLowerCase()) { supportLevelMap[supportLevel].commercialNames.get(type).add(commercialName); } }); return supportLevelMap; }, {}); // Generate the support level string supportLevelStr = Object.entries(supportLevels) .map(([supportLevel, { types, commercialNames }]) => { const allCommercialNames = new Set(); // Store all commercial names for this support level // Collect all commercial names across types Array.from(commercialNames.entries()).forEach(([type, namesSet]) => { namesSet.forEach(name => { allCommercialNames.add(name); }); }); // Case: Multiple support levels but no commercial names listed if (Object.keys(supportLevels).length > 1 && allCommercialNames.size === 0 && types.size !== 0) { const typesList = Array.from(types).join(', '); // Get all types return `<p><b>${capitalize(supportLevel)}</b>: ${typesList}</p>`; } // If there's more than one commercial name, display them if (allCommercialNames.size > 1) { const allNamesArray = Array.from(allCommercialNames).join(', '); return `<p><b>${capitalize(supportLevel)}</b>: ${allNamesArray}</p>`; } // Otherwise, just show the support level return `<p>${capitalize(supportLevel)}</p>`; }) .join(''); // Add SQL driver support levels if the connector is a SQL connector. // We assume only connectors starting with sql_ are relevant. if (connector.startsWith('sql_')) { const certifiedDrivers = sqlDrivers.certified.length ? `<strong>Certified:</strong> ${sqlDrivers.certified.map(driver => driver.commercialName).join(', ')}` : ''; const communityDrivers = sqlDrivers.community.length ? `<strong>Community:</strong> ${sqlDrivers.community.map(driver => driver.commercialName).join(', ')}` : ''; // Add the SQL driver support to the support level string if (certifiedDrivers || communityDrivers) { // Reset the support levels supportLevelStr = '' supportLevelStr += `<p>${certifiedDrivers}${certifiedDrivers && communityDrivers ? '</br> ' : ''}${communityDrivers}</p>`; } } // Build the cloud support column and include the connector URL where a connector page is available. Otherwise, just mark as available. const firstCloudSupportedType = Array.from(types.entries()) .map(([_, commercialNames]) => Object.values(commercialNames).find(({ isCloudSupported }) => isCloudSupported)) .find(entry => entry); const cloudLinkDisplay = firstCloudSupportedType ? firstCloudSupportedType.urls.redpandaCloudUrl ? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a>` : `Yes` : 'No'; const firstUrl = getFirstUrlFromTypesArray(Array.from(types.entries()), isCloud); // Logic for showAllInfo = true and isCloud = false if (showAllInfo && !isCloud) { return ` <tr id="row-${id}"> <td class="tableblock halign-left valign-top" id="componentName-${id}"> <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p> </td> <td class="tableblock halign-left valign-top" id="componentType-${id}"> <p class="tableblock">${typesArray}</p> <!-- Display types linked to Connect URL only --> </td> <td class="tableblock halign-left valign-top" id="componentSupport-${id}"> <p class="tableblock">${supportLevelStr.trim()}</p> <!-- Display support levels by type --> </td> <td class="tableblock halign-left valign-top" id="componentLicense-${id}"> <p class="tableblock">${isLicensed}</p> </td> <td class="tableblock halign-left valign-top" id="componentCloud-${id}"> <p class="tableblock">${cloudLinkDisplay}</p> <!-- Display 'Yes' or 'No' with link to first cloud-supported type --> </td> </tr>`; } // Logic for isCloud = true and showAllInfo = false (Cloud Table) if (isCloud && !showAllInfo) { return ` <tr id="row-${id}"> <td class="tableblock halign-left valign-top" id="componentName-${id}"> <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p> </td> <td class="tableblock halign-left valign-top" id="componentType-${id}"> ${typesArray} <!-- Display bulleted list for cloud types if commercial name differs --> </td> </tr>`; } // Default table display return ` <tr id="row-${id}"> <td class="tableblock halign-left valign-top" id="componentName-${id}"> <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p> </td> <td class="tableblock halign-left valign-top" id="componentType-${id}"> <p class="tableblock">${typesArray}</p> <!-- Display types without commercial names --> </td> <td class="tableblock halign-left valign-top" id="componentSupport-${id}"> <p class="tableblock">${supportLevelStr.trim()}</p> </td> <td class="tableblock halign-left valign-top" id="componentLicense-${id}"> <p class="tableblock">${isLicensed}</p> </td> </tr>`; }) .filter(row => row !== '') .join(''); // Filter out empty rows } /** * Registers a block macro to generate a searchable and sortable table displaying connector data. * * This macro creates a dynamic HTML table that lists all available connectors, allowing filtering and sorting * by type, support level, and cloud support. * * * The table includes: * - Name: The name of the connector. * - Connector Type: The type of the connector. * - Support Level: The support level for each connector, including associated SQL drivers if applicable. * - Enterprise Licensed: Indicates whether the connector requires an Enterprise license. * - Cloud Support: Shows if the connector is supported in Redpanda Cloud. * * Filters: * - Type: Allows the user to filter by connector type. * - Support: Allows the user to filter by support level (if not in cloud view). * - Search: A text input field to search for connectors by name. * * Attributes: * - `all`: If specified, displays additional columns such as support level, enterprise licensing, and cloud support. * * Data Sources: * - `csvData`: Parsed CSV data that provides details about each connector. * - SQL driver data is processed separately using the `processSqlDrivers` function, which groups the drivers by support level. * * Example usage in AsciiDoc: * ``` * component_table::[] * ``` * * Example output: * ``` * | Name | Connector Type | Support Level | Enterprise Licensed | Cloud Support | * |-------|----------------|---------------- |---------------------|-----| * | SQL | Input, Output | Certified | No | No | * ``` * * @param {Object} parent - The parent document where the table will be inserted. * @param {string} target - Target element. * @param {Object} attributes - Positional attributes passed to the macro. * - `all`: If provided, extra columns are shown. */ registry.blockMacro(function () { const self = this; self.named('component_table'); self.positionalAttributes(['all']); // Allows for displaying all data self.process((parent, target, attributes) => { const isCloud = parent.getDocument().getAttributes()['env-cloud'] !== undefined; const showAllInfo = attributes?.all const csvData = context.config?.attributes?.csvData || null; if (!csvData) return console.error(`CSV data is not available for ${parent.getDocument().getAttributes()['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-info extension.`) const sqlDriversData = processSqlDrivers(csvData); const types = new Set(); const uniqueSupportLevel = new Set(); csvData.data.forEach(row => { if (row.type && row.type.toLowerCase() !== 'sql_driver') types.add(row.type); if (row.support_level) uniqueSupportLevel.add(row.support_level); }); const createDropdownCheckboxOptions = (values, id) => Array.from(values) .map(value => ` <label class="dropdown-checkbox-option"> <input type="checkbox" value="${value}" checked onchange="filterComponentTable()"> <span>${capitalize(value).replace("_", " ")}</span> </label>`) .join(''); let tableHtml = ` <div class="table-filters"> <input class="table-search" type="text" id="componentTableSearch" onkeyup="filterComponentTable()" placeholder="Search for components..."> <div class="filter-group"> <label for="typeFilterToggle">Type:</label> <div class="dropdown-checkbox-wrapper"> <button type="button" class="dropdown-checkbox-toggle" id="typeFilterToggle" onclick="toggleDropdownCheckbox('typeFilter')" aria-expanded="false" aria-haspopup="true" aria-controls="typeFilterMenu"> <span class="dropdown-text">All Types Selected</span> <span class="dropdown-arrow">▼</span> </button> <div class="dropdown-checkbox-menu" id="typeFilterMenu" role="menu" aria-labelledby="typeFilterToggle"> ${createDropdownCheckboxOptions(types, 'typeFilter')} </div> </div> </div> `; if (!isCloud) { tableHtml += ` <div class="filter-group"> <label for="supportFilterToggle" id="labelForSupportFilter">Support:</label> <div class="dropdown-checkbox-wrapper"> <button type="button" class="dropdown-checkbox-toggle" id="supportFilterToggle" onclick="toggleDropdownCheckbox('supportFilter')" aria-expanded="false" aria-haspopup="true" aria-controls="supportFilterMenu"> <span class="dropdown-text">All Support Levels Selected</span> <span class="dropdown-arrow">▼</span> </button> <div class="dropdown-checkbox-menu" id="supportFilterMenu" role="menu" aria-labelledby="supportFilterToggle"> ${createDropdownCheckboxOptions(uniqueSupportLevel, 'supportFilter')} </div> </div> </div> `; } if (showAllInfo) { tableHtml += ` <div class="filter-group"> <label for="cloudSupportFilterToggle">Available in Cloud:</label> <div class="dropdown-checkbox-wrapper"> <button type="button" class="dropdown-checkbox-toggle" id="cloudSupportFilterToggle" onclick="toggleDropdownCheckbox('cloudSupportFilter')" aria-expanded="false" aria-haspopup="true" aria-controls="cloudSupportFilterMenu"> <span class="dropdown-text">All Options Selected</span> <span class="dropdown-arrow">▼</span> </button> <div class="dropdown-checkbox-menu" id="cloudSupportFilterMenu" role="menu" aria-labelledby="cloudSupportFilterToggle"> <label class="dropdown-checkbox-option"> <input type="checkbox" value="yes" checked onchange="filterComponentTable()"> <span>Yes</span> </label> <label class="dropdown-checkbox-option"> <input type="checkbox" value="no" checked onchange="filterComponentTable()"> <span>No</span> </label> </div> </div> </div> <div class="filter-group"> <label for="enterpriseFilterToggle">Enterprise License:</label> <div class="dropdown-checkbox-wrapper"> <button type="button" class="dropdown-checkbox-toggle" id="enterpriseFilterToggle" onclick="toggleDropdownCheckbox('enterpriseFilter')" aria-expanded="false" aria-haspopup="true" aria-controls="enterpriseFilterMenu"> <span class="dropdown-text">All Options Selected</span> <span class="dropdown-arrow">▼</span> </button> <div class="dropdown-checkbox-menu" id="enterpriseFilterMenu" role="menu" aria-labelledby="enterpriseFilterToggle"> <label class="dropdown-checkbox-option"> <input type="checkbox" value="yes" checked onchange="filterComponentTable()"> <span>Yes</span> </label> <label class="dropdown-checkbox-option"> <input type="checkbox" value="no" checked onchange="filterComponentTable()"> <span>No</span> </label> </div> </div> </div> `; } tableHtml += `</div> <!-- CSS styles are defined in the external redpanda-connect-filters.css stylesheet --> <table class="tableblock frame-all grid-all stripes-even no-clip stretch component-table sortable" id="componentTable"> <colgroup> ${showAllInfo ? '<col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;">' : isCloud ? '<col style="width: 50%;"><col style="width: 50%;">' : '<col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;">'} </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Name</th> <th class="tableblock halign-left valign-top">Connector Type</th> ${showAllInfo ? ` <th class="tableblock halign-left valign-top">Support Level</th> <th class="tableblock halign-left valign-top">Enterprise Licensed</th> <th class="tableblock halign-left valign-top">Available in Cloud</th> ` : isCloud ? '' : ` <th class="tableblock halign-left valign-top">Support Level</th> <th class="tableblock halign-left valign-top">Enterprise Licensed</th>`} </tr> </thead> <tbody> ${generateConnectorsHTMLTable(processConnectors(csvData), sqlDriversData, isCloud, showAllInfo)} </tbody> </table> <script> ${filterComponentTable.toString()} ${updateURLParameters.toString()} function getQueryParams() { const params = {}; const searchParams = new URLSearchParams(window.location.search); searchParams.forEach((value, key) => { params[key] = value.toLowerCase(); }); return params; } // Define global dropdown functions (shared between macros) window.initializeDropdownFunctions = window.initializeDropdownFunctions || function() { // Component type dropdown toggle function window.toggleComponentTypeDropdown = function() { const toggle = document.getElementById('componentTypeDropdownToggle'); const menu = document.getElementById('componentTypeDropdownMenu'); if (!toggle || !menu) return; const isOpen = menu.classList.contains('show'); // Close all other dropdowns first (including filter dropdowns) document.querySelectorAll('.dropdown-checkbox-menu.show, .dropdown-menu.show').forEach(dropdown => { if (dropdown !== menu) { dropdown.classList.remove('show'); const otherToggle = dropdown.parentNode.querySelector('.dropdown-checkbox-toggle, .dropdown-toggle'); if (otherToggle) { otherToggle.classList.remove('open'); otherToggle.setAttribute('aria-expanded', 'false'); } } }); // Toggle current dropdown if (isOpen) { menu.classList.remove('show'); toggle.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); } else { menu.classList.add('show'); toggle.classList.add('open'); toggle.setAttribute('aria-expanded', 'true'); // Focus first option const firstOption = menu.querySelector('.dropdown-option'); if (firstOption) firstOption.focus(); } }; }; // Initialize the functions window.initializeDropdownFunctions(); function toggleDropdownCheckbox(filterId) { const toggle = document.getElementById(filterId + 'Toggle'); const menu = document.getElementById(filterId + 'Menu'); if (!toggle || !menu) return; const isOpen = menu.classList.contains('show'); // Close all other dropdowns first document.querySelectorAll('.dropdown-checkbox-menu.show').forEach(dropdown => { if (dropdown !== menu) { dropdown.classList.remove('show'); const otherToggle = dropdown.parentNode.querySelector('.dropdown-checkbox-toggle'); if (otherToggle) { otherToggle.classList.remove('open'); otherToggle.setAttribute('aria-expanded', 'false'); } } }); // Toggle current dropdown if (isOpen) { menu.classList.remove('show'); toggle.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); } else { menu.classList.add('show'); toggle.classList.add('open'); toggle.setAttribute('aria-expanded', 'true'); } } function updateDropdownText(filterId, allSelectedText, someSelectedText) { const menu = document.getElementById(filterId + 'Menu'); const toggle = document.getElementById(filterId + 'Toggle'); if (!menu || !toggle) return; const checkboxes = menu.querySelectorAll('input[type="checkbox"]'); const checkedCount = menu.querySelectorAll('input[type="checkbox"]:checked').length; const totalCount = checkboxes.length; const textElement = toggle.querySelector('.dropdown-text'); if (!textElement) return; if (checkedCount === 0) { textElement.textContent = 'None Selected'; } else if (checkedCount === totalCount) { textElement.textContent = allSelectedText; } else if (checkedCount === 1) { const checkedBox = menu.querySelector('input[type="checkbox"]:checked'); if (checkedBox) { const label = checkedBox.nextElementSibling; textElement.textContent = label ? label.textContent : getSingularText(someSelectedText); } } else { textElement.textContent = checkedCount + ' ' + someSelectedText; } } function getSingularText(pluralText) { // Handle various plural patterns and convert to singular if (pluralText.includes('Types Selected')) { return 'Type Selected'; } else if (pluralText.includes('Support Levels Selected')) { return 'Support Level Selected'; } else if (pluralText.includes('Options Selected')) { return 'Option Selected'; } else if (pluralText.includes('Items Selected')) { return 'Item Selected'; } else if (pluralText.includes('Categories Selected')) { return 'Category Selected'; } else if (pluralText.includes('Filters Selected')) { return 'Filter Selected'; } else if (pluralText.endsWith('s Selected')) { // Generic fallback for words ending in 's Selected' return pluralText.replace(/s Selected$/, ' Selected'); } else if (pluralText.endsWith('ies Selected')) { // Handle words ending in 'ies' (e.g., "Categories Selected" -> "Category Selected") return pluralText.replace(/ies Selected$/, 'y Selected'); } else { // If no pattern matches, return as-is return pluralText; } } // Close dropdown when clicking outside (local handler for filter dropdowns only) document.addEventListener('click', function(event) { if (!event.target.closest('.dropdown-checkbox-wrapper')) { document.querySelectorAll('.dropdown-checkbox-menu.show').forEach(menu => { menu.classList.remove('show'); const toggle = menu.parentNode.querySelector('.dropdown-checkbox-toggle'); if (toggle) { toggle.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); } }); } }); // Add keyboard navigation support (local handler for filter dropdowns only) document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { // Close all open filter dropdowns on Escape document.querySelectorAll('.dropdown-checkbox-menu.show').forEach(menu => { menu.classList.remove('show'); const toggle = menu.parentNode.querySelector('.dropdown-checkbox-toggle'); if (toggle) { toggle.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); toggle.focus(); // Return focus to toggle button } }); } }); // Initialize filters from URL parameters document.addEventListener('DOMContentLoaded', function() { const params = getQueryParams(); const search = document.getElementById('componentTableSearch'); const typeFilterMenu = document.getElementById('typeFilterMenu'); const supportFilterMenu = document.getElementById('supportFilterMenu'); const cloudSupportFilterMenu = document.getElementById('cloudSupportFilterMenu'); const enterpriseFilterMenu = document.getElementById('enterpriseFilterMenu'); if (params.search && search) { search.value = params.search; } if (params.type && typeFilterMenu) { const types = params.type.split(','); // First uncheck all checkboxes typeFilterMenu.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); // Then check only the ones in the URL types.forEach(type => { const checkbox = typeFilterMenu.querySelector(\`input[value="\${type}"]\`); if (checkbox) checkbox.checked = true; }); } if (params.support && supportFilterMenu) { const supports = params.support.split(','); // First uncheck all checkboxes supportFilterMenu.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); // Then check only the ones in the URL supports.forEach(support => { const checkbox = supportFilterMenu.querySelector(\`input[value="\${support}"]\`); if (checkbox) checkbox.checked = true; }); } if (params.cloud && cloudSupportFilterMenu) { const cloudOptions = params.cloud.split(','); // First uncheck all checkboxes cloudSupportFilterMenu.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); // Then check only the ones in the URL cloudOptions.forEach(option => { const checkbox = cloudSupportFilterMenu.querySelector(\`input[value="\${option}"]\`); if (checkbox) checkbox.checked = true; }); } if (params.enterprise && enterpriseFilterMenu) { const enterpriseOptions = params.enterprise.split(','); // First uncheck all checkboxes enterpriseFilterMenu.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); // Then check only the ones in the URL enterpriseOptions.forEach(option => { const checkbox = enterpriseFilterMenu.querySelector(\`input[value="\${option}"]\`); if (checkbox) checkbox.checked = true; }); } filterComponentTable(); }); </script> `; return self.createBlock(parent, 'pass', tableHtml); }); }); /** * Registers a block macro to display metadata about the selected component. * * This macro creates a dropdown to select different types of a connector component, such as Input, Output, or Processor. * It also provides links to the corresponding Cloud or Self-Managed documentation for the selected component type, and displays information on whether the connector requires an enterprise license. * * * The dropdown lists all types of the connector component: * - Type: A dropdown with options such as Input, Output, Processor, etc. * * Information displayed includes: * - Availability: Displays links to Cloud and Self-Managed (Connect) documentation. * - License: If the component requires an enterprise license, a message is displayed with a link to upgrade. * * Data Sources: * - `csvData`: Parsed CSV data providing details about each connector. * It filters the data to find the relevant rows for the current connector by matching the `doctitle`. * - `redpandaConnectUrl`: URL for the Self-Managed version of the component documentation. * - `redpandaCloudUrl`: URL for the Cloud version of the component documentation. * * Example usage in AsciiDoc: * ``` * component_type_dropdown::[] * ``` * * Example output: * ``` * <div class="metadata-block"> * <div style="padding:10px;display: flex;flex-direction: column;gap: 6px;"> * <p style="display: flex;align-items: center;gap: 6px;"><strong>Type:</strong> * <select class="type-dropdown" onchange="window.location.href=this.value"> * <option value="..." data-support="certified">Input</option> * <option value="..." data-support="community">Output</option> * </select> * </p> * <p><strong>Available in:</strong> <a href="...">Cloud</a>, <a href="...">Self-Managed</a></p> * <p><strong>License</strong>: This component requires an <a href="https://redpanda.com/compare-platform-editions" target="_blank">Enterprise license</a>. To upgrade, contact <a href="https://redpanda.com/try-redpanda?section=enterprise-trial" target="_blank" rel="noopener">Redpanda sales</a>.</p> * </div> * </div> * ``` * * @param {Object} parent - The parent document where the dropdown will be inserted. * @param {strin