@redpanda-data/docs-extensions-and-macros 5.46.0 → 5.47.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/bin/doc-tools.js +15 -2
- package/cli-utils/sync-schemas.js +66 -12
- package/docs-data/property-overrides.schema.json +202 -29
- package/package.json +1 -1
- package/tools/overrides-audit/adapters/properties.js +14 -3
- package/tools/overrides-audit/classify.js +175 -3
- package/tools/property-extractor/README.adoc +141 -2
- package/tools/property-extractor/compare-properties.js +12 -2
- package/tools/property-extractor/generate-handlebars-docs.js +262 -11
- package/tools/property-extractor/helpers/applyPropertyLinks.js +586 -0
- package/tools/property-extractor/helpers/audienceScope.js +172 -0
- package/tools/property-extractor/helpers/findAudienceScopeProblems.js +121 -0
- package/tools/property-extractor/helpers/index.js +3 -0
- package/tools/property-extractor/helpers/seeAlsoView.js +10 -24
- package/tools/property-extractor/property_extractor.py +30 -2
- package/tools/property-extractor/templates/property.hbs +32 -2
- package/tools/property-extractor/templates/topic-property.hbs +32 -2
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The two prefixes `related_topics` has used since it was free text, and the two
|
|
4
|
+
// booleans `see_also` replaced them with. Both spellings stay supported: the
|
|
5
|
+
// prefix is what 79 entries in the live overrides file are written as, and the
|
|
6
|
+
// booleans are what a JSON schema can actually check.
|
|
7
|
+
const CLOUD_PREFIX = 'cloud-only:';
|
|
8
|
+
const SELF_MANAGED_PREFIX = 'self-managed-only:';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Read the audience an override value is scoped to.
|
|
12
|
+
*
|
|
13
|
+
* One parser for every field that can be narrowed to a single docs build, so
|
|
14
|
+
* `see_also`, `links`, description paragraphs and admonitions cannot drift apart
|
|
15
|
+
* on how the scope is spelled. Strings carry the scope as a leading
|
|
16
|
+
* `cloud-only:` / `self-managed-only:` prefix; objects carry it as a
|
|
17
|
+
* `cloud_only` / `self_managed_only` boolean beside a `content` string
|
|
18
|
+
* (`self_hosted_only` is the deprecated spelling of the same flag).
|
|
19
|
+
*
|
|
20
|
+
* Setting both would wrap the value in `ifdef` and `ifndef` at once, so it would
|
|
21
|
+
* render in neither build. The schema rejects that on objects; on a string it
|
|
22
|
+
* cannot happen, since only one prefix can lead.
|
|
23
|
+
*
|
|
24
|
+
* @param {string|{content: string, cloud_only?: boolean, self_managed_only?: boolean}} item
|
|
25
|
+
* @returns {{content: string, cloudOnly: boolean, selfManagedOnly: boolean}|null} Null when the item carries no usable content.
|
|
26
|
+
*/
|
|
27
|
+
function parseAudience(item) {
|
|
28
|
+
if (typeof item === 'string') {
|
|
29
|
+
const trimmed = item.trim();
|
|
30
|
+
if (trimmed.startsWith(CLOUD_PREFIX)) {
|
|
31
|
+
return { content: trimmed.slice(CLOUD_PREFIX.length).trim(), cloudOnly: true, selfManagedOnly: false };
|
|
32
|
+
}
|
|
33
|
+
if (trimmed.startsWith(SELF_MANAGED_PREFIX)) {
|
|
34
|
+
return { content: trimmed.slice(SELF_MANAGED_PREFIX.length).trim(), cloudOnly: false, selfManagedOnly: true };
|
|
35
|
+
}
|
|
36
|
+
return { content: trimmed, cloudOnly: false, selfManagedOnly: false };
|
|
37
|
+
}
|
|
38
|
+
if (item && typeof item === 'object' && typeof item.content === 'string') {
|
|
39
|
+
return {
|
|
40
|
+
content: item.content.trim(),
|
|
41
|
+
cloudOnly: item.cloud_only === true,
|
|
42
|
+
// self_hosted_only is the old name for the same flag. The product is
|
|
43
|
+
// called Self-Managed and the string prefix has always been
|
|
44
|
+
// `self-managed-only:`, so one concept was going by two different words
|
|
45
|
+
// depending on whether you wrote it as a prefix or a boolean. Still read,
|
|
46
|
+
// because it is in the published schema, but nothing emits it.
|
|
47
|
+
selfManagedOnly: item.self_managed_only === true || item.self_hosted_only === true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Wrap AsciiDoc in the conditional for one audience, or return it unchanged when
|
|
55
|
+
* it belongs in every build.
|
|
56
|
+
*
|
|
57
|
+
* The blank lines are not cosmetic. A conditional is resolved by the
|
|
58
|
+
* preprocessor, so one glued to neighbouring prose leaves those lines adjacent
|
|
59
|
+
* and Asciidoctor merges them into a single paragraph. Worse, the merge depends
|
|
60
|
+
* on the branch: a conditional wrapping a delimited block renders correctly with
|
|
61
|
+
* the attribute set and merges its neighbours with it unset, so a self-managed
|
|
62
|
+
* preview can look right while cloud-docs silently loses its paragraph breaks.
|
|
63
|
+
* Nothing errors and no text goes missing, so only a structural assertion on
|
|
64
|
+
* rendered HTML catches it -- see __tests__/tools/property-extractor/conditional-rendering.test.js.
|
|
65
|
+
*
|
|
66
|
+
* Emits the bare `endif::[]` and puts the cloud branch first, matching the
|
|
67
|
+
* convention already used throughout templates/property.hbs.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} content - AsciiDoc to wrap.
|
|
70
|
+
* @param {{cloudOnly: boolean, selfManagedOnly: boolean}} scope - Audience, as returned by parseAudience.
|
|
71
|
+
* @returns {string} The content, wrapped in a blank-line-separated conditional when it is scoped.
|
|
72
|
+
*/
|
|
73
|
+
function wrapForAudience(content, scope) {
|
|
74
|
+
if (!scope || (!scope.cloudOnly && !scope.selfManagedOnly)) return content;
|
|
75
|
+
const directive = scope.cloudOnly ? 'ifdef::env-cloud[]' : 'ifndef::env-cloud[]';
|
|
76
|
+
return `\n${directive}\n${content}\nendif::[]\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the paired conditional for a value that differs by audience, so each
|
|
81
|
+
* build sees its own form of the same passage.
|
|
82
|
+
*
|
|
83
|
+
* Used where a link cannot survive in one build -- a cross-page xref to a page
|
|
84
|
+
* Cloud does not publish -- and the sentence still has to read correctly there.
|
|
85
|
+
* Cloud branch first, same as wrapForAudience.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} cloudContent - What the Cloud build shows.
|
|
88
|
+
* @param {string} selfManagedContent - What the self-managed build shows.
|
|
89
|
+
* @returns {string} Both branches, blank-line separated.
|
|
90
|
+
*/
|
|
91
|
+
function wrapBothAudiences(cloudContent, selfManagedContent) {
|
|
92
|
+
if (cloudContent === selfManagedContent) return cloudContent;
|
|
93
|
+
// A blank line between the two directive pairs, not just around the
|
|
94
|
+
// whole block. Nothing downstream of this function currently calls it
|
|
95
|
+
// twice for overlapping text (applyLinksToText groups every scoped spec
|
|
96
|
+
// matching one paragraph into a single call), so nothing today depends on
|
|
97
|
+
// this specific line -- but paragraphBounds treats an unbroken run of
|
|
98
|
+
// conditional directives as ONE paragraph, exactly the mechanism that let
|
|
99
|
+
// a second duplication nest inside a first one's endif::[]/ifndef::[]
|
|
100
|
+
// pair with no blank line to stop it. Kept here too, defensively, so a
|
|
101
|
+
// future caller of this function alone does not have to rediscover that.
|
|
102
|
+
return [
|
|
103
|
+
'',
|
|
104
|
+
'ifdef::env-cloud[]',
|
|
105
|
+
cloudContent,
|
|
106
|
+
'endif::[]',
|
|
107
|
+
'',
|
|
108
|
+
'ifndef::env-cloud[]',
|
|
109
|
+
selfManagedContent,
|
|
110
|
+
'endif::[]',
|
|
111
|
+
'',
|
|
112
|
+
].join('\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find conditionals that are glued to neighbouring content.
|
|
117
|
+
*
|
|
118
|
+
* The complement to the cross-branch paragraph check in the corpus tests, and
|
|
119
|
+
* the only one of the two that catches the symmetric case. When a conditional in
|
|
120
|
+
* prose has no blank line before it, both builds merge the neighbouring
|
|
121
|
+
* paragraphs, so comparing the two renders sees no difference and reports
|
|
122
|
+
* nothing -- yet both are wrong. Reading the source catches it.
|
|
123
|
+
*
|
|
124
|
+
* Scoped to prose: a conditional whose neighbour is a table row (`|`), a block
|
|
125
|
+
* delimiter, a block attribute line (`[...]`), a list item or another
|
|
126
|
+
* conditional is left alone. Table cells have carried their own conditional
|
|
127
|
+
* shape since long before this, and a delimiter terminates the paragraph on its
|
|
128
|
+
* own.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} text - AsciiDoc to inspect.
|
|
131
|
+
* @returns {Array<{line: number, directive: string, neighbour: string, side: ('before'|'after')}>}
|
|
132
|
+
*/
|
|
133
|
+
function findGluedConditionals(text) {
|
|
134
|
+
if (typeof text !== 'string' || !text.includes('::')) return [];
|
|
135
|
+
const lines = text.split('\n');
|
|
136
|
+
const offenders = [];
|
|
137
|
+
const selfTerminating = (line) => {
|
|
138
|
+
const t = line.trim();
|
|
139
|
+
if (t === '') return true;
|
|
140
|
+
if (/^if(n?)def::/.test(t) || t === 'endif::[]') return true;
|
|
141
|
+
if (/^(?:-{4,}|={4,}|\.{4,}|\*{4,}|_{4,}|\+{4,}|\/{4,})$/.test(t)) return true;
|
|
142
|
+
if (/^\[.*\]$/.test(t)) return true; // block attribute, e.g. [NOTE] or [cols="1s,2a"]
|
|
143
|
+
if (/^[|!]/.test(t)) return true; // table row or cell
|
|
144
|
+
if (/^[*.-]+\s/.test(t) || /^\d+\.\s/.test(t)) return true; // list item
|
|
145
|
+
if (/^\.\S/.test(t)) return true; // block title, e.g. .Enterprise license required
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
lines.forEach((line, i) => {
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
if (/^if(n?)def::/.test(trimmed)) {
|
|
151
|
+
const before = i === 0 ? '' : lines[i - 1];
|
|
152
|
+
if (!selfTerminating(before)) {
|
|
153
|
+
offenders.push({ line: i + 1, directive: trimmed, neighbour: before.trim(), side: 'before' });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (trimmed === 'endif::[]') {
|
|
157
|
+
const after = i === lines.length - 1 ? '' : lines[i + 1];
|
|
158
|
+
if (!selfTerminating(after)) {
|
|
159
|
+
offenders.push({ line: i + 1, directive: trimmed, neighbour: after.trim(), side: 'after' });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return offenders;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = parseAudience;
|
|
167
|
+
module.exports.parseAudience = parseAudience;
|
|
168
|
+
module.exports.wrapForAudience = wrapForAudience;
|
|
169
|
+
module.exports.wrapBothAudiences = wrapBothAudiences;
|
|
170
|
+
module.exports.findGluedConditionals = findGluedConditionals;
|
|
171
|
+
module.exports.CLOUD_PREFIX = CLOUD_PREFIX;
|
|
172
|
+
module.exports.SELF_MANAGED_PREFIX = SELF_MANAGED_PREFIX;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CLOUD_PREFIX, SELF_MANAGED_PREFIX } = require('./audienceScope.js');
|
|
4
|
+
|
|
5
|
+
// A prefix that was clearly MEANT to scope a value to one docs build, spelled
|
|
6
|
+
// in a way the parser does not accept. Deliberately generous: underscores for
|
|
7
|
+
// hyphens, a space before the colon, any casing, a missing separator, and the
|
|
8
|
+
// retired `self-hosted-only` product name.
|
|
9
|
+
//
|
|
10
|
+
// Why this matters more than an unmatched link: an unrecognised prefix is not
|
|
11
|
+
// dropped, it is published. `cloud_only: Cloud clusters require...` renders on
|
|
12
|
+
// the page as that literal string, prefix and all, in both builds. Nothing
|
|
13
|
+
// errors, nothing is missing, and the only way to notice is to read the
|
|
14
|
+
// rendered page.
|
|
15
|
+
const SUSPICIOUS_PREFIX = /^[\s]*((?:cloud|self[-_ ]?managed|self[-_ ]?hosted)[-_ ]?only)[\s]*:/i;
|
|
16
|
+
|
|
17
|
+
// Keys that look like an attempt at the audience booleans but are not the two
|
|
18
|
+
// the templates read. `self_hosted_only` is absent on purpose: it is the
|
|
19
|
+
// deprecated spelling and parseAudience still honours it.
|
|
20
|
+
const VALID_FLAGS = new Set(['cloud_only', 'self_managed_only', 'self_hosted_only']);
|
|
21
|
+
const SUSPICIOUS_FLAG = /^(?:cloud|self[-_ ]?managed|self[-_ ]?hosted)[-_ ]?only$/i;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Report a string whose leading prefix looks like a failed audience scope.
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} value
|
|
27
|
+
* @returns {string|null} The offending prefix, or null when the value is fine.
|
|
28
|
+
*/
|
|
29
|
+
function findSuspiciousPrefix (value) {
|
|
30
|
+
if (typeof value !== 'string') return null;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
// An exact prefix is correct by definition, so check those first.
|
|
33
|
+
if (trimmed.startsWith(CLOUD_PREFIX) || trimmed.startsWith(SELF_MANAGED_PREFIX)) return null;
|
|
34
|
+
const match = SUSPICIOUS_PREFIX.exec(trimmed);
|
|
35
|
+
return match ? match[1] : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Report object keys that look like an attempt at the audience booleans.
|
|
40
|
+
*
|
|
41
|
+
* A misspelled flag is silent in a different way: the template's `{{#if
|
|
42
|
+
* cloud_only}}` is simply false, so the value renders in BOTH builds rather
|
|
43
|
+
* than the one the writer scoped it to.
|
|
44
|
+
*
|
|
45
|
+
* @param {unknown} item
|
|
46
|
+
* @returns {string[]} The offending key names.
|
|
47
|
+
*/
|
|
48
|
+
function findSuspiciousFlags (item) {
|
|
49
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return [];
|
|
50
|
+
return Object.keys(item).filter((key) => !VALID_FLAGS.has(key) && SUSPICIOUS_FLAG.test(key));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pushPrefix (problems, property, field, value) {
|
|
54
|
+
const prefix = findSuspiciousPrefix(value);
|
|
55
|
+
if (prefix) {
|
|
56
|
+
problems.push({
|
|
57
|
+
property,
|
|
58
|
+
field,
|
|
59
|
+
problem: 'unrecognized-prefix',
|
|
60
|
+
detail: prefix,
|
|
61
|
+
text: String(value).trim().slice(0, 120)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pushFlags (problems, property, field, item) {
|
|
67
|
+
for (const key of findSuspiciousFlags(item)) {
|
|
68
|
+
problems.push({ property, field, problem: 'unrecognized-flag', detail: key, text: '' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Walk every audience-scopable field on every property and report the scopes
|
|
74
|
+
* that will not take effect.
|
|
75
|
+
*
|
|
76
|
+
* Run BEFORE applyPropertyLinks: that pass flattens an array description into
|
|
77
|
+
* a single string, after which a per-paragraph prefix can no longer be
|
|
78
|
+
* attributed to the paragraph it came from.
|
|
79
|
+
*
|
|
80
|
+
* @param {Record<string, object>} properties
|
|
81
|
+
* @returns {Array<{property: string, field: string, problem: string, detail: string, text: string}>}
|
|
82
|
+
*/
|
|
83
|
+
function findAudienceScopeProblems (properties) {
|
|
84
|
+
const problems = [];
|
|
85
|
+
for (const [name, prop] of Object.entries(properties || {})) {
|
|
86
|
+
if (!prop || typeof prop !== 'object') continue;
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(prop.description)) {
|
|
89
|
+
prop.description.forEach((para, i) => pushPrefix(problems, name, `description[${i}]`, para));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (prop.links && typeof prop.links === 'object') {
|
|
93
|
+
for (const [key, value] of Object.entries(prop.links)) {
|
|
94
|
+
pushPrefix(problems, name, `links["${key}"]`, value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Both spellings: `related_topics` is the field 118 live entries use, and
|
|
99
|
+
// `see_also` is what replaced it.
|
|
100
|
+
for (const field of ['see_also', 'related_topics']) {
|
|
101
|
+
const items = prop[field];
|
|
102
|
+
if (!Array.isArray(items)) continue;
|
|
103
|
+
items.forEach((item, i) => {
|
|
104
|
+
pushPrefix(problems, name, `${field}[${i}]`, item);
|
|
105
|
+
pushFlags(problems, name, `${field}[${i}]`, item);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const field of ['admonitions', 'includes']) {
|
|
110
|
+
const items = prop[field];
|
|
111
|
+
if (!Array.isArray(items)) continue;
|
|
112
|
+
items.forEach((item, i) => pushFlags(problems, name, `${field}[${i}]`, item));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return problems;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = findAudienceScopeProblems;
|
|
119
|
+
module.exports.findAudienceScopeProblems = findAudienceScopeProblems;
|
|
120
|
+
module.exports.findSuspiciousPrefix = findSuspiciousPrefix;
|
|
121
|
+
module.exports.findSuspiciousFlags = findSuspiciousFlags;
|
|
@@ -17,6 +17,9 @@ module.exports = {
|
|
|
17
17
|
normalizePropertyAnchors: require('./normalizePropertyAnchors.js'),
|
|
18
18
|
canonicalizePropertyXrefs: require('./canonicalizePropertyXrefs.js'),
|
|
19
19
|
seeAlsoView: require('./seeAlsoView.js'),
|
|
20
|
+
audienceScope: require('./audienceScope.js'),
|
|
21
|
+
applyPropertyLinks: require('./applyPropertyLinks.js'),
|
|
22
|
+
findAudienceScopeProblems: require('./findAudienceScopeProblems.js'),
|
|
20
23
|
capitalize: require('./capitalize.js'),
|
|
21
24
|
length: require('./length.js'),
|
|
22
25
|
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const parseAudience = require('./audienceScope.js');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Normalizes a property's related-topics data into one shape for the
|
|
5
7
|
* templates to render, regardless of which field or convention the override
|
|
6
8
|
* author used:
|
|
7
9
|
*
|
|
8
10
|
* - see_also (current): array of items, each either a plain string (shown
|
|
9
|
-
* everywhere) or {content, cloud_only,
|
|
11
|
+
* everywhere) or {content, cloud_only, self_managed_only} (shown only in
|
|
10
12
|
* the named build). Schema-validated — see docs-data/property-overrides.schema.json.
|
|
11
13
|
* - related_topics (deprecated, still read for back-compat with existing
|
|
12
14
|
* override files): array of strings, optionally prefixed with
|
|
@@ -14,8 +16,11 @@
|
|
|
14
16
|
* behaviour. That prefix was free text with no validation; see_also
|
|
15
17
|
* replaces it with data a schema can check.
|
|
16
18
|
*
|
|
19
|
+
* Both spellings are read by the shared helpers/audienceScope.js parser, which
|
|
20
|
+
* every audience-scoped override field uses, so they cannot drift apart.
|
|
21
|
+
*
|
|
17
22
|
* @param {object} property - A property record, as extracted or overridden.
|
|
18
|
-
* @returns {Array<{content: string, cloudOnly: boolean,
|
|
23
|
+
* @returns {Array<{content: string, cloudOnly: boolean, selfManagedOnly: boolean}>}
|
|
19
24
|
*/
|
|
20
25
|
function normalizeSeeAlso(property) {
|
|
21
26
|
if (!property) return [];
|
|
@@ -26,26 +31,7 @@ function normalizeSeeAlso(property) {
|
|
|
26
31
|
: [];
|
|
27
32
|
|
|
28
33
|
return rawItems
|
|
29
|
-
.map((item) =>
|
|
30
|
-
if (typeof item === 'string') {
|
|
31
|
-
const trimmed = item.trim();
|
|
32
|
-
if (trimmed.startsWith('cloud-only:')) {
|
|
33
|
-
return { content: trimmed.slice('cloud-only:'.length).trim(), cloudOnly: true, selfHostedOnly: false };
|
|
34
|
-
}
|
|
35
|
-
if (trimmed.startsWith('self-managed-only:')) {
|
|
36
|
-
return { content: trimmed.slice('self-managed-only:'.length).trim(), cloudOnly: false, selfHostedOnly: true };
|
|
37
|
-
}
|
|
38
|
-
return { content: trimmed, cloudOnly: false, selfHostedOnly: false };
|
|
39
|
-
}
|
|
40
|
-
if (item && typeof item === 'object' && typeof item.content === 'string') {
|
|
41
|
-
return {
|
|
42
|
-
content: item.content.trim(),
|
|
43
|
-
cloudOnly: item.cloud_only === true,
|
|
44
|
-
selfHostedOnly: item.self_hosted_only === true,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
})
|
|
34
|
+
.map((item) => parseAudience(item))
|
|
49
35
|
.filter((item) => item && item.content);
|
|
50
36
|
}
|
|
51
37
|
|
|
@@ -56,7 +42,7 @@ function normalizeSeeAlso(property) {
|
|
|
56
42
|
* once instead of duplicated per item.
|
|
57
43
|
*
|
|
58
44
|
* sectionType is 'cloud' only when every item is cloud-only, 'self-managed'
|
|
59
|
-
* only when every item is self-
|
|
45
|
+
* only when every item is self-managed-only, and 'normal' otherwise (a mix of
|
|
60
46
|
* conditional and/or unconditional items, each wrapped individually).
|
|
61
47
|
*
|
|
62
48
|
* @param {object} property - A property record, as extracted or overridden.
|
|
@@ -67,7 +53,7 @@ function seeAlsoView(property) {
|
|
|
67
53
|
let sectionType = 'normal';
|
|
68
54
|
if (items.length > 0) {
|
|
69
55
|
if (items.every((item) => item.cloudOnly)) sectionType = 'cloud';
|
|
70
|
-
else if (items.every((item) => item.
|
|
56
|
+
else if (items.every((item) => item.selfManagedOnly)) sectionType = 'self-managed';
|
|
71
57
|
}
|
|
72
58
|
return { items, sectionType };
|
|
73
59
|
}
|
|
@@ -1243,15 +1243,43 @@ def _normalize_admonitions(admonitions):
|
|
|
1243
1243
|
continue
|
|
1244
1244
|
if entry["title"]:
|
|
1245
1245
|
normalized_entry["title"] = entry["title"]
|
|
1246
|
+
# The entry is rebuilt rather than copied, so every field the templates
|
|
1247
|
+
# read has to be carried across explicitly. These two scope the whole
|
|
1248
|
+
# admonition block to one docs build; setting both would wrap it in
|
|
1249
|
+
# ifdef and ifndef at once so it rendered in neither, which the schema
|
|
1250
|
+
# rejects and the generator drops with a warning.
|
|
1251
|
+
if entry.get("cloud_only") is True:
|
|
1252
|
+
normalized_entry["cloud_only"] = True
|
|
1253
|
+
# self_hosted_only is the deprecated spelling of self_managed_only, read
|
|
1254
|
+
# so an override written against the older schema still scopes, and
|
|
1255
|
+
# normalized to the current name so only one spelling reaches the
|
|
1256
|
+
# templates.
|
|
1257
|
+
if entry.get("self_managed_only") is True or entry.get("self_hosted_only") is True:
|
|
1258
|
+
normalized_entry["self_managed_only"] = True
|
|
1246
1259
|
normalized.append(normalized_entry)
|
|
1247
1260
|
return normalized
|
|
1248
1261
|
|
|
1249
1262
|
|
|
1250
1263
|
def _apply_override_to_existing_property(property_dict, override, overrides_file_path):
|
|
1251
1264
|
"""Apply overrides to an existing property."""
|
|
1252
|
-
# Apply description override
|
|
1265
|
+
# Apply description override. Either a plain string or, when a paragraph has
|
|
1266
|
+
# to be scoped to one docs build, an array of paragraphs; the generator
|
|
1267
|
+
# flattens the array into AsciiDoc so the audience conditional never has to
|
|
1268
|
+
# be hand-written into the JSON string.
|
|
1253
1269
|
if "description" in override:
|
|
1254
1270
|
property_dict["description"] = override["description"]
|
|
1271
|
+
|
|
1272
|
+
# Declared links: {"<text in the prose>": "<target>"}. Carried through as
|
|
1273
|
+
# data and turned into AsciiDoc by the generator, which is what keeps the
|
|
1274
|
+
# description itself plain prose the overrides audit can compare to source.
|
|
1275
|
+
if "links" in override:
|
|
1276
|
+
property_dict["links"] = override["links"]
|
|
1277
|
+
|
|
1278
|
+
# Shared partials pulled into the property entry (the "internal use only"
|
|
1279
|
+
# warning, the HTTP Proxy breaking-change notice). Carried as data so the
|
|
1280
|
+
# description stays prose the overrides audit can compare against source.
|
|
1281
|
+
if "includes" in override:
|
|
1282
|
+
property_dict["includes"] = override["includes"]
|
|
1255
1283
|
|
|
1256
1284
|
# Apply version override (introduced in version)
|
|
1257
1285
|
if "version" in override:
|
|
@@ -1282,7 +1310,7 @@ def _apply_override_to_existing_property(property_dict, override, overrides_file
|
|
|
1282
1310
|
logger.warning(f"related_topics for property must be an array")
|
|
1283
1311
|
|
|
1284
1312
|
# Apply see_also override. Structural shape (plain string, or an object
|
|
1285
|
-
# naming exactly one of cloud_only/
|
|
1313
|
+
# naming exactly one of cloud_only/self_managed_only) is enforced by
|
|
1286
1314
|
# docs-data/property-overrides.schema.json via `doc-tools validate
|
|
1287
1315
|
# property-overrides`, not here — this just passes the data through for
|
|
1288
1316
|
# seeAlsoView.js to normalize at render time.
|
|
@@ -27,10 +27,34 @@ endif::[]
|
|
|
27
27
|
|
|
28
28
|
No description available.
|
|
29
29
|
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{#if includes}}
|
|
32
|
+
{{#each includes}}
|
|
33
|
+
|
|
34
|
+
{{#if cloud_only}}
|
|
35
|
+
ifdef::env-cloud[]
|
|
36
|
+
{{/if}}
|
|
37
|
+
{{#if self_managed_only}}
|
|
38
|
+
ifndef::env-cloud[]
|
|
39
|
+
{{/if}}
|
|
40
|
+
include::{{{target}}}
|
|
41
|
+
{{#if cloud_only}}
|
|
42
|
+
endif::[]
|
|
43
|
+
{{/if}}
|
|
44
|
+
{{#if self_managed_only}}
|
|
45
|
+
endif::[]
|
|
46
|
+
{{/if}}
|
|
47
|
+
{{/each}}
|
|
30
48
|
{{/if}}
|
|
31
49
|
{{#if admonitions}}
|
|
32
50
|
{{#each admonitions}}
|
|
33
51
|
|
|
52
|
+
{{#if cloud_only}}
|
|
53
|
+
ifdef::env-cloud[]
|
|
54
|
+
{{/if}}
|
|
55
|
+
{{#if self_managed_only}}
|
|
56
|
+
ifndef::env-cloud[]
|
|
57
|
+
{{/if}}
|
|
34
58
|
{{#if title}}
|
|
35
59
|
.{{{title}}}
|
|
36
60
|
{{/if}}
|
|
@@ -38,6 +62,12 @@ No description available.
|
|
|
38
62
|
====
|
|
39
63
|
{{{text}}}
|
|
40
64
|
====
|
|
65
|
+
{{#if cloud_only}}
|
|
66
|
+
endif::[]
|
|
67
|
+
{{/if}}
|
|
68
|
+
{{#if self_managed_only}}
|
|
69
|
+
endif::[]
|
|
70
|
+
{{/if}}
|
|
41
71
|
{{/each}}
|
|
42
72
|
{{/if}}
|
|
43
73
|
{{#if is_enterprise}}
|
|
@@ -222,7 +252,7 @@ endif::[]
|
|
|
222
252
|
ifdef::env-cloud[]
|
|
223
253
|
* {{{content}}}
|
|
224
254
|
endif::[]
|
|
225
|
-
{{else if
|
|
255
|
+
{{else if selfManagedOnly}}
|
|
226
256
|
ifndef::env-cloud[]
|
|
227
257
|
* {{{content}}}
|
|
228
258
|
endif::[]
|
|
@@ -236,7 +266,7 @@ endif::[]
|
|
|
236
266
|
ifdef::env-cloud[]
|
|
237
267
|
{{{content}}}
|
|
238
268
|
endif::[]
|
|
239
|
-
{{else if
|
|
269
|
+
{{else if selfManagedOnly}}
|
|
240
270
|
ifndef::env-cloud[]
|
|
241
271
|
{{{content}}}
|
|
242
272
|
endif::[]
|
|
@@ -25,10 +25,34 @@
|
|
|
25
25
|
|
|
26
26
|
No description available.
|
|
27
27
|
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#if includes}}
|
|
30
|
+
{{#each includes}}
|
|
31
|
+
|
|
32
|
+
{{#if cloud_only}}
|
|
33
|
+
ifdef::env-cloud[]
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if self_managed_only}}
|
|
36
|
+
ifndef::env-cloud[]
|
|
37
|
+
{{/if}}
|
|
38
|
+
include::{{{target}}}
|
|
39
|
+
{{#if cloud_only}}
|
|
40
|
+
endif::[]
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{#if self_managed_only}}
|
|
43
|
+
endif::[]
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{/each}}
|
|
28
46
|
{{/if}}
|
|
29
47
|
{{#if admonitions}}
|
|
30
48
|
{{#each admonitions}}
|
|
31
49
|
|
|
50
|
+
{{#if cloud_only}}
|
|
51
|
+
ifdef::env-cloud[]
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{#if self_managed_only}}
|
|
54
|
+
ifndef::env-cloud[]
|
|
55
|
+
{{/if}}
|
|
32
56
|
{{#if title}}
|
|
33
57
|
.{{{title}}}
|
|
34
58
|
{{/if}}
|
|
@@ -36,6 +60,12 @@ No description available.
|
|
|
36
60
|
====
|
|
37
61
|
{{{text}}}
|
|
38
62
|
====
|
|
63
|
+
{{#if cloud_only}}
|
|
64
|
+
endif::[]
|
|
65
|
+
{{/if}}
|
|
66
|
+
{{#if self_managed_only}}
|
|
67
|
+
endif::[]
|
|
68
|
+
{{/if}}
|
|
39
69
|
{{/each}}
|
|
40
70
|
{{/if}}
|
|
41
71
|
{{#if is_enterprise}}
|
|
@@ -179,7 +209,7 @@ endif::[]
|
|
|
179
209
|
ifdef::env-cloud[]
|
|
180
210
|
* {{{content}}}
|
|
181
211
|
endif::[]
|
|
182
|
-
{{else if
|
|
212
|
+
{{else if selfManagedOnly}}
|
|
183
213
|
ifndef::env-cloud[]
|
|
184
214
|
* {{{content}}}
|
|
185
215
|
endif::[]
|
|
@@ -193,7 +223,7 @@ endif::[]
|
|
|
193
223
|
ifdef::env-cloud[]
|
|
194
224
|
{{{content}}}
|
|
195
225
|
endif::[]
|
|
196
|
-
{{else if
|
|
226
|
+
{{else if selfManagedOnly}}
|
|
197
227
|
ifndef::env-cloud[]
|
|
198
228
|
{{{content}}}
|
|
199
229
|
endif::[]
|