Skip to content

Commit 709bb45

Browse files
committed
Support related topics for cloud/self-managed only
1 parent 8b136fb commit 709bb45

File tree

8 files changed

+219
-46
lines changed

8 files changed

+219
-46
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@redpanda-data/docs-extensions-and-macros",
3-
"version": "4.10.7",
3+
"version": "4.10.8",
44
"description": "Antora extensions and macros developed for Redpanda documentation.",
55
"keywords": [
66
"antora",

tools/property-extractor/generate-handlebars-docs.js

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const helpers = require('./helpers');
2222
* CLI Usage: node generate-handlebars-docs.js <input-file> <output-dir>
2323
*/
2424

25-
// Register all helpers
25+
// Register helpers
2626
Object.entries(helpers).forEach(([name, fn]) => {
2727
if (typeof fn !== 'function') {
2828
console.error(`❌ Helper "${name}" is not a function`);
@@ -62,11 +62,6 @@ function getTemplatePath(defaultPath, envVar) {
6262

6363
/**
6464
* Register Handlebars partials used to render property documentation.
65-
*
66-
* Registers:
67-
* - "property"
68-
* - "topic-property"
69-
* - "deprecated-property"
7065
*/
7166
function registerPartials() {
7267
const templatesDir = path.join(__dirname, 'templates');
@@ -129,11 +124,24 @@ function generatePropertyPartials(properties, partialsDir) {
129124

130125
Object.values(properties).forEach(prop => {
131126
if (!prop.name || !prop.config_scope) return;
132-
if (prop.config_scope === 'topic') propertyGroups.topic.push(prop);
133-
else if (prop.config_scope === 'broker') propertyGroups.broker.push(prop);
134-
else if (prop.config_scope === 'cluster') {
135-
if (isObjectStorageProperty(prop)) propertyGroups['object-storage'].push(prop);
136-
else propertyGroups.cluster.push(prop);
127+
128+
switch (prop.config_scope) {
129+
case 'topic':
130+
propertyGroups.topic.push(prop);
131+
break;
132+
case 'broker':
133+
propertyGroups.broker.push(prop);
134+
break;
135+
case 'cluster':
136+
if (isObjectStorageProperty(prop)) propertyGroups['object-storage'].push(prop);
137+
else propertyGroups.cluster.push(prop);
138+
break;
139+
case 'object-storage':
140+
propertyGroups['object-storage'].push(prop);
141+
break;
142+
default:
143+
console.warn(`⚠️ Unknown config_scope: ${prop.config_scope} for ${prop.name}`);
144+
break;
137145
}
138146
});
139147

@@ -145,7 +153,7 @@ function generatePropertyPartials(properties, partialsDir) {
145153
const selectedTemplate = type === 'topic' ? topicTemplate : propertyTemplate;
146154
const content = props.map(p => selectedTemplate(p)).join('\n');
147155
const filename = `${type}-properties.adoc`;
148-
fs.writeFileSync(path.join(propertiesPartialsDir, filename), AUTOGEN_NOTICE + content, 'utf8');
156+
fs.writeFileSync(path.join(propertiesPartialsDir, filename), AUTOGEN_NOTICE + content, 'utf8');
149157
console.log(`✅ Generated ${filename} (${props.length} properties)`);
150158
totalCount += props.length;
151159
});
@@ -178,19 +186,18 @@ function generateDeprecatedDocs(properties, outputDir) {
178186
clusterProperties: clusterProperties.length ? clusterProperties : null
179187
};
180188

181-
const output = template(data);
182189
const outputPath = process.env.OUTPUT_PARTIALS_DIR
183190
? path.join(process.env.OUTPUT_PARTIALS_DIR, 'deprecated', 'deprecated-properties.adoc')
184191
: path.join(outputDir, 'partials', 'deprecated', 'deprecated-properties.adoc');
185192

186193
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
187-
fs.writeFileSync(outputPath, AUTOGEN_NOTICE + output, 'utf8');
194+
fs.writeFileSync(outputPath, AUTOGEN_NOTICE + template(data), 'utf8');
188195
console.log(`✅ Generated ${outputPath}`);
189196
return deprecatedProperties.length;
190197
}
191198

192199
/**
193-
* Generate topic-property-mappings.adoc using the mappings template and topic properties.
200+
* Generate topic-property-mappings.adoc
194201
*/
195202
function generateTopicPropertyMappings(properties, partialsDir) {
196203
const templatesDir = path.join(__dirname, 'templates');
@@ -218,31 +225,42 @@ function generateTopicPropertyMappings(properties, partialsDir) {
218225
}
219226

220227
/**
221-
* Generate error reports for missing descriptions and deprecated properties.
228+
* Generate error reports for missing descriptions, deprecated, and undocumented properties.
222229
*/
223-
function generateErrorReports(properties) {
230+
function generateErrorReports(properties, documentedProperties = []) {
224231
const emptyDescriptions = [];
225232
const deprecatedProperties = [];
233+
const allKeys = Object.keys(properties);
234+
235+
// Use documentedProperties array (property names that were rendered into partials)
236+
const documentedSet = new Set(documentedProperties);
237+
const undocumented = [];
226238

227-
Object.values(properties).forEach(p => {
228-
if (!p.description || !p.description.trim()) emptyDescriptions.push(p.name);
229-
if (p.is_deprecated) deprecatedProperties.push(p.name);
239+
Object.entries(properties).forEach(([key, p]) => {
240+
const name = p.name || key;
241+
if (!p.description || !p.description.trim()) emptyDescriptions.push(name);
242+
if (p.is_deprecated) deprecatedProperties.push(name);
243+
if (!documentedSet.has(name)) undocumented.push(name);
230244
});
231245

232-
const total = Object.keys(properties).length;
246+
const total = allKeys.length;
233247
const pctEmpty = total ? ((emptyDescriptions.length / total) * 100).toFixed(2) : '0.00';
234248
const pctDeprecated = total ? ((deprecatedProperties.length / total) * 100).toFixed(2) : '0.00';
235-
console.log(`Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%)`);
236-
console.log(`Deprecated: ${deprecatedProperties.length} (${pctDeprecated}%)`);
249+
const pctUndocumented = total ? ((undocumented.length / total) * 100).toFixed(2) : '0.00';
250+
251+
console.log(`📉 Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%)`);
252+
console.log(`🕸️ Deprecated: ${deprecatedProperties.length} (${pctDeprecated}%)`);
253+
console.log(`🚫 Not documented: ${undocumented.length} (${pctUndocumented}%)`);
237254

238255
return {
239256
empty_descriptions: emptyDescriptions.sort(),
240-
deprecated_properties: deprecatedProperties.sort()
257+
deprecated_properties: deprecatedProperties.sort(),
258+
undocumented_properties: undocumented.sort(),
241259
};
242260
}
243261

244262
/**
245-
* Main generator — only supports partials and deprecated docs.
263+
* Main generator
246264
*/
247265
function generateAllDocs(inputFile, outputDir) {
248266
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
@@ -252,13 +270,49 @@ function generateAllDocs(inputFile, outputDir) {
252270

253271
let partialsCount = 0;
254272
let deprecatedCount = 0;
273+
const documentedProps = []; // Track which property names were rendered
255274

256275
if (process.env.GENERATE_PARTIALS === '1' && process.env.OUTPUT_PARTIALS_DIR) {
257276
console.log('📄 Generating property partials and deprecated docs...');
258277
deprecatedCount = generateDeprecatedDocs(properties, outputDir);
259-
partialsCount = generatePropertyPartials(properties, process.env.OUTPUT_PARTIALS_DIR);
260278

261-
// Generate topic-property-mappings.adoc
279+
// Wrap generatePropertyPartials to also collect property names
280+
const originalWrite = fs.writeFileSync;
281+
const propertyTemplate = handlebars.compile(
282+
fs.readFileSync(getTemplatePath(path.join(__dirname, 'templates', 'property.hbs'), 'TEMPLATE_PROPERTY'), 'utf8')
283+
);
284+
const topicTemplate = handlebars.compile(
285+
fs.readFileSync(getTemplatePath(path.join(__dirname, 'templates', 'topic-property.hbs'), 'TEMPLATE_TOPIC_PROPERTY'), 'utf8')
286+
);
287+
288+
const propertiesPartialsDir = path.join(process.env.OUTPUT_PARTIALS_DIR, 'properties');
289+
fs.mkdirSync(propertiesPartialsDir, { recursive: true });
290+
291+
const propertyGroups = { cluster: [], topic: [], broker: [], 'object-storage': [] };
292+
Object.values(properties).forEach(p => {
293+
if (!p.name || !p.config_scope) return;
294+
if (p.config_scope === 'topic') propertyGroups.topic.push(p);
295+
else if (p.config_scope === 'broker') propertyGroups.broker.push(p);
296+
else if (p.config_scope === 'cluster') {
297+
if (isObjectStorageProperty(p)) propertyGroups['object-storage'].push(p);
298+
else propertyGroups.cluster.push(p);
299+
}
300+
});
301+
302+
Object.entries(propertyGroups).forEach(([type, props]) => {
303+
if (props.length === 0) return;
304+
props.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
305+
const selectedTemplate = type === 'topic' ? topicTemplate : propertyTemplate;
306+
const content = props.map(p => {
307+
documentedProps.push(p.name);
308+
return selectedTemplate(p);
309+
}).join('\n');
310+
const filename = `${type}-properties.adoc`;
311+
originalWrite(path.join(propertiesPartialsDir, filename), AUTOGEN_NOTICE + content, 'utf8');
312+
console.log(`✅ Generated ${filename} (${props.length} properties)`);
313+
partialsCount += props.length;
314+
});
315+
262316
try {
263317
generateTopicPropertyMappings(properties, process.env.OUTPUT_PARTIALS_DIR);
264318
} catch (err) {
@@ -268,24 +322,34 @@ function generateAllDocs(inputFile, outputDir) {
268322
console.log('📄 Skipping partial generation (set GENERATE_PARTIALS=1 and OUTPUT_PARTIALS_DIR to enable)');
269323
}
270324

271-
const errors = generateErrorReports(properties);
272-
const inputData = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
273-
inputData.empty_descriptions = errors.empty_descriptions;
274-
inputData.deprecated_properties = errors.deprecated_properties;
275-
fs.writeFileSync(inputFile, JSON.stringify(inputData, null, 2), 'utf8');
325+
const errors = generateErrorReports(properties, documentedProps);
326+
327+
const totalProperties = Object.keys(properties).length;
328+
const notRendered = errors.undocumented_properties.length;
329+
const pctRendered = totalProperties
330+
? ((partialsCount / totalProperties) * 100).toFixed(2)
331+
: '0.00';
276332

277-
console.log('📊 Summary:');
278-
console.log(` Total properties: ${Object.keys(properties).length}`);
279-
console.log(` Total partials generated: ${partialsCount}`);
280-
console.log(` Deprecated properties: ${deprecatedCount}`);
333+
console.log('\n📊 Summary:');
334+
console.log(` 🧱 Total properties found: ${totalProperties}`);
335+
console.log(` 🧩 Property partials generated: ${partialsCount} (${pctRendered}% of total)`);
336+
console.log(` 🚫 Not documented: ${notRendered}`);
337+
console.log(` 🕸️ Deprecated properties: ${deprecatedCount}`);
338+
339+
if (notRendered > 0) {
340+
console.log('⚠️ Undocumented properties:\n ' + errors.undocumented_properties.join('\n '));
341+
}
281342

282343
return {
283-
totalProperties: Object.keys(properties).length,
284-
propertyPartials: partialsCount,
285-
deprecatedProperties: deprecatedCount
344+
totalProperties,
345+
generatedPartials: partialsCount,
346+
undocumentedProperties: errors.undocumented_properties,
347+
deprecatedProperties: deprecatedCount,
348+
percentageRendered: pctRendered
286349
};
287350
}
288351

352+
289353
module.exports = {
290354
generateAllDocs,
291355
generateDeprecatedDocs,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Returns 'cloud' if all topics are cloud-only, 'self-managed' if all are self-managed-only, else 'normal'
2+
module.exports = function allTopicsConditional(related_topics) {
3+
if (!Array.isArray(related_topics) || related_topics.length === 0) return null;
4+
let allCloud = true;
5+
let allSelfManaged = true;
6+
for (const t of related_topics) {
7+
if (typeof t !== 'string') {
8+
allCloud = false;
9+
allSelfManaged = false;
10+
break;
11+
}
12+
const trimmed = t.trim();
13+
if (!trimmed.startsWith('cloud-only:')) allCloud = false;
14+
if (!trimmed.startsWith('self-managed-only:')) allSelfManaged = false;
15+
}
16+
if (allCloud) return 'cloud';
17+
if (allSelfManaged) return 'self-managed';
18+
return 'normal';
19+
};

tools/property-extractor/helpers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ module.exports = {
1111
renderPropertyExample: require('./renderPropertyExample.js'),
1212
formatUnits: require('./formatUnits.js'),
1313
anchorName: require('./anchorName.js'),
14+
parseRelatedTopic: require('./parseRelatedTopic.js'),
15+
allTopicsConditional: require('./allTopicsConditional.js'),
1416
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Returns an object with type and value for a related topic
2+
// type: 'cloud', 'self-managed', or 'normal'
3+
module.exports = function parseRelatedTopic(topic) {
4+
if (typeof topic !== 'string') return { type: 'normal', value: topic };
5+
const trimmed = topic.trim();
6+
if (trimmed.startsWith('cloud-only:')) {
7+
return { type: 'cloud', value: trimmed.replace(/^cloud-only:/, '').trim() };
8+
}
9+
if (trimmed.startsWith('self-managed-only:')) {
10+
return { type: 'self-managed', value: trimmed.replace(/^self-managed-only:/, '').trim() };
11+
}
12+
return { type: 'normal', value: trimmed };
13+
};

tools/property-extractor/templates/property.hbs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ endif::[]
2323
{{else}}
2424

2525
No description available.
26+
2627
{{/if}}
2728
{{#if is_enterprise}}
2829

@@ -31,9 +32,11 @@ ifndef::env-cloud[]
3132
endif::[]
3233
{{/if}}
3334
{{#if cloud_byoc_only}}
35+
3436
ifdef::env-cloud[]
3537
NOTE: This property is available only in Redpanda Cloud BYOC deployments.
3638
endif::[]
39+
3740
{{/if}}
3841
{{#if units}}
3942

@@ -89,12 +92,48 @@ endif::[]
8992
{{{renderPropertyExample this}}}
9093
{{/if}}
9194
{{#if related_topics}}
95+
{{#with (allTopicsConditional related_topics) as |sectionType|}}
9296

97+
{{#if (eq sectionType "cloud")}}
98+
ifdef::env-cloud[]
9399
*Related topics:*
94100

95-
{{#each related_topics}}
96-
* {{{this}}}
101+
{{#each ../related_topics}}
102+
{{#with (parseRelatedTopic this)}}
103+
* {{{value}}}
104+
{{/with}}
97105
{{/each}}
106+
endif::[]
107+
{{else if (eq sectionType "self-managed")}}
108+
ifndef::env-cloud[]
109+
*Related topics:*
110+
111+
{{#each ../related_topics}}
112+
{{#with (parseRelatedTopic this)}}
113+
* {{{value}}}
114+
{{/with}}
115+
{{/each}}
116+
endif::[]
117+
{{else}}
118+
*Related topics:*
119+
120+
{{#each ../related_topics}}
121+
{{#with (parseRelatedTopic this)}}
122+
{{#if (eq type "cloud")}}
123+
ifdef::env-cloud[]
124+
* {{{value}}}
125+
endif::[]
126+
{{else if (eq type "self-managed")}}
127+
ifndef::env-cloud[]
128+
* {{{value}}}
129+
endif::[]
130+
{{else}}
131+
* {{{value}}}
132+
{{/if}}
133+
{{/with}}
134+
{{/each}}
135+
{{/if}}
136+
{{/with}}
98137
{{/if}}
99138
{{#if aliases}}
100139

0 commit comments

Comments
 (0)