@redpanda-data/docs-extensions-and-macros 3.7.0 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,11 @@
1
- /* Example use in the playbook
2
- * antora:
3
- extensions:
4
- * - require: ./extensions/generate-rp-connect-info.js
5
- */
6
-
7
1
  'use strict'
8
2
  const fs = require('fs');
9
3
  const path = require('path');
10
4
  const Papa = require('papaparse');
11
5
 
12
- const CSV_PATH = 'redpanda_connect.csv'
6
+ const CSV_PATH = 'internal/plugins/info.csv'
13
7
  const GITHUB_OWNER = 'redpanda-data'
14
- const GITHUB_REPO = 'rp-connect-docs'
8
+ const GITHUB_REPO = 'connect'
15
9
  const GITHUB_REF = 'main'
16
10
 
17
11
  module.exports.register = function ({ config }) {
@@ -28,6 +22,7 @@ module.exports.register = function ({ config }) {
28
22
  this.once('contentClassified', async ({ contentCatalog }) => {
29
23
  const redpandaConnect = contentCatalog.getComponents().find(component => component.name === 'redpanda-connect');
30
24
  const redpandaCloud = contentCatalog.getComponents().find(component => component.name === 'redpanda-cloud');
25
+ const preview = contentCatalog.getComponents().find(component => component.name === 'preview');
31
26
  if (!redpandaConnect) return;
32
27
  const pages = contentCatalog.getPages();
33
28
 
@@ -35,7 +30,7 @@ module.exports.register = function ({ config }) {
35
30
  // Fetch CSV data (either from local file or GitHub)
36
31
  const csvData = await fetchCSV(config.csvpath);
37
32
  const parsedData = Papa.parse(csvData, { header: true, skipEmptyLines: true });
38
- const enrichedData = enrichCsvDataWithUrls(parsedData, pages, logger);
33
+ const enrichedData = translateCsvData(parsedData, pages, logger);
39
34
  parsedData.data = enrichedData;
40
35
 
41
36
  if (redpandaConnect) {
@@ -44,6 +39,10 @@ module.exports.register = function ({ config }) {
44
39
  if (redpandaCloud) {
45
40
  redpandaCloud.latest.asciidoc.attributes.csvData = parsedData;
46
41
  }
42
+ // For previewing the data on our extensions site
43
+ if (preview) {
44
+ preview.latest.asciidoc.attributes.csvData = parsedData;
45
+ }
47
46
 
48
47
  } catch (error) {
49
48
  logger.error('Error fetching or parsing CSV data:', error.message);
@@ -82,28 +81,82 @@ module.exports.register = function ({ config }) {
82
81
  }
83
82
  }
84
83
 
85
- function enrichCsvDataWithUrls(parsedData, connectPages, logger) {
84
+ /**
85
+ * Translates the parsed CSV data into our expected format.
86
+ * If "enterprise" is found in the `support` column, it is replaced with "certified" in the output.
87
+ *
88
+ * @param {object} parsedData - The CSV data parsed into an object.
89
+ * @param {array} pages - The list of pages to map the URLs (used for enrichment with URLs).
90
+ * @param {object} logger - The logger used for error handling.
91
+ *
92
+ * @returns {array} - The translated and enriched data.
93
+ */
94
+ function translateCsvData(parsedData, pages, logger) {
86
95
  return parsedData.data.map(row => {
87
96
  // Create a new object with trimmed keys and values
88
97
  const trimmedRow = Object.fromEntries(
89
98
  Object.entries(row).map(([key, value]) => [key.trim(), value.trim()])
90
99
  );
91
- const connector = trimmedRow.connector;
100
+
101
+ // Map fields from the trimmed row to the desired output
102
+ const connector = trimmedRow.name;
92
103
  const type = trimmedRow.type;
93
- let url = '';
94
- for (const file of connectPages) {
104
+ const commercial_name = trimmedRow.commercial_name;
105
+ const available_connect_version = trimmedRow.version;
106
+ const deprecated = trimmedRow.deprecated.toLowerCase() === 'y' ? 'y' : 'n';
107
+ const is_cloud_supported = trimmedRow.cloud.toLowerCase() === 'y' ? 'y' : 'n';
108
+ const cloud_ai = trimmedRow.cloud_with_gpu.toLowerCase() === 'y' ? 'y' : 'n';
109
+ // Handle enterprise to certified conversion and set enterprise license flag
110
+ const originalSupport = trimmedRow.support.toLowerCase();
111
+ const support_level = originalSupport === 'enterprise' ? 'certified' : originalSupport;
112
+ const is_licensed = originalSupport === 'enterprise' ? 'Yes' : 'No';
113
+
114
+ // Redpanda Connect and Cloud enrichment URLs
115
+ let redpandaConnectUrl = '';
116
+ let redpandaCloudUrl = '';
117
+
118
+ // Look for both Redpanda Connect and Cloud URLs
119
+ for (const file of pages) {
120
+ const component = file.src.component;
95
121
  const filePath = file.path;
96
- if (filePath.endsWith(`${connector}.adoc`) && filePath.includes(`pages/${type}s/`)) {
97
- url = `../${type}s/${connector}`;
98
- break;
122
+
123
+ if (
124
+ component === 'redpanda-connect' &&
125
+ filePath.endsWith(`/${connector}.adoc`) &&
126
+ filePath.includes(`pages/${type}s/`)
127
+ ) {
128
+ redpandaConnectUrl = file.pub.url;
129
+ }
130
+
131
+ // Only check for Redpanda Cloud URLs if cloud is supported
132
+ if (
133
+ is_cloud_supported === 'y' &&
134
+ component === 'redpanda-cloud' &&
135
+ filePath.endsWith(`/${connector}.adoc`) &&
136
+ filePath.includes(`${type}s/`)
137
+ ) {
138
+ redpandaCloudUrl = file.pub.url;
99
139
  }
100
140
  }
101
- if (!url) {
102
- logger.warn(`No matching URL found for connector: ${connector} of type: ${type}`);
141
+
142
+ // Log a warning if neither URL was found (only warn for missing cloud if it should support cloud)
143
+ if (!redpandaConnectUrl && (!redpandaCloudUrl && is_cloud_supported === 'y')) {
144
+ logger.info(`Docs missing for: ${connector} of type: ${type}`);
103
145
  }
146
+
147
+ // Return the translated and enriched row
104
148
  return {
105
- ...trimmedRow,
106
- url: url
149
+ connector,
150
+ type,
151
+ commercial_name,
152
+ available_connect_version,
153
+ support_level, // "enterprise" is replaced with "certified"
154
+ deprecated,
155
+ is_cloud_supported,
156
+ cloud_ai,
157
+ is_licensed, // "Yes" if the original support level was "enterprise"
158
+ redpandaConnectUrl,
159
+ redpandaCloudUrl,
107
160
  };
108
161
  });
109
162
  }
@@ -1,9 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * Registers macros for use in Redpanda Connect contexts in the Redpanda documentation.
5
+ * @param {Registry} registry - The Antora registry where this block macro is registered.
6
+ * @param {Object} context - The Antora context that provides access to configuration data, such as parsed CSV content.
7
+ */
3
8
  module.exports.register = function (registry, context) {
4
9
  function filterComponentTable() {
5
10
  const nameInput = document.getElementById('componentTableSearch').value.trim().toLowerCase();
6
11
  const typeFilter = Array.from(document.querySelector('#typeFilter').selectedOptions).map(option => option.value);
12
+ const cloudSupportInput= document.getElementById('cloudSupportFilter')?.value;
7
13
 
8
14
  // Check for the existence of support and enterprise license filters (optional)
9
15
  const supportFilterElement = document.querySelector('#supportFilter');
@@ -11,9 +17,9 @@ module.exports.register = function (registry, context) {
11
17
  ? Array.from(supportFilterElement.selectedOptions).map(option => option.value)
12
18
  : [];
13
19
 
14
- // Get the 'support=enterprise' query parameter from the URL
15
20
  const params = getQueryParams();
16
21
  const enterpriseSupportFilter = params.support === 'enterprise'; // Check if 'support=enterprise' is in the URL
22
+ const cloudSupportFilter = params.support === 'cloud'; // Check if 'support=cloud' is in the URL
17
23
 
18
24
  const table = document.getElementById('componentTable');
19
25
  const trs = table.getElementsByTagName('tr');
@@ -24,12 +30,14 @@ module.exports.register = function (registry, context) {
24
30
  const typeTd = row.querySelector('td[id^="componentType-"]');
25
31
  const supportTd = row.querySelector('td[id^="componentSupport-"]'); // Support column, if present
26
32
  const enterpriseSupportTd = row.querySelector('td[id^="componentLicense-"]'); // Enterprise License column, if present
33
+ const cloudSupportTd = row.querySelector('td[id^="componentCloud-"]'); // Cloud support column, if present
27
34
 
28
35
  if (typeTd) { // Ensure that at least the Type column is present
29
36
  const nameText = nameTd ? nameTd.textContent.trim().toLowerCase() : '';
30
37
  const typeText = typeTd.textContent.trim().toLowerCase().split(', ').map(item => item.trim());
31
38
  const supportText = supportTd ? supportTd.textContent.trim().toLowerCase() : '';
32
39
  const enterpriseSupportText = enterpriseSupportTd ? enterpriseSupportTd.textContent.trim().toLowerCase() : ''; // Yes or No
40
+ const cloudSupportText = cloudSupportTd ? cloudSupportTd.textContent.trim().toLowerCase() : ''; // Yes or No
33
41
 
34
42
  // Determine if the row should be shown
35
43
  const showRow =
@@ -37,6 +45,9 @@ module.exports.register = function (registry, context) {
37
45
  (typeFilter.length === 0 || typeFilter.some(value => typeText.includes(value))) && // Filter by type
38
46
  (!supportTd || supportFilter.length === 0 || supportFilter.some(value => supportText.includes(value))) && // Filter by support if present
39
47
  (!enterpriseSupportFilter || !enterpriseSupportTd || supportText.includes('enterprise') || enterpriseSupportText === 'yes') // Filter by enterprise support if 'support=enterprise' is in the URL
48
+ &&
49
+ (!cloudSupportFilter || !cloudSupportTd || supportText.includes('cloud') || cloudSupportText === 'yes') && // Filter by cloud support if 'support=cloud' is in the URL
50
+ (!cloudSupportInput || cloudSupportText === cloudSupportInput)
40
51
  );
41
52
 
42
53
  row.style.display = showRow ? '' : 'none';
@@ -46,198 +57,468 @@ module.exports.register = function (registry, context) {
46
57
  }
47
58
  }
48
59
 
49
- const capitalize = s => s && s[0].toUpperCase() + s.slice(1);
50
-
51
- function processConnectors(parsedData) {
52
- return parsedData.data.reduce((connectors, row) => {
53
- const { connector, commercial_name, type, support_level, is_cloud_supported, is_licensed, url } = row;
54
- const isCloudSupported = is_cloud_supported === 'y';
55
-
56
- if (!connectors[connector]) {
57
- connectors[connector] = {
58
- types: new Map(),
59
- supportLevels: new Map(),
60
- isLicensed: is_licensed === 'y' ? 'Yes' : 'No',
61
- isCloudConnectorSupported: false,
62
- urls: new Set()
63
- };
60
+ /**
61
+ * Gets the first URL (either Redpanda Connect or Redpanda Cloud) for a given connector from the typesArray.
62
+ * If the cloud option is enabled (`isCloud = true`), it prefers the Redpanda Cloud URL; otherwise, it returns the Redpanda Connect URL.
63
+ *
64
+ * @param {Array} typesArray - An array of types where each type has a list of commercial names with URLs.
65
+ * @param {boolean} isCloud - A flag to indicate if Cloud URLs should be prioritized.
66
+ * @returns {string} - The first found URL (either Redpanda Connect or Cloud), or an empty string if no URL is available.
67
+ */
68
+ function getFirstUrlFromTypesArray(typesArray, isCloud) {
69
+ for (const [type, commercialNames] of typesArray) {
70
+ for (const commercialName in commercialNames) {
71
+ const { urls = {} } = commercialNames[commercialName];
72
+ const redpandaConnectUrl = urls.redpandaConnectUrl || '';
73
+ const redpandaCloudUrl = urls.redpandaCloudUrl || '';
74
+
75
+ // Return Cloud URL if isCloud is true and Cloud URL exists
76
+ if (isCloud && redpandaCloudUrl) {
77
+ return redpandaCloudUrl;
64
78
  }
65
- connectors[connector].types.set(capitalize(type), { url });
66
79
 
67
- // Check at the connector level if any type supports cloud
68
- if (isCloudSupported) {
69
- connectors[connector].isCloudConnectorSupported = true;
80
+ // Return Connect URL if isCloud is false or no Cloud URL exists
81
+ if (!isCloud && redpandaConnectUrl) {
82
+ return redpandaConnectUrl;
70
83
  }
71
84
 
72
- // Update supportLevels with commercial name and cloud support info
73
- if (!connectors[connector].supportLevels.has(support_level)) {
74
- connectors[connector].supportLevels.set(support_level, []);
85
+ // If Cloud URL exists but isCloud is false, fallback to Cloud URL if no Connect URL exists
86
+ if (!isCloud && redpandaCloudUrl) {
87
+ return redpandaCloudUrl;
75
88
  }
89
+ }
90
+ }
91
+ return ''; // Return an empty string if no URL is found
92
+ }
76
93
 
77
- connectors[connector].supportLevels.get(support_level).push({
78
- commercial_name,
79
- isCloudSupported
80
- });
81
-
82
- if (url) connectors[connector].urls.add(url);
94
+ const capitalize = s => s && s[0].toUpperCase() + s.slice(1);
83
95
 
84
- return connectors;
85
- }, {});
86
- }
96
+ /**
97
+ * Processes the parsed CSV data and returns a data structure organized by connector.
98
+ *
99
+ * This function processes each row in the CSV data to create a nested object where the key is the connector name.
100
+ * Each connector contains:
101
+ * - `types`: A Map of connector types, with associated URLs for Redpanda Connect and Redpanda Cloud.
102
+ * - Each type maps to commercial names and stores information on URLs, support level, and cloud support.
103
+ * - `supportLevels`: A Map of support levels containing commercial names and whether the type supports cloud.
104
+ * - `isLicensed`: A boolean flag indicating whether the connector requires an enterprise license.
105
+ * - `isCloudConnectorSupported`: A boolean flag indicating whether any type of this connector supports Redpanda Cloud.
106
+ *
107
+ * Expected structure of the returned data:
108
+ *
109
+ * {
110
+ * "connectorName": {
111
+ * "types": Map {
112
+ * "Input": { // Connector Type
113
+ * "commercial_name": {
114
+ * urls: {
115
+ * redpandaConnectUrl: "/redpanda-connect/components/inputs/connectorName/",
116
+ * redpandaCloudUrl: "/redpanda-cloud/develop/connect/components/inputs/connectorName/"
117
+ * },
118
+ * supportLevel: "certified", // Support level for this commercial name
119
+ * isCloudSupported: true // Whether this type supports cloud
120
+ * },
121
+ * ...
122
+ * },
123
+ * "Output": { // Another Connector Type
124
+ * "commercial_name": {
125
+ * urls: {
126
+ * redpandaConnectUrl: "/redpanda-connect/components/outputs/connectorName/",
127
+ * redpandaCloudUrl: "/redpanda-cloud/develop/connect/components/outputs/connectorName/"
128
+ * },
129
+ * supportLevel: "community", // Support level for this commercial name
130
+ * isCloudSupported: false // Whether this type supports cloud
131
+ * },
132
+ * ...
133
+ * },
134
+ * ...
135
+ * },
136
+ * "isLicensed": "Yes" or "No", // Indicates if the connector requires an Enterprise license.
137
+ * "isCloudConnectorSupported": true or false // Indicates if any type for this connector supports Redpanda Cloud.
138
+ * },
139
+ * ...
140
+ * }
141
+ *
142
+ * Notes:
143
+ * - For each connector, `types` is a `Map` that contains multiple connector types.
144
+ * - For each type, there may be multiple commercial names. Each commercial name contains URLs, support levels, and cloud support flags.
145
+ * - The `isCloudConnectorSupported` flag is set to `true` if any of the types for the connector support cloud.
146
+ *
147
+ * @param {object} parsedData - The CSV data parsed into an object.
148
+ * @returns {object} - The processed connectors data structure.
149
+ */
150
+ function processConnectors(parsedData) {
151
+ return parsedData.data.reduce((connectors, row) => {
152
+ const { connector, commercial_name, type, support_level, is_cloud_supported, is_licensed, redpandaConnectUrl, redpandaCloudUrl } = row;
153
+ const isCloudSupported = is_cloud_supported === 'y';
154
+
155
+ // Initialize the connector if it's not already in the map
156
+ if (!connectors[connector]) {
157
+ connectors[connector] = {
158
+ types: new Map(),
159
+ isLicensed: is_licensed,
160
+ isCloudConnectorSupported: false
161
+ };
162
+ }
87
163
 
164
+ // Ensure type exists for the connector
165
+ if (!connectors[connector].types.has(type)) {
166
+ connectors[connector].types.set(type, {});
167
+ }
88
168
 
169
+ // Store the commercial name under the type
170
+ if (!connectors[connector].types.get(type)[commercial_name]) {
171
+ connectors[connector].types.get(type)[commercial_name] = {
172
+ urls: {
173
+ redpandaConnectUrl: redpandaConnectUrl || '',
174
+ redpandaCloudUrl: redpandaCloudUrl || ''
175
+ },
176
+ supportLevel: support_level,
177
+ isCloudSupported: isCloudSupported
178
+ };
179
+ }
89
180
 
90
- function generateConnectorsHTMLTable(connectors, isCloud) {
91
- return Object.entries(connectors).map(([connector, details], id) => {
92
- const { types, supportLevels, isCloudConnectorSupported, isLicensed, urls } = details;
93
- const firstUrl = urls.size > 0 ? urls.values().next().value : null;
181
+ // Check at the connector level if any commercial name supports cloud
182
+ if (isCloudSupported) {
183
+ connectors[connector].isCloudConnectorSupported = true;
184
+ }
94
185
 
95
- const typesArray = Array.from(types.entries())
96
- .map(([type, { url }]) => {
97
- return url ? `<a href="${url}/">${type}</a>` : `<span>${type}</span>`;
98
- })
99
- .filter(item => item !== '');
186
+ return connectors;
187
+ }, {});
188
+ }
100
189
 
101
- const typesStr = typesArray.join(', ');
190
+ /**
191
+ * Processes parsed CSV data and groups SQL drivers by their support level.
192
+ *
193
+ * This function extracts the SQL drivers from the parsed CSV data, grouping
194
+ * them into two categories: "certified" and "community". Each driver is also
195
+ * associated with a flag indicating whether it supports cloud.
196
+ *
197
+ * @param {Object} parsedData - The parsed CSV data containing driver information.
198
+ * The expected structure of each row should contain at least the following:
199
+ * {
200
+ * connector: string, // The name of the connector
201
+ * commercial_name: string, // The commercial name of the SQL driver
202
+ * support_level: string, // The support level ('certified', 'community')
203
+ * is_cloud_supported: string // 'y' or 'n', indicating if the driver supports cloud
204
+ * }
205
+ *
206
+ * @returns {Object} - An object with two properties:
207
+ * - `certified`: An array of SQL drivers with 'certified' support level. Each driver contains:
208
+ * - `commercialName`: The trimmed commercial name of the driver (e.g., 'PostgreSQL').
209
+ * - `isCloudSupported`: A boolean indicating whether the driver supports cloud.
210
+ * - `community`: An array of SQL drivers with 'community' support level. Each driver contains:
211
+ * - `commercialName`: The trimmed commercial name of the driver (e.g., 'Trino').
212
+ * - `isCloudSupported`: A boolean indicating whether the driver supports cloud.
213
+ *
214
+ * Example return structure:
215
+ * {
216
+ * certified: [
217
+ * { commercialName: 'PostgreSQL', isCloudSupported: true },
218
+ * { commercialName: 'MySQL', isCloudSupported: true },
219
+ * ],
220
+ * community: [
221
+ * { commercialName: 'Trino', isCloudSupported: false },
222
+ * { commercialName: 'ClickHouse', isCloudSupported: false },
223
+ * ]
224
+ * }
225
+ */
226
+ function processSqlDrivers(parsedData) {
227
+ const sqlDrivers = {
228
+ certified: [],
229
+ community: []
230
+ };
231
+
232
+ parsedData.data.forEach(row => {
233
+ const { connector: driverName, commercial_name, support_level, is_cloud_supported } = row;
234
+ const isCloudSupported = is_cloud_supported === 'y';
235
+ const supportLevel = support_level.toLowerCase();
236
+
237
+ // Only process SQL drivers
238
+ if (driverName.startsWith('sql_driver')) {
239
+ const driverData = {
240
+ commercialName: commercial_name.trim(),
241
+ isCloudSupported: isCloudSupported
242
+ };
243
+
244
+ // Group drivers based on their support level
245
+ if (supportLevel === 'certified') {
246
+ sqlDrivers.certified.push(driverData);
247
+ } else if (supportLevel === 'community') {
248
+ sqlDrivers.community.push(driverData);
249
+ }
250
+ }
251
+ });
102
252
 
103
- const supportLevelStr = Array.from(supportLevels.entries())
104
- .sort(([levelA], [levelB]) => levelA.localeCompare(levelB)) // Sort by level alphabetically
105
- .map(([level, commercialNames]) => {
106
- let filteredNames = commercialNames;
253
+ return sqlDrivers;
254
+ }
107
255
 
108
- if (isCloud) {
109
- filteredNames = commercialNames
110
- .filter(({ isCloudSupported }) => isCloudSupported)
111
- .map(({ commercial_name }) => commercial_name);
112
- } else {
113
- filteredNames = commercialNames.map(({ commercial_name }) => commercial_name);
114
- }
115
- filteredNames = [...new Set(filteredNames)];
116
- if (filteredNames.length === 0) return '';
117
- if (supportLevels.size === 1) {
118
- return `<p>${capitalize(level)}</p>`;
119
- } else {
120
- return `<p><b>${capitalize(level)}</b>: ${filteredNames.join(', ')}</p>`;
121
- }
256
+ /**
257
+ * Generates an HTML table for the list of connectors, including their types, support levels, and cloud support.
258
+ *
259
+ * This function iterates over the provided connectors and generates an HTML table row for each connector.
260
+ * It includes type-specific information, support level (including SQL driver details), licensing, and cloud support.
261
+ *
262
+ * @param {Object} connectors - An object containing the connector data, where each key is a connector name and
263
+ * each value contains details about its types, licensing, and cloud support.
264
+ * {
265
+ * types: Map - A map of connector types (e.g., Input, Output, Processor), with associated commercial names.
266
+ * isLicensed: 'Yes' or 'No' - Indicates if the connector requires an enterprise license.
267
+ * isCloudConnectorSupported: true or false - Indicates if any type for this connector supports Redpanda Cloud.
268
+ * }
269
+ * @param {Object} sqlDrivers - An object containing the SQL driver support data, separated by support level:
270
+ * {
271
+ * certified: Array<{ commercialName: string, isCloudSupported: boolean }>,
272
+ * community: Array<{ commercialName: string, isCloudSupported: boolean }>
273
+ * }
274
+ * @param {boolean} isCloud - A flag indicating whether to filter by cloud support. If true, only cloud-supported connectors are shown.
275
+ * @param {boolean} showAllInfo - A flag indicating whether to show all information or limit the data displayed (e.g., for cloud-only views).
276
+ *
277
+ * @returns {string} - A string containing the generated HTML for the connectors table rows.
278
+ * The output is a string of HTML rows with the following columns:
279
+ * - Connector name
280
+ * - Connector types (linked to Redpanda Connect or Cloud documentation URLs)
281
+ * - Support levels (including SQL drivers if applicable)
282
+ * - Enterprise licensing information
283
+ * - Cloud support status (Yes/No with a link if applicable)
284
+ */
285
+ function generateConnectorsHTMLTable(connectors, sqlDrivers, isCloud, showAllInfo) {
286
+ return Object.entries(connectors)
287
+ .filter(([_, details]) => {
288
+
289
+ // If isCloud is true, filter out rows that do not support cloud
290
+ return !isCloud || details.isCloudConnectorSupported;
122
291
  })
123
- .filter(item => item !== '')
124
- .join('');
125
-
126
- const connectorNameHtml = firstUrl
127
- ? `<code><a href="${firstUrl}/">${connector}</a></code>`
128
- : `<code><span>${connector}</span></code>`;
129
-
130
- if (isCloud) {
131
- if (isCloudConnectorSupported && supportLevelStr.trim() !== '') {
132
- return `
133
- <tr id="row-${id}">
134
- <td class="tableblock halign-left valign-top" id="componentName-${id}">
135
- <p class="tableblock">${connectorNameHtml}</p>
136
- </td>
137
- <td class="tableblock halign-left valign-top" id="componentType-${id}">
138
- <p class="tableblock">${typesStr}</p>
139
- </td>
140
- </tr>`;
141
- } else {
142
- return '';
292
+ .map(([connector, details], id) => {
293
+ const { types, isCloudConnectorSupported, isLicensed } = details;
294
+
295
+ // Generate the type and commercial name links for each connector
296
+ const typesArray = Array.from(types.entries())
297
+ .map(([type, commercialNames]) => {
298
+ const uniqueCommercialNames = Object.keys(commercialNames);
299
+ const urlsArray = [];
300
+ uniqueCommercialNames.forEach(commercialName => {
301
+ const { urls = {}, isCloudSupported } = commercialNames[commercialName];
302
+ const redpandaConnectUrl = urls.redpandaConnectUrl || '';
303
+ const redpandaCloudUrl = urls.redpandaCloudUrl || '';
304
+ if (isCloud && !showAllInfo) {
305
+ // Only show Cloud URLs in the Cloud table
306
+ if (redpandaCloudUrl) {
307
+ urlsArray.push(`<a href="${redpandaCloudUrl}">${capitalize(type)}</a>`);
308
+ }
309
+ } else {
310
+ // Show Connect URLs in non-cloud tables
311
+ if (redpandaConnectUrl) {
312
+ urlsArray.push(`<a href="${redpandaConnectUrl}">${capitalize(type)}</a>`);
313
+ } else if (redpandaCloudUrl) {
314
+ // Fallback to Cloud URL if available
315
+ urlsArray.push(`<a href="${redpandaCloudUrl}">${capitalize(type)}</a>`);
316
+ }
317
+ }
318
+ });
319
+ // Filter out duplicates in URLs array for unique types
320
+ const uniqueUrls = [...new Set(urlsArray)];
321
+ return uniqueUrls.join(', '); // Return the types as a string of links
322
+ })
323
+ .filter(item => item !== '') // Remove any empty entries
324
+ .join(', '); // Join them into a single string
325
+
326
+ let supportLevelStr = ''; // Initialize the variable
327
+ // Generate the support level string
328
+ const supportLevels = Array.from(types.entries())
329
+ .reduce((supportLevelMap, [type, commercialNames]) => {
330
+ Object.entries(commercialNames).forEach(([commercialName, { supportLevel }]) => {
331
+ if (!supportLevelMap[supportLevel]) {
332
+ supportLevelMap[supportLevel] = {
333
+ types: new Set(),
334
+ commercialNames: new Map() // To track commercial names for each type
335
+ };
336
+ }
337
+ supportLevelMap[supportLevel].types.add(type); // Add the type to the Set (automatically removes duplicates)
338
+
339
+ // Add the commercial name to the type (only if it's not the connector name)
340
+ if (!supportLevelMap[supportLevel].commercialNames.has(type)) {
341
+ supportLevelMap[supportLevel].commercialNames.set(type, new Set());
342
+ }
343
+ if (commercialName.toLowerCase() !== connector.toLowerCase()) {
344
+ supportLevelMap[supportLevel].commercialNames.get(type).add(commercialName);
345
+ }
346
+ });
347
+ return supportLevelMap;
348
+ }, {});
349
+
350
+ // Generate the support level string
351
+ supportLevelStr = Object.entries(supportLevels)
352
+ .map(([supportLevel, { types, commercialNames }]) => {
353
+ const allCommercialNames = new Set(); // Store all commercial names for this support level
354
+
355
+ // Collect all commercial names across types
356
+ Array.from(commercialNames.entries()).forEach(([type, namesSet]) => {
357
+ namesSet.forEach(name => {
358
+ allCommercialNames.add(name);
359
+ });
360
+ });
361
+
362
+ // Case: Multiple support levels but no commercial names listed
363
+ if (Object.keys(supportLevels).length > 1 && allCommercialNames.size === 0 && types.size !== 0) {
364
+ const typesList = Array.from(types).join(', '); // Get all types
365
+ return `<p><b>${capitalize(supportLevel)}</b>: ${typesList}</p>`;
366
+ }
367
+
368
+ // If there's more than one commercial name, display them
369
+ if (allCommercialNames.size > 1) {
370
+ const allNamesArray = Array.from(allCommercialNames).join(', ');
371
+ return `<p><b>${capitalize(supportLevel)}</b>: ${allNamesArray}</p>`;
372
+ }
373
+
374
+ // Otherwise, just show the support level
375
+ return `<p>${capitalize(supportLevel)}</p>`;
376
+ })
377
+ .join('');
378
+
379
+ // Add SQL driver support levels if the connector is a SQL connector.
380
+ // We assume only connectors starting with sql_ are relevant.
381
+ if (connector.startsWith('sql_')) {
382
+ const certifiedDrivers = sqlDrivers.certified.length ? `<strong>Certified:</strong> ${sqlDrivers.certified.map(driver => driver.commercialName).join(', ')}` : '';
383
+ const communityDrivers = sqlDrivers.community.length ? `<strong>Community:</strong> ${sqlDrivers.community.map(driver => driver.commercialName).join(', ')}` : '';
384
+
385
+ // Add the SQL driver support to the support level string
386
+ if (certifiedDrivers || communityDrivers) {
387
+ // Reset the support levels
388
+ supportLevelStr = ''
389
+ supportLevelStr += `<p>${certifiedDrivers}${certifiedDrivers && communityDrivers ? '</br> ' : ''}${communityDrivers}</p>`;
143
390
  }
144
- } else {
391
+ }
392
+ // Build the cloud support column
393
+ const firstCloudSupportedType = Array.from(types.entries())
394
+ .map(([_, commercialNames]) => Object.values(commercialNames).find(({ isCloudSupported }) => isCloudSupported))
395
+ .find(entry => entry && entry.urls.redpandaCloudUrl);
396
+ const cloudLinkDisplay = firstCloudSupportedType
397
+ ? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a>`
398
+ : 'No';
399
+
400
+ const firstUrl = getFirstUrlFromTypesArray(Array.from(types.entries()), isCloud);
401
+
402
+ // Logic for showAllInfo = true and isCloud = false
403
+ if (showAllInfo && !isCloud) {
145
404
  return `
146
- <tr id="row-${id}">
147
- <td class="tableblock halign-left valign-top" id="componentName-${id}">
148
- <p class="tableblock">${connectorNameHtml}</p>
149
- </td>
150
- <td class="tableblock halign-left valign-top" id="componentType-${id}">
151
- <p class="tableblock">${typesStr}</p>
152
- </td>
153
- <td class="tableblock halign-left valign-top" id="componentSupport-${id}">
154
- <p class="tableblock">${supportLevelStr.trim()}</p>
155
- </td>
156
- <td class="tableblock halign-left valign-top" id="componentLicense-${id}">
157
- <p class="tableblock">${isLicensed}</p>
158
- </td>
159
- </tr>`;
160
- }
161
- }).filter(row => row !== '').join(''); // Filter out empty rows
162
- }
163
-
164
- let tabsCounter = 1; // Counter for generating unique IDs
165
-
166
- // Add the category tabs for components
167
- registry.blockMacro(function () {
168
- const self = this;
169
- self.named('components_by_category');
170
- self.positionalAttributes(['type']);
171
- self.process((parent, target, attrs) => {
172
- const type = attrs.type;
173
- const categoriesData = context.config?.attributes?.connectCategoriesData || null
174
- if (!categoriesData) return console.error (`Category data is not available for ${parent.getDocument().getAttributes()['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-categories extension.`)
175
- const categories = categoriesData[type] || null;
176
- const currentTabsId = `tabs-${tabsCounter++}`; // Unique ID for this set of tabs
177
- if (!categories) return
178
-
179
- let tabsHtml = `
180
- <div id="${currentTabsId}" class="openblock tabs is-sync is-loaded" data-sync-group-id="${type}">
181
- <div class="content">
182
- <div class="ulist tablist">
183
- <ul role="tablist">`;
184
-
185
- categories.forEach((category, index) => {
186
- tabsHtml += `
187
- <li id="${currentTabsId}-${category.name}" class="tab" tabindex="${index === 0 ? '0' : '-1'}" role="tab" data-sync-id="${category.name}" aria-controls="${currentTabsId}-${category.name}--panel" aria-selected="${index === 0}">
188
- <p>${category.name}</p>
189
- </li>`;
190
- });
191
-
192
- tabsHtml += `
193
- </ul>
194
- </div>`;
195
-
196
- categories.forEach((category, index) => {
197
- tabsHtml += `
198
- <div id="${currentTabsId}-${category.name}--panel" class="tabpanel${index === 0 ? '' : ' is-hidden'}" aria-labelledby="${currentTabsId}-${category.name}"${index === 0 ? '' : ' hidden'} role="tabpanel">
199
- <div class="listingblock">
200
- <div class="content">
201
- <p>${category.description}</p>
202
- <div class="two-column-grid">`;
203
- category.items.forEach(item => {
204
- tabsHtml += `
205
- <a href="${item.url}" class="component-card"><strong>${item.name}</strong></a>`;
206
- });
207
- tabsHtml += `
208
- </div>
209
- </div>
210
- </div>
211
- </div>`;
212
- });
213
-
214
- tabsHtml += `
215
- </div>
216
- </div>`;
217
-
218
- return self.createBlock(parent, 'pass', tabsHtml);
219
- });
220
- });
405
+ <tr id="row-${id}">
406
+ <td class="tableblock halign-left valign-top" id="componentName-${id}">
407
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
408
+ </td>
409
+ <td class="tableblock halign-left valign-top" id="componentType-${id}">
410
+ <p class="tableblock">${typesArray}</p> <!-- Display types linked to Connect URL only -->
411
+ </td>
412
+ <td class="tableblock halign-left valign-top" id="componentSupport-${id}">
413
+ <p class="tableblock">${supportLevelStr.trim()}</p> <!-- Display support levels by type -->
414
+ </td>
415
+ <td class="tableblock halign-left valign-top" id="componentLicense-${id}">
416
+ <p class="tableblock">${isLicensed}</p>
417
+ </td>
418
+ <td class="tableblock halign-left valign-top" id="componentCloud-${id}">
419
+ <p class="tableblock">${cloudLinkDisplay}</p> <!-- Display 'Yes' or 'No' with link to first cloud-supported type -->
420
+ </td>
421
+ </tr>`;
422
+ }
423
+ // Logic for isCloud = true and showAllInfo = false (Cloud Table)
424
+ if (isCloud && !showAllInfo) {
425
+ return `
426
+ <tr id="row-${id}">
427
+ <td class="tableblock halign-left valign-top" id="componentName-${id}">
428
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
429
+ </td>
430
+ <td class="tableblock halign-left valign-top" id="componentType-${id}">
431
+ ${typesArray} <!-- Display bulleted list for cloud types if commercial name differs -->
432
+ </td>
433
+ </tr>`;
434
+ }
435
+ // Default table display
436
+ return `
437
+ <tr id="row-${id}">
438
+ <td class="tableblock halign-left valign-top" id="componentName-${id}">
439
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
440
+ </td>
441
+ <td class="tableblock halign-left valign-top" id="componentType-${id}">
442
+ <p class="tableblock">${typesArray}</p> <!-- Display types without commercial names -->
443
+ </td>
444
+ <td class="tableblock halign-left valign-top" id="componentSupport-${id}">
445
+ <p class="tableblock">${supportLevelStr.trim()}</p>
446
+ </td>
447
+ <td class="tableblock halign-left valign-top" id="componentLicense-${id}">
448
+ <p class="tableblock">${isLicensed}</p>
449
+ </td>
450
+ </tr>`;
451
+ })
452
+ .filter(row => row !== '')
453
+ .join(''); // Filter out empty rows
454
+ }
221
455
 
222
- // Add the searchable table of all components (component catalog)
456
+ /**
457
+ * Registers a block macro to generate a searchable and sortable table displaying connector data.
458
+ *
459
+ * This macro creates a dynamic HTML table that lists all available connectors, allowing filtering and sorting
460
+ * by type, support level, and cloud support.
461
+ *
462
+ *
463
+ * The table includes:
464
+ * - Name: The name of the connector.
465
+ * - Connector Type: The type of the connector.
466
+ * - Support Level: The support level for each connector, including associated SQL drivers if applicable.
467
+ * - Enterprise Licensed: Indicates whether the connector requires an Enterprise license.
468
+ * - Cloud Support: Shows if the connector is supported in Redpanda Cloud.
469
+ *
470
+ * Filters:
471
+ * - Type: Allows the user to filter by connector type.
472
+ * - Support: Allows the user to filter by support level (if not in cloud view).
473
+ * - Search: A text input field to search for connectors by name.
474
+ *
475
+ * Attributes:
476
+ * - `all`: If specified, displays additional columns such as support level, enterprise licensing, and cloud support.
477
+ *
478
+ * Data Sources:
479
+ * - `csvData`: Parsed CSV data that provides details about each connector.
480
+ * - SQL driver data is processed separately using the `processSqlDrivers` function, which groups the drivers by support level.
481
+ *
482
+ * Example usage in AsciiDoc:
483
+ * ```
484
+ * component_table::[]
485
+ * ```
486
+ *
487
+ * Example output:
488
+ * ```
489
+ * | Name | Connector Type | Support Level | Enterprise Licensed | Cloud Support |
490
+ * |-------|----------------|---------------- |---------------------|-----|
491
+ * | SQL | Input, Output | Certified | No | No |
492
+ * ```
493
+ *
494
+ * @param {Object} parent - The parent document where the table will be inserted.
495
+ * @param {string} target - Target element.
496
+ * @param {Object} attributes - Positional attributes passed to the macro.
497
+ * - `all`: If provided, extra columns are shown.
498
+ */
223
499
  registry.blockMacro(function () {
224
500
  const self = this;
225
501
  self.named('component_table');
226
- self.process((parent, target, attrs) => {
502
+ self.positionalAttributes(['all']); // Allows for displaying all data
503
+ self.process((parent, target, attributes) => {
227
504
  const isCloud = parent.getDocument().getAttributes()['env-cloud'] !== undefined;
505
+ const showAllInfo = attributes?.all
506
+
228
507
  const csvData = context.config?.attributes?.csvData || null;
229
508
  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.`)
509
+
510
+ const sqlDriversData = processSqlDrivers(csvData);
511
+
230
512
  const types = new Set();
231
513
  const uniqueSupportLevel = new Set();
232
-
233
514
  csvData.data.forEach(row => {
234
- if (row.type) types.add(row.type);
515
+ if (row.type && row.type.toLowerCase() !== 'sql_driver') types.add(row.type);
235
516
  if (row.support_level) uniqueSupportLevel.add(row.support_level);
236
517
  });
237
518
 
238
519
  const createOptions = (values) =>
239
520
  Array.from(values)
240
- .map(value => `<option selected value="${value}">${capitalize(value).replace("_"," ")}</option>`)
521
+ .map(value => `<option selected value="${value}">${capitalize(value).replace("_", " ")}</option>`)
241
522
  .join('');
242
523
 
243
524
  let tableHtml = `
@@ -247,114 +528,167 @@ function generateConnectorsHTMLTable(connectors, isCloud) {
247
528
  <select multiple class="type-dropdown" id="typeFilter" onchange="filterComponentTable()">
248
529
  ${createOptions(types)}
249
530
  </select>
250
- `
531
+ `;
532
+
251
533
  if (!isCloud) {
252
534
  tableHtml += `
253
- <br><label for="supportFilter" id="labelForSupportFilter">Support:</label>
254
- <select multiple class="type-dropdown" id="supportFilter" onchange="filterComponentTable()">
255
- ${createOptions(uniqueSupportLevel)}
256
- </select>`
535
+ <br><label for="supportFilter" id="labelForSupportFilter">Support:</label>
536
+ <select multiple class="type-dropdown" id="supportFilter" onchange="filterComponentTable()">
537
+ ${createOptions(uniqueSupportLevel)}
538
+ </select>
539
+ `;
540
+ }
541
+
542
+ if (showAllInfo) {
543
+ tableHtml += `
544
+ <br><label for="cloudSupportFilter">Available in Cloud:</label>
545
+ <select class="type-dropdown" id="cloudSupportFilter" onchange="filterComponentTable()">
546
+ <option value="">All</option>
547
+ <option value="yes">Yes</option>
548
+ <option value="no">No</option>
549
+ </select>
550
+ `;
257
551
  }
258
552
 
259
553
  tableHtml += `</div>
260
554
  <table class="tableblock frame-all grid-all stripes-even no-clip stretch component-table sortable" id="componentTable">
261
555
  <colgroup>
262
- ${isCloud
263
- ? '<col style="width: 50%;"><col style="width: 50%;">'
264
- : '<col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;">'
265
- }
556
+ ${showAllInfo
557
+ ? '<col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;"><col style="width: 20%;">'
558
+ : isCloud
559
+ ? '<col style="width: 50%;"><col style="width: 50%;">'
560
+ : '<col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;">'}
266
561
  </colgroup>
267
562
  <thead>
268
563
  <tr>
269
564
  <th class="tableblock halign-left valign-top">Name</th>
270
565
  <th class="tableblock halign-left valign-top">Connector Type</th>
271
- ${isCloud ? '' : `
272
- <th class="tableblock halign-left valign-top">Support Level</th>
273
- <th class="tableblock halign-left valign-top">Enterprise Licensed</th>`}
566
+ ${showAllInfo ? `
567
+ <th class="tableblock halign-left valign-top">Support Level</th>
568
+ <th class="tableblock halign-left valign-top">Enterprise Licensed</th>
569
+ <th class="tableblock halign-left valign-top">Available in Cloud</th>
570
+ ` : isCloud ? '' : `
571
+ <th class="tableblock halign-left valign-top">Support Level</th>
572
+ <th class="tableblock halign-left valign-top">Enterprise Licensed</th>`}
274
573
  </tr>
275
574
  </thead>
276
575
  <tbody>
277
- ${generateConnectorsHTMLTable(processConnectors(csvData), isCloud)}
576
+ ${generateConnectorsHTMLTable(processConnectors(csvData), sqlDriversData, isCloud, showAllInfo)}
278
577
  </tbody>
279
578
  </table>
280
579
  <script>
281
- ${filterComponentTable.toString()}
282
-
283
- function getQueryParams() {
284
- const params = {};
285
- const searchParams = new URLSearchParams(window.location.search);
286
- searchParams.forEach((value, key) => {
287
- params[key] = value.toLowerCase();
288
- });
289
-
290
- return params;
291
- }
292
-
293
- function updateComponentUrl(select, redirect) {
294
- const anchor = select.closest('tr').querySelector('a');
295
- anchor.href = select.value;
296
- if (redirect) {
297
- window.location.href = select.value; // Redirect to the new URL
580
+ ${filterComponentTable.toString()}
581
+ function getQueryParams() {
582
+ const params = {};
583
+ const searchParams = new URLSearchParams(window.location.search);
584
+ searchParams.forEach((value, key) => {
585
+ params[key] = value.toLowerCase();
586
+ });
587
+ return params;
298
588
  }
299
- }
300
589
 
301
- // Initialize Choices.js for type dropdowns
302
- document.addEventListener('DOMContentLoaded', function() {
303
- const params = getQueryParams();
304
- const search = document.getElementById('componentTableSearch');
305
- const typeFilter = document.getElementById('typeFilter');
306
- const supportFilter = document.getElementById('supportFilter');
307
- if (params.search && search) {
308
- search.value = params.search;
309
- }
310
- if (params.type && typeFilter) {
311
- typeFilter.value = params.type;
312
- }
313
- if (params.support && supportFilter) {
314
- supportFilter.value = params.support;
315
- }
316
- filterComponentTable();
317
- const typeDropdowns = document.querySelectorAll('.type-dropdown');
318
- typeDropdowns.forEach(dropdown => {
319
- new Choices(dropdown, {
320
- searchEnabled: false,
321
- allowHTML: true,
322
- removeItemButton: true });
590
+ // Initialize Choices.js for type dropdowns
591
+ document.addEventListener('DOMContentLoaded', function() {
592
+ const params = getQueryParams();
593
+ const search = document.getElementById('componentTableSearch');
594
+ const typeFilter = document.getElementById('typeFilter');
595
+ const supportFilter = document.getElementById('supportFilter');
596
+ if (params.search && search) {
597
+ search.value = params.search;
598
+ }
599
+ if (params.type && typeFilter) {
600
+ typeFilter.value = params.type;
601
+ }
602
+ if (params.support && supportFilter) {
603
+ supportFilter.value = params.support;
604
+ }
605
+ filterComponentTable();
606
+ const typeDropdowns = document.querySelectorAll('.type-dropdown');
607
+ typeDropdowns.forEach(dropdown => {
608
+ new Choices(dropdown, {
609
+ searchEnabled: false,
610
+ allowHTML: true,
611
+ removeItemButton: true
612
+ });
613
+ });
323
614
  });
324
- });
325
- </script>`;
326
-
615
+ </script>
616
+ `;
327
617
  return self.createBlock(parent, 'pass', tableHtml);
328
618
  });
329
619
  });
330
620
 
621
+ /**
622
+ * Registers a block macro to generate a dropdown for component types and display metadata about the selected component.
623
+ *
624
+ * This macro creates a dropdown to select different types of a connector component, such as Input, Output, or Processor.
625
+ * 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.
626
+ *
627
+ *
628
+ * The dropdown lists all types of the connector component:
629
+ * - Type: A dropdown with options such as Input, Output, Processor, etc.
630
+ *
631
+ * Information displayed includes:
632
+ * - Availability: Displays links to Cloud and Self-Managed (Connect) documentation.
633
+ * - License: If the component requires an enterprise license, a message is displayed with a link to upgrade.
634
+ *
635
+ * Data Sources:
636
+ * - `csvData`: Parsed CSV data providing details about each connector.
637
+ * It filters the data to find the relevant rows for the current connector by matching the `doctitle`.
638
+ * - `redpandaConnectUrl`: URL for the Self-Managed version of the component documentation.
639
+ * - `redpandaCloudUrl`: URL for the Cloud version of the component documentation.
640
+ *
641
+ * Example usage in AsciiDoc:
642
+ * ```
643
+ * component_type_dropdown::[]
644
+ * ```
645
+ *
646
+ * Example output:
647
+ * ```
648
+ * <div class="metadata-block">
649
+ * <div style="padding:10px;display: flex;flex-direction: column;gap: 6px;">
650
+ * <p style="display: flex;align-items: center;gap: 6px;"><strong>Type:</strong>
651
+ * <select class="type-dropdown" onchange="window.location.href=this.value">
652
+ * <option value="..." data-support="certified">Input</option>
653
+ * <option value="..." data-support="community">Output</option>
654
+ * </select>
655
+ * </p>
656
+ * <p><strong>Available in:</strong> <a href="...">Cloud</a>, <a href="...">Self-Managed</a></p>
657
+ * <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>
658
+ * </div>
659
+ * </div>
660
+ * ```
661
+ *
662
+ * @param {Object} parent - The parent document where the dropdown will be inserted.
663
+ * @param {string} target - The target element.
664
+ * @param {Object} attrs - Attributes passed to the macro.
665
+ */
331
666
  registry.blockMacro(function () {
332
667
  const self = this;
333
668
  self.named('component_type_dropdown');
334
669
  self.process((parent, target, attrs) => {
335
670
  const attributes = parent.getDocument().getAttributes();
671
+ const component = attributes['page-component-title']; // Current component (e.g., 'Redpanda Cloud' or 'Redpanda Connect')
336
672
  const name = attributes['doctitle'];
337
673
  const type = attributes['type'];
338
-
339
674
  if (!name || !type) {
340
675
  return self.createBlock(parent, 'pass', '');
341
676
  }
342
-
343
677
  const csvData = context.config?.attributes?.csvData || null;
344
- if (!csvData) return console.error(`CSV data is not available for ${attributes['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-info extension.`)
678
+ if (!csvData) return console.error(`CSV data is not available for ${attributes['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-info extension.`);
679
+ // Filter for the specific connector by name
345
680
  const componentRows = csvData.data.filter(row => row.connector.trim().toLowerCase() === name.trim().toLowerCase());
346
-
347
681
  if (componentRows.length === 0) {
348
- return self.createBlock(parent, 'pass', '');
682
+ console.error(`No data found for connector: ${name}`);
349
683
  }
350
-
351
- // Process types from CSV
684
+ // Process types and metadata from CSV
352
685
  const types = componentRows.map(row => ({
353
686
  type: row.type.trim(),
354
687
  support: row.support_level.trim(),
355
- url: row.url ? row.url.trim() : '#'
688
+ isCloudSupported: row.is_cloud_supported === 'y',
689
+ redpandaConnectUrl: row.redpandaConnectUrl,
690
+ redpandaCloudUrl: row.redpandaCloudUrl
356
691
  }));
357
-
358
692
  // Move the current page's type to the first position in the dropdown
359
693
  const sortedTypes = [...types];
360
694
  const currentTypeIndex = sortedTypes.findIndex(typeObj => typeObj.type === type);
@@ -362,50 +696,130 @@ function generateConnectorsHTMLTable(connectors, isCloud) {
362
696
  const [currentType] = sortedTypes.splice(currentTypeIndex, 1);
363
697
  sortedTypes.unshift(currentType);
364
698
  }
365
-
366
699
  // Check if the component requires an Enterprise license (based on support level)
367
- let enterpriseAdmonition = '';
368
- if (componentRows.some(row => row.support_level.toLowerCase() === 'enterprise')) {
369
- enterpriseAdmonition = `
370
- <div class="admonitionblock note">
371
- <table>
372
- <tbody>
373
- <tr>
374
- <td class="icon">
375
- <i class="fa icon-note" title="Note"></i>
376
- </td>
377
- <td class="content">
378
- <div class="paragraph">
379
- <p>This feature 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>
380
- </div>
381
- </td>
382
- </tr>
383
- </tbody>
384
- </table>
385
- </div>`;
700
+ const requiresEnterprise = componentRows.some(row => row.is_licensed.toLowerCase() === 'yes');
701
+ let enterpriseLicenseInfo = '';
702
+ if (requiresEnterprise) {
703
+ enterpriseLicenseInfo = `
704
+ <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>`;
386
705
  }
706
+ const isCloudSupported = componentRows.some(row => row.is_cloud_supported === 'y');
707
+ let availableInInfo = '';
708
+
709
+ if (isCloudSupported) {
710
+ const availableInLinks = [];
711
+
712
+ // Check if the component is Cloud and apply the `current-version` class
713
+ if (sortedTypes[0].redpandaCloudUrl) {
714
+ if (component === 'Cloud') {
715
+ availableInLinks.push('<span title="You are viewing the Cloud version of this component" class="current-version">Cloud</span>'); // Highlight the current version
716
+ } else {
717
+ availableInLinks.push(`<a title="View the Cloud version of this component" href="${sortedTypes[0].redpandaCloudUrl}">Cloud</a>`);
718
+ }
719
+ }
387
720
 
388
- // Create the dropdown for types
721
+ // Check if the component is Connect and apply the `current-version` class
722
+ if (sortedTypes[0].redpandaConnectUrl) {
723
+ if (component === 'Connect') {
724
+ availableInLinks.push('<span title="You are viewing the Self-Managed version of this component" class="current-version">Self-Managed</span>'); // Highlight the current version
725
+ } else {
726
+ availableInLinks.push(`<a title="View the Self-Managed version of this component" href="${sortedTypes[0].redpandaConnectUrl}">Self-Managed</a>`);
727
+ }
728
+ }
729
+ availableInInfo = `<p><strong>Available in:</strong> ${availableInLinks.join(', ')}</p>`;
730
+ } else {
731
+ availableInInfo = `<p><strong>Available in:</strong> <span title="You are viewing the Self-Managed version of this component" class="current-version">Self-Managed</span></p>`;
732
+ }
733
+ // Build the dropdown for types with links depending on the current component
389
734
  let typeDropdown = '';
390
735
  if (sortedTypes.length > 1) {
736
+ const dropdownLinks = sortedTypes.map(typeObj => {
737
+ const link = (component === 'Cloud' && typeObj.redpandaCloudUrl) || typeObj.redpandaConnectUrl;
738
+ return `<option value="${link}" data-support="${typeObj.support}">${capitalize(typeObj.type)}</option>`;
739
+ }).join('');
391
740
  typeDropdown = `
392
- <div class="page-type-dropdown">
393
- <p>Type: </p>
394
- <select class="type-dropdown" onchange="window.location.href=this.value">
395
- ${sortedTypes.map(typeObj => `<option value="../${typeObj.url}/" data-support="${typeObj.support}">${capitalize(typeObj.type)}</option>`).join('')}
396
- </select>
397
- </div>
398
- <script>
399
- // Initialize Choices.js for type dropdowns
400
- document.addEventListener('DOMContentLoaded', function() {
401
- const typeDropdowns = document.querySelectorAll('.type-dropdown');
402
- typeDropdowns.forEach(dropdown => {
403
- new Choices(dropdown, { searchEnabled: false, allowHTML: true, shouldSort: false });
741
+ <p style="display: flex;align-items: center;gap: 6px;"><strong>Type:</strong>
742
+ <select class="type-dropdown" onchange="window.location.href=this.value">
743
+ ${dropdownLinks}
744
+ </select>
745
+ </p>
746
+ <script>
747
+ // Initialize Choices.js for type dropdowns
748
+ document.addEventListener('DOMContentLoaded', function() {
749
+ const typeDropdowns = document.querySelectorAll('.type-dropdown');
750
+ typeDropdowns.forEach(dropdown => {
751
+ new Choices(dropdown, { searchEnabled: false, allowHTML: true, shouldSort: false, itemSelectText: '' });
752
+ });
404
753
  });
405
- });
406
- </script>`;
754
+ </script>`;
407
755
  }
408
- return self.createBlock(parent, 'pass', typeDropdown + enterpriseAdmonition);
756
+ // Return the metadata block with consistent layout
757
+ return self.createBlock(parent, 'pass', `
758
+ <div class="metadata-block">
759
+ <div style="padding:10px;display: flex;flex-direction: column;gap: 6px;">
760
+ ${typeDropdown}
761
+ ${availableInInfo}
762
+ ${enterpriseLicenseInfo}
763
+ </div>
764
+ </div>`);
765
+ });
766
+ });
767
+
768
+ let tabsCounter = 1; // Counter for generating unique IDs
769
+
770
+ // Add the category tabs for components
771
+ registry.blockMacro(function () {
772
+ const self = this;
773
+ self.named('components_by_category');
774
+ self.positionalAttributes(['type']);
775
+ self.process((parent, target, attrs) => {
776
+ const type = attrs.type;
777
+ const categoriesData = context.config?.attributes?.connectCategoriesData || null
778
+ if (!categoriesData) return console.error (`Category data is not available for ${parent.getDocument().getAttributes()['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-categories extension.`)
779
+ const categories = categoriesData[type] || null;
780
+ const currentTabsId = `tabs-${tabsCounter++}`; // Unique ID for this set of tabs
781
+ if (!categories) return
782
+
783
+ let tabsHtml = `
784
+ <div id="${currentTabsId}" class="openblock tabs is-sync is-loaded" data-sync-group-id="${type}">
785
+ <div class="content">
786
+ <div class="ulist tablist">
787
+ <ul role="tablist">`;
788
+
789
+ categories.forEach((category, index) => {
790
+ tabsHtml += `
791
+ <li id="${currentTabsId}-${category.name}" class="tab" tabindex="${index === 0 ? '0' : '-1'}" role="tab" data-sync-id="${category.name}" aria-controls="${currentTabsId}-${category.name}--panel" aria-selected="${index === 0}">
792
+ <p>${category.name}</p>
793
+ </li>`;
794
+ });
795
+
796
+ tabsHtml += `
797
+ </ul>
798
+ </div>`;
799
+
800
+ categories.forEach((category, index) => {
801
+ tabsHtml += `
802
+ <div id="${currentTabsId}-${category.name}--panel" class="tabpanel${index === 0 ? '' : ' is-hidden'}" aria-labelledby="${currentTabsId}-${category.name}"${index === 0 ? '' : ' hidden'} role="tabpanel">
803
+ <div class="listingblock">
804
+ <div class="content">
805
+ <p>${category.description}</p>
806
+ <div class="two-column-grid">`;
807
+ category.items.forEach(item => {
808
+ tabsHtml += `
809
+ <a href="${item.url}" class="component-card"><strong>${item.name}</strong></a>`;
810
+ });
811
+ tabsHtml += `
812
+ </div>
813
+ </div>
814
+ </div>
815
+ </div>`;
816
+ });
817
+
818
+ tabsHtml += `
819
+ </div>
820
+ </div>`;
821
+
822
+ return self.createBlock(parent, 'pass', tabsHtml);
409
823
  });
410
824
  });
411
825
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",