Skip to content

Commit 9c3caef

Browse files
feat(settings): add :deployment: filter option to {settings} directive (#3318)
Adds a new :deployment: option to the {settings} directive that hides settings not available for the specified deployment type (ech, ece, eck, self). Settings with applies_to metadata that lack an explicit entry for the requested deployment are treated as unavailable. Settings without any applies_to metadata are always shown. Updates docs/syntax/automated_settings.md to document the new option and adds filtered examples to the Kibana settings test page. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 286866d commit 9c3caef

8 files changed

Lines changed: 279 additions & 17 deletions

File tree

docs/syntax/automated_settings.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@ The `{settings}` directive is generic. Although the largest current examples com
1111
::::
1212
```
1313

14+
#### Options
15+
16+
`:deployment: <value>`
17+
: Filters the rendered settings to only those available for the specified deployment type. When omitted, all settings are shown regardless of deployment.
18+
19+
Valid values: `ech` (Elastic Cloud Hosted), `ece` (Elastic Cloud Enterprise), `eck` (Elastic Cloud on Kubernetes), `self` (self-managed).
20+
21+
A setting is considered available for a deployment type if its `applies_to.deployment` block explicitly lists that deployment with a non-removed lifecycle. If a setting has `applies_to` metadata but no entry for the requested deployment, it is treated as unavailable and hidden.
22+
23+
Settings with no `applies_to` metadata at all are always shown, regardless of the filter.
24+
25+
```markdown
26+
::::{settings} /syntax/settings-with-applies-example.yml
27+
:deployment: ech
28+
::::
29+
```
30+
1431
### Schema
1532

1633
The schema below reflects the structure currently supported by docs-builder. For the original settings-gen schema that inspired this format, see [the Kibana schema reference](https://github.com/elastic/kibana/tree/main/docs/settings-gen#schema).

docs/testing/kibana-settings-yaml-samples.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,29 @@ Some descriptions use links and anchors that target the real Kibana reference pa
3333

3434
:::{settings} /testing/kibana-security-settings.yml
3535
:::
36+
37+
## Deployment filter preview
38+
39+
The `:deployment:` option filters settings to only those available for the specified deployment type.
40+
Settings with no `applies_to` are always shown. Settings with `applies_to` that do not explicitly list
41+
the deployment are treated as unavailable.
42+
43+
Accepted values: `ech`, `ece`, `eck`, `self`.
44+
45+
### kibana-general-settings.yml — ECH only
46+
47+
:::{settings} /testing/kibana-general-settings.yml
48+
:deployment: ech
49+
:::
50+
51+
### kibana-general-settings.yml — self-managed only
52+
53+
:::{settings} /testing/kibana-general-settings.yml
54+
:deployment: self
55+
:::
56+
57+
### kibana-security-settings.yml — ECH only
58+
59+
:::{settings} /testing/kibana-security-settings.yml
60+
:deployment: ech
61+
:::

src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ private static void WriteSettingsBlock(HtmlRenderer renderer, SettingsBlock bloc
502502
SettingsCollection = settings,
503503
GroupHeadingLevel = block.GroupHeadingLevel,
504504
VersionsConfig = block.Build.VersionsConfiguration,
505+
ActiveDeploymentFilter = block.ActiveDeploymentFilter,
505506
RenderMarkdown = s =>
506507
{
507508
var normalized = SettingsMarkdownNormalizer.Normalize(s, settings.Product);

src/Elastic.Markdown/Myst/Directives/Settings/SettingsBlock.cs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public class SettingsBlock(DirectiveBlockParser parser, ParserContext context) :
3131

3232
public bool Found { get; private set; }
3333

34+
/// <summary>
35+
/// When set, only settings available for this deployment type are rendered.
36+
/// Accepted values: <c>ech</c>, <c>ece</c>, <c>eck</c>, <c>self</c>.
37+
/// </summary>
38+
public string? ActiveDeploymentFilter { get; private set; }
39+
3440
/// <summary>
3541
/// Heading level for each YAML group title (e.g. 3 when the directive follows an <c>##</c> heading), same rule as <see cref="Stepper.StepBlock"/>.
3642
/// </summary>
@@ -49,19 +55,26 @@ public IEnumerable<PageTocItem> GeneratedTableOfContent
4955
return [];
5056

5157
var level = GroupHeadingLevel;
52-
return settings.Groups.Select(g => new PageTocItem
53-
{
54-
Heading = g.Name ?? string.Empty,
55-
Slug = SettingsViewModel.GroupHeadingSlug(g),
56-
Level = level
57-
}).Where(t => !string.IsNullOrEmpty(t.Slug));
58+
return settings.Groups
59+
.Where(g => ActiveDeploymentFilter is null ||
60+
DeploymentFilter.AnyVisible(g.Settings, ActiveDeploymentFilter, null))
61+
.Select(g => new PageTocItem
62+
{
63+
Heading = g.Name ?? string.Empty,
64+
Slug = SettingsViewModel.GroupHeadingSlug(g),
65+
Level = level
66+
}).Where(t => !string.IsNullOrEmpty(t.Slug));
5867
}
5968
}
6069

6170

6271
//TODO add all options from
6372
//https://mystmd.org/guide/directives#directive-include
64-
public override void FinalizeAndValidate(ParserContext context) => ExtractInclusionPath(context);
73+
public override void FinalizeAndValidate(ParserContext context)
74+
{
75+
ExtractInclusionPath(context);
76+
ValidateDeploymentFilter();
77+
}
6578

6679
/// <summary>
6780
/// Records docset substitution keys referenced in raw settings YAML (e.g. <c>page_description</c>) so
@@ -118,6 +131,24 @@ private static void PrepareSettingForRendering(Setting setting, ParserContext co
118131
PrepareSettingForRendering(child, context);
119132
}
120133

134+
private void ValidateDeploymentFilter()
135+
{
136+
var raw = Prop("deployment");
137+
if (raw is null)
138+
return;
139+
140+
var trimmed = raw.Trim().ToLowerInvariant();
141+
if (!DeploymentFilter.ValidValues.Contains(trimmed))
142+
{
143+
this.EmitWarning(
144+
$"Unknown deployment filter '{raw}'. Valid values are: {string.Join(", ", DeploymentFilter.ValidValues)}."
145+
);
146+
return;
147+
}
148+
149+
ActiveDeploymentFilter = trimmed;
150+
}
151+
121152
private void ExtractInclusionPath(ParserContext context)
122153
{
123154
var includePath = Arguments;
@@ -207,33 +238,41 @@ private string[] LoadGeneratedAnchors()
207238
if (TryLoadSettings() is not { } settings)
208239
return [];
209240

210-
return CollectSettingIds(settings).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
241+
return CollectSettingIds(settings, ActiveDeploymentFilter).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
211242
}
212243

213-
private static IEnumerable<string> CollectSettingIds(YamlSettings yaml)
244+
private static IEnumerable<string> CollectSettingIds(YamlSettings yaml, string? deploymentFilter)
214245
{
215246
if (!string.IsNullOrWhiteSpace(yaml.Id))
216247
yield return yaml.Id;
217248

218249
foreach (var group in yaml.Groups)
219250
{
251+
var groupVisible = deploymentFilter is null ||
252+
DeploymentFilter.AnyVisible(group.Settings, deploymentFilter, null);
253+
if (!groupVisible)
254+
continue;
255+
220256
var groupSlug = SettingsViewModel.GroupHeadingSlug(group);
221257
if (!string.IsNullOrEmpty(groupSlug))
222258
yield return groupSlug;
223-
foreach (var id in CollectSettingIds(group.Settings, parentName: null))
259+
foreach (var id in CollectSettingIds(group.Settings, parentName: null, deploymentFilter))
224260
yield return id;
225261
}
226262
}
227263

228-
private static IEnumerable<string> CollectSettingIds(Setting[] settings, string? parentName)
264+
private static IEnumerable<string> CollectSettingIds(Setting[] settings, string? parentName, string? deploymentFilter)
229265
{
230266
foreach (var setting in settings)
231267
{
268+
if (deploymentFilter is not null && !setting.IsVisibleForDeployment(deploymentFilter, null))
269+
continue;
270+
232271
var displayName = SettingsViewModel.ComposeSettingName(parentName, setting.Name);
233272
var fragmentId = SettingsViewModel.SettingFragmentId(setting, displayName);
234273
if (!string.IsNullOrWhiteSpace(fragmentId))
235274
yield return fragmentId;
236-
foreach (var id in CollectSettingIds(setting.Settings, displayName))
275+
foreach (var id in CollectSettingIds(setting.Settings, displayName, deploymentFilter))
237276
yield return id;
238277
}
239278
}

src/Elastic.Markdown/Myst/Directives/Settings/SettingsView.cshtml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
@RenderAdmonition("note", Model.SettingsCollection.Note)
77
@foreach (var group in Model.SettingsCollection.Groups)
88
{
9-
@RenderGroup(group)
9+
if (Model.IsGroupVisible(group))
10+
{
11+
@RenderGroup(group)
12+
}
1013
}
1114
@functions {
1215

@@ -65,12 +68,15 @@
6568
@RenderMarkdown(group.Description)
6669
@RenderAdmonition("note", group.Note)
6770
@RenderExample(group.Example)
68-
<dl>
69-
@foreach (var setting in group.Settings)
71+
<dl>
72+
@foreach (var setting in group.Settings)
73+
{
74+
if (Model.IsSettingVisible(setting, null))
7075
{
7176
@RenderSetting(setting, null, null)
7277
}
73-
</dl>
78+
}
79+
</dl>
7480
return HtmlString.Empty;
7581
}
7682

@@ -99,7 +105,10 @@
99105
<dl>
100106
@foreach (var child in setting.Settings)
101107
{
102-
@RenderSetting(child, displayName, appliesTo)
108+
if (Model.IsSettingVisible(child, appliesTo))
109+
{
110+
@RenderSetting(child, displayName, appliesTo)
111+
}
103112
}
104113
</dl>
105114
}

src/Elastic.Markdown/Myst/Directives/Settings/SettingsViewModel.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ public class SettingsViewModel
2222
/// <summary>Markdown heading level for each group section (1–6).</summary>
2323
public required int GroupHeadingLevel { get; init; }
2424

25+
/// <summary>
26+
/// When set, only settings visible for this deployment type are rendered.
27+
/// Accepted values: <c>ech</c>, <c>ece</c>, <c>eck</c>, <c>self</c>.
28+
/// </summary>
29+
public string? ActiveDeploymentFilter { get; init; }
30+
31+
public bool IsGroupVisible(SettingsGrouping group) =>
32+
ActiveDeploymentFilter is null ||
33+
DeploymentFilter.AnyVisible(group.Settings, ActiveDeploymentFilter, null);
34+
35+
public bool IsSettingVisible(Setting setting, ApplicableTo? inheritedAppliesTo) =>
36+
ActiveDeploymentFilter is null ||
37+
setting.IsVisibleForDeployment(ActiveDeploymentFilter, inheritedAppliesTo);
38+
2539
public string RenderAppliesToInline(ApplicableTo? appliesTo) =>
2640
RenderAppliesToPlacement(appliesTo, ApplicabilityBadgePlacement.Combined);
2741

src/Elastic.Markdown/Myst/Directives/Settings/StructuredSettings.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,54 @@ public static class SettingDisplay
116116
_ => value.ToString()
117117
};
118118
}
119+
120+
public static class DeploymentFilter
121+
{
122+
/// <summary>Valid filter tokens accepted by the <c>:deployment:</c> directive option.</summary>
123+
public static readonly IReadOnlySet<string> ValidValues =
124+
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ech", "ece", "eck", "self" };
125+
126+
/// <summary>
127+
/// Returns the <see cref="AppliesCollection"/> for the given deployment filter key,
128+
/// mapping the canonical <c>ech</c> token to the <c>ess</c> model field.
129+
/// Returns <c>null</c> when the deployment type is not mentioned (i.e. not available).
130+
/// </summary>
131+
public static AppliesCollection? GetForDeployment(this DeploymentApplicability deployment, string key) =>
132+
key.ToLowerInvariant() switch
133+
{
134+
"ech" => deployment.Ess,
135+
"ece" => deployment.Ece,
136+
"eck" => deployment.Eck,
137+
"self" => deployment.Self,
138+
_ => null
139+
};
140+
141+
/// <summary>
142+
/// Returns <c>true</c> when the setting should be shown for the given deployment filter.
143+
/// A setting with no <c>applies_to</c> at all is always visible (no restriction).
144+
/// A setting with <c>applies_to</c> that does not explicitly list the deployment is considered unavailable.
145+
/// </summary>
146+
public static bool IsVisibleForDeployment(this Setting setting, string deploymentFilter, ApplicableTo? inheritedAppliesTo)
147+
{
148+
var appliesTo = setting.ResolveAppliesTo(inheritedAppliesTo);
149+
150+
if (appliesTo is null)
151+
return true;
152+
153+
if (appliesTo.Deployment is not { } deployment)
154+
return false;
155+
156+
var col = deployment.GetForDeployment(deploymentFilter);
157+
if (col is null)
158+
return false;
159+
160+
return col.Any(a => a.Lifecycle is not ProductLifecycle.Removed and not ProductLifecycle.Unavailable);
161+
}
162+
163+
/// <summary>Returns <c>true</c> when at least one setting (recursively) in <paramref name="settings"/> is visible.</summary>
164+
public static bool AnyVisible(Setting[] settings, string deploymentFilter, ApplicableTo? inheritedAppliesTo) =>
165+
settings.Any(s =>
166+
s.IsVisibleForDeployment(deploymentFilter, inheritedAppliesTo) ||
167+
AnyVisible(s.Settings, deploymentFilter, s.ResolveAppliesTo(inheritedAppliesTo))
168+
);
169+
}

0 commit comments

Comments
 (0)