@redpanda-data/docs-extensions-and-macros 4.0.0 → 4.1.0

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.
package/README.adoc CHANGED
@@ -254,6 +254,90 @@ antora:
254
254
  - require: '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-categories'
255
255
  ```
256
256
 
257
+ === Compute end-of-life extension
258
+
259
+ This extension calculates and attaches metadata related to the end-of-life (EoL) status of docs pages, such as nearing EoL, past EoL, and associated EoL dates. This metadata can be used to display relevant banners or messages in docs to inform users about the lifecycle of each version.
260
+
261
+ The extension leverages configuration settings provided in the Antora playbook to apply EoL calculations, specify the warning period, and include links to upgrade documentation and EoL policies.
262
+
263
+ The extension computes whether a page is nearing EoL or past EoL based on the `page-release-date` attribute and configured settings.
264
+ It injects the following attributes into each page, making them available for use in UI templates:
265
+
266
+ - `page-is-nearing-eol`: Indicates if the page is within the warning period before EoL. Calculated using `(page-release-date + supported_months) - warning_weeks`.
267
+ - `page-is-past-eol`: Indicates if the page has passed its EoL. Calculated using `today > (page-release-date + supported_months)`.
268
+ - `page-eol-date`: The calculated EoL date in a human-readable format. Calculated using `page-release-date + supported_months`.
269
+ - `page-eol-doc`: The URL to the supported versions policy or EoL documentation.
270
+ - `page-upgrade-doc`: The Antora resource ID to a document containing upgrade instructions.
271
+
272
+ ==== Environment variables
273
+
274
+ This extension does not require any environment variables.
275
+
276
+ ==== Configuration options
277
+
278
+ To enable and configure the extension, add it to the `antora.extensions` section of your Antora playbook. Define the EoL settings under the `data.eol_settings` key with the following options:
279
+
280
+ `component` (required):: The component name to which the configuration applies.
281
+ `eol_doc` (required):: A link to the supported versions policy or EoL documentation.
282
+ `upgrade_doc` (required):: A link to the upgrade instructions.
283
+ `supported_months` (optional, default: 12):: The number of months after the publish date when the documentation reaches its EoL.
284
+ `warning_weeks` (optional, default: 6):: The number of weeks before EoL when the documentation is considered to be nearing EoL. Can be used to decide when to notify users of the upcoming EoL status.
285
+
286
+ [,yaml]
287
+ ----
288
+ antora:
289
+ extensions:
290
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/end-of-life'
291
+ data:
292
+ eol_settings:
293
+ - component: 'ROOT'
294
+ supported_months: 18
295
+ warning_weeks: 8
296
+ eol_doc: https://support.redpanda.com/hc/en-us/articles/20617574366743-Redpanda-Supported-Versions
297
+ upgrade_doc: ROOT:upgrade:index.adoc
298
+ ----
299
+
300
+ ==== Registration example
301
+
302
+ You can register the extension with a customized configuration for different components in your playbook:
303
+
304
+ [,yaml]
305
+ ----
306
+ antora:
307
+ extensions:
308
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/compute-end-of-life'
309
+ data:
310
+ eol_settings:
311
+ - component: 'ROOT'
312
+ supported_months: 12
313
+ warning_weeks: 6
314
+ eol_doc: https://example.com/supported-versions
315
+ upgrade_doc: ROOT:upgrade:index.adoc
316
+ - component: 'example-docs'
317
+ supported_months: 24
318
+ warning_weeks: 12
319
+ eol_doc: https://example.com/example-supported-versions
320
+ upgrade_doc: example-docs:upgrade:index.adoc
321
+ ----
322
+
323
+
324
+ ==== Example Handlebars template:
325
+
326
+ [,handlebars]
327
+ ----
328
+ {{#if page.attributes.is-nearing-eol}}
329
+ <div class="banner-container nearing-eol">
330
+ This documentation will reach its end of life on {{page.attributes.eol-date}}.
331
+ Please <a href="{{resolve-resource page.attributes.upgrade-doc}}">upgrade to a supported version</a>.
332
+ </div>
333
+ {{else if page.attributes.is-past-eol}}
334
+ <div class="banner-container past-eol">
335
+ This documentation reached its end of life on {{page.attributes.eol-date}}.
336
+ See our <a href="{{page.attributes.eol-doc}}" target="_blank">supported versions policy</a>.
337
+ </div>
338
+ {{/if}}
339
+ ----
340
+
257
341
  === Generate index data
258
342
 
259
343
  The `generate-index-data` extension creates structured index data about doc pages based on configurable filters. The indexed data is saved to a specified attribute in all component versions, enabling the dynamic generation of categorized links and descriptions within your docs using UI templates.
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const calculateEOL = require('./util/calculate-eol.js');
4
+
5
+ module.exports.register = function ({ config }) {
6
+ this.on('contentClassified', ({ contentCatalog }) => {
7
+ const logger = this.getLogger("compute-end-of-life-extension");
8
+
9
+ // Extract EOL configuration from the config object
10
+ const eolConfigs = config.data?.eol_settings || [];
11
+ if (!Array.isArray(eolConfigs) || eolConfigs.length === 0) {
12
+ logger.warn('No end-of-life settings found in configuration.');
13
+ return;
14
+ }
15
+
16
+ eolConfigs.forEach(({ component: componentName, supported_months, warning_weeks, eol_doc, upgrade_doc }) => {
17
+ if (!eol_doc || !upgrade_doc) {
18
+ logger.error(
19
+ `End-of-life configuration for component "${component}" is missing required attributes. ` +
20
+ `Ensure both "eol_doc" and "upgrade_doc" are specified.`
21
+ );
22
+ return;
23
+ }
24
+ const resolvedEOLMonths = supported_months && supported_months > 0 ? supported_months : 12; // Default: 12 months
25
+ const resolvedWarningWeeks = warning_weeks && warning_weeks > 0 ? warning_weeks : 6; // Default: 6 weeks
26
+
27
+ logger.info(
28
+ `Processing component: ${componentName} with end-of-life months: ${resolvedEOLMonths}, Warning weeks: ${resolvedWarningWeeks}`
29
+ );
30
+
31
+ const component = contentCatalog.getComponents().find((c) => c.name === componentName);
32
+
33
+ if (!component) {
34
+ logger.warn(`Component not found: ${componentName}`);
35
+ return;
36
+ }
37
+
38
+ component.versions.forEach(({ asciidoc, version }) => {
39
+ const releaseDate = asciidoc.attributes['page-release-date'];
40
+ if (releaseDate) {
41
+ // Pass resolved configuration to calculateEOL
42
+ const eolInfo = calculateEOL(releaseDate, resolvedEOLMonths, resolvedWarningWeeks, logger);
43
+ Object.assign(asciidoc.attributes, {
44
+ 'page-is-nearing-eol': eolInfo.isNearingEOL.toString(),
45
+ 'page-is-past-eol': eolInfo.isPastEOL.toString(),
46
+ 'page-eol-date': eolInfo.eolDate,
47
+ 'page-eol-doc': eol_doc,
48
+ 'page-upgrade-doc': upgrade_doc,
49
+ });
50
+ } else {
51
+ logger.warn(`No release date found for component: ${componentName}. Make sure to set {page-release-date} in the antora.yml of the component version ${version}.`);
52
+ }
53
+ });
54
+ });
55
+ });
56
+ };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ module.exports = (releaseDate, eolMonths, warningWeeks, logger) => {
4
+ if (!releaseDate) {
5
+ logger.warn(
6
+ 'No release date provided. Make sure to set {page-release-date} in the antora.yml of the component.'
7
+ );
8
+ return null;
9
+ }
10
+
11
+ // Parse the input date string YYYY-MM-DD in UTC
12
+ const parseUTCDate = (dateString) => {
13
+ const [year, month, day] = dateString.split('-').map(Number);
14
+ // month - 1 because JS months are 0-based
15
+ // https://www.w3schools.com/jsref/jsref_getmonth.asp
16
+ return new Date(Date.UTC(year, month - 1, day));
17
+ };
18
+
19
+ const targetDate = parseUTCDate(releaseDate);
20
+
21
+ if (isNaN(targetDate.getTime())) {
22
+ logger.warn('Invalid release date format:', releaseDate);
23
+ return null;
24
+ }
25
+
26
+ // Calculate EoL date in UTC
27
+ const eolDate = new Date(targetDate.getTime()); // clone
28
+ eolDate.setUTCMonth(eolDate.getUTCMonth() + eolMonths);
29
+
30
+ // Calculate the threshold for warning (X weeks before EoL) in UTC
31
+ const weeksBeforeEOL = new Date(eolDate.getTime());
32
+ weeksBeforeEOL.setUTCDate(weeksBeforeEOL.getUTCDate() - warningWeeks * 7);
33
+
34
+ // Compare times in milliseconds to avoid timezone confusion
35
+ const nowMs = Date.now();
36
+ const eolMs = eolDate.getTime();
37
+ const warningMs = weeksBeforeEOL.getTime();
38
+
39
+ const isNearingEOL = nowMs >= warningMs && nowMs < eolMs;
40
+ const isPastEOL = nowMs > eolMs;
41
+
42
+ // Format the EoL date in UTC
43
+ const humanReadableEOLDate = new Intl.DateTimeFormat('en-US', {
44
+ year: 'numeric',
45
+ month: 'long',
46
+ day: 'numeric',
47
+ timeZone: 'UTC', // Ensure UTC in output
48
+ }).format(eolDate);
49
+
50
+ return {
51
+ isNearingEOL,
52
+ isPastEOL,
53
+ eolDate: humanReadableEOLDate, // For example "March 1, 2025"
54
+ };
55
+ };
@@ -0,0 +1,19 @@
1
+ function customStringify(obj) {
2
+ return JSON.stringify(obj, (key, value) => {
3
+ if (value instanceof Map) {
4
+ return {
5
+ type: 'Map',
6
+ value: Array.from(value.entries())
7
+ };
8
+ } else if (value instanceof Set) {
9
+ return {
10
+ type: 'Set',
11
+ value: Array.from(value)
12
+ };
13
+ } else if (typeof value === 'function') {
14
+ return value.toString();
15
+ } else {
16
+ return value;
17
+ }
18
+ }, 2);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -33,6 +33,7 @@
33
33
  "./extensions/archive-attachments": "./extensions/archive-attachments.js",
34
34
  "./extensions/add-pages-to-root": "./extensions/add-pages-to-root.js",
35
35
  "./extensions/collect-bloblang-samples": "./extensions/collect-bloblang-samples.js",
36
+ "./extensions/compute-end-of-life": "./extensions/compute-end-of-life.js",
36
37
  "./extensions/generate-rp-connect-categories": "./extensions/generate-rp-connect-categories.js",
37
38
  "./extensions/generate-index-data": "./extensions/generate-index-data.js",
38
39
  "./extensions/generate-rp-connect-info": "./extensions/generate-rp-connect-info.js",
@@ -1,19 +0,0 @@
1
- function customStringify(obj) {
2
- return JSON.stringify(obj, (key, value) => {
3
- if (value instanceof Map) {
4
- return {
5
- type: 'Map',
6
- value: Array.from(value.entries())
7
- };
8
- } else if (value instanceof Set) {
9
- return {
10
- type: 'Set',
11
- value: Array.from(value)
12
- };
13
- } else if (typeof value === 'function') {
14
- return value.toString();
15
- } else {
16
- return value;
17
- }
18
- }, 2);
19
- }