Skip to content

Commit f68d68b

Browse files
authored
Add product features support on products.yml (#3043)
* Add features mapping to products.yml * Add docs-builder to products.yml for release notes * Add tests * Introduce VersioningSystem.None for products outside the public documentation cycle * Set sentinel non-value for None * Throw if there is no valid versioning for a product with public-reference enabled
1 parent 6d125b6 commit f68d68b

25 files changed

Lines changed: 249 additions & 20 deletions

File tree

config/changelog.example.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
filename: timestamp
2020

2121
# Products configuration (optional)
22-
# If not specified, all products from products.yml are allowed
22+
# If not specified, all products from products.yml are allowed (including those
23+
# that have 'public-reference' disabled).
2324
products:
2425
# List of available product IDs (empty = all from products.yml)
2526
# Accepts string or list: "elasticsearch, kibana" or [elasticsearch, kibana]

config/products.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ products:
5959
versioning: 'terraform-google-edot-cf'
6060
curator:
6161
display: 'Elasticsearch Curator'
62+
docs-builder:
63+
display: 'Elastic Docs Builder'
64+
repository: 'docs-builder'
65+
features:
66+
public-reference: false
6267
ecs:
6368
display: 'Elastic Common Schema (ECS)'
6469
ecs-logging:

docs/configure/site/products.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ products:
1111
display: 'Elastic Distribution of OpenTelemetry Collector'
1212
versioning: 'stack'
1313
repository: 'elastic-edot-collector'
14+
docs-builder:
15+
display: 'Elastic Docs Builder'
16+
repository: 'docs-builder'
17+
features:
18+
public-reference: false
1419
#...
1520
```
1621

@@ -19,14 +24,19 @@ products:
1924
`products`
2025
: A YAML mapping where each key is an Elastic product.
2126
* `display`: A friendly name for the product.
22-
* `versioning`: The versioning system used by the project. The value for this field must match one of the versioning systems defined in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml)
27+
* `versioning`: The versioning system used by the project. The value for this field must match one of the versioning systems defined in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml). Optional for products that only participate in release notes.
2328
* `repository`: The repository name for the product. It's optional and primarily intended for handling edge cases where there is a mismatch between the repository name and the product identifier.
29+
* `features`: An optional mapping that controls which docs-builder subsystems the product participates in. When omitted, all features are enabled (backward compatible). When present, all features default to `true` and individual features can be opted out by setting them to `false`. The available features are:
30+
* `public-reference`: The product can be referenced in `applies_to` blocks, page frontmatter `products`, and gets `{{ product.<id> }}` substitutions. This is what "being a documentation product" means today.
31+
* `release-notes`: The product participates in the changelog and release notes system.
2432

25-
33+
:::{note}
34+
Products without a `features` mapping behave exactly as before -- they participate in all subsystems. The `features` mapping uses opt-out semantics: all features are enabled by default, and you only need to set a feature to `false` to disable it. For example, internal tools that need release notes but don't have public-facing documentation can set `public-reference: false`.
35+
:::
2636

2737
## Substitutions
2838

29-
Writing `{{ product.<product-id> }}` renders the friendly name of the product in the documentation. For example:
39+
Writing `{{ product.<product-id> }}` renders the friendly name of the product in the documentation. Substitutions are generated only for products with the `public-reference` feature (or no explicit `features` mapping). For example:
3040

3141
| Substitution | Result |
3242
|---------------------------------|---|

docs/contribute/changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ To use the `docs-builder changelog` commands in your development workflow:
1010
1. [Bundle changelogs](/contribute/bundle-changelogs.md) with the `docs-builder changelog bundle` command. For example, create a bundle for the pull requests that are included in a product release. When changelogs are no longer needed in the repo, [remove changelog files](/contribute/bundle-changelogs.md#changelog-remove) with `docs-builder changelog remove`.
1111
1. [Publish release notes](/contribute/publish-changelogs.md): Use the `{changelog}` directive in docs or `docs-builder changelog render` to produce release documentation.
1212

13-
For more information about running `docs-builder`, go to [Contribute locally](https://www.elastic.co/docs/contribute-docs/locally).
13+
For more information about running `docs-builder`, go to [Contribute locally](https://www.elastic.co/docs/contribute-docs/locally).

docs/contribute/configure-changelogs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Before you can use the `docs-builder changelog` commands in your development workflow, you must make some decisions and do some setup steps:
44

5-
1. Ensure that your products exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml).
5+
1. Ensure that your products exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml). Products that only need release notes (not public documentation) can be added with `features: { public-reference: false }`. For more information, refer to [Products](/configure/site/products.md).
66
1. Add labels to your GitHub pull requests that map to [changelog types](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/ChangelogEntryType.cs). At a minimum, create labels for the `feature`, `bug-fix`, and `breaking-change` types.
77
1. Optional: Choose areas or components that your changes affect and add labels to your GitHub pull requests (such as `:Analytics/Aggregations`).
88
1. Optional: Add labels to your GitHub pull requests to indicate that they are not notable and should not generate changelogs. For example, `non-issue` or `release_notes:skip`. Alternatively, you can assume that all PRs are *not* notable unless a specific label is present (for example, `@Public`).

docs/contribute/create-changelogs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The changelogs associated with the `docs-builder changelog` commands use the fol
1010
:::{important}
1111
Some of the fields in the schema accept only a specific set of values:
1212

13-
- Product values must exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml). Invalid products will cause the `docs-builder changelog add` command to fail.
13+
- Product values must exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml). All products in the catalog are valid for changelogs, including those that have `public-reference` disabled. Invalid products will cause the `docs-builder changelog add` command to fail.
1414
- Type, subtype, and lifecycle values must match the available values defined in [ChangelogEntryType.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/ChangelogEntryType.cs), [ChangelogEntrySubtype.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/ChangelogEntrySubtype.cs), and [Lifecycle.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/Lifecycle.cs) respectively. Invalid values will cause the `docs-builder changelog add` command to fail.
1515
:::
1616

docs/syntax/frontmatter.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The products frontmatter is a list of products that the page relates to.
4949
This is used for the "Products" filter in the Search UI.
5050

5151
The products frontmatter is a list of objects, each object has an `id` field.
52+
Only products with the `public-reference` feature enabled in [`products.yml`](https://github.com/elastic/docs-builder/blob/main/config/products.yml) are valid here. Products that have set `public-reference: false` cannot be used in frontmatter.
5253

5354
| Product ID | Product Name |
5455
|---------------------------------------------|-----------------------------------------------|

src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ public ConfigurationFile(DocumentationSetFile docSetFile, IDocumentationSetConte
174174
_substitutions[$"version.{alternativeName}.base"] = system.Base;
175175
}
176176

177-
// Add product substitutions
178-
foreach (var product in productsConfig.Products.Values)
177+
// Add product substitutions (only for products with public-reference feature)
178+
foreach (var product in productsConfig.PublicReferenceProducts.Values)
179179
{
180180
var alternativeProductId = product.Id.Replace('-', '_');
181181
_substitutions[$"product.{product.Id}"] = product.DisplayName;

src/Elastic.Documentation.Configuration/Products/Product.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ public record ProductsConfiguration
1212
{
1313
public required FrozenDictionary<string, Product> Products { get; init; }
1414

15+
/// <summary>
16+
/// Products with the <c>public-reference</c> feature enabled. These can appear in
17+
/// applies_to blocks, page frontmatter, and get display-name substitutions.
18+
/// When a product has no explicit <c>features</c> map, it is included here by default.
19+
/// </summary>
20+
public required FrozenDictionary<string, Product> PublicReferenceProducts { get; init; }
21+
1522
/// <summary>
1623
/// Product id to display name mappings for fast lookups.
1724
/// </summary>
@@ -54,12 +61,28 @@ public record ProductLink
5461
public string Id { get; set; } = string.Empty;
5562
}
5663

64+
/// <summary>Declares which docs-builder subsystems a product participates in.</summary>
65+
public record ProductFeatures
66+
{
67+
/// <summary>Product can be referenced in applies_to blocks, page frontmatter, and gets display-name substitutions.</summary>
68+
public bool PublicReference { get; init; }
69+
70+
/// <summary>Product participates in the changelog / release-notes system.</summary>
71+
public bool ReleaseNotes { get; init; }
72+
73+
/// <summary>All features enabled -- the implicit default when no <c>features</c> map is present in YAML.</summary>
74+
public static ProductFeatures All => new() { PublicReference = true, ReleaseNotes = true };
75+
76+
public static readonly FrozenSet<string> KnownKeys = FrozenSet.ToFrozenSet(["public-reference", "release-notes"], StringComparer.OrdinalIgnoreCase);
77+
}
78+
5779
[YamlSerializable]
5880
public record Product
5981
{
6082
public required string Id { get; init; }
6183
public required string DisplayName { get; init; }
6284
public VersioningSystem? VersioningSystem { get; init; }
6385
public string? Repository { get; init; }
86+
public ProductFeatures Features { get; init; } = ProductFeatures.All;
6487
}
6588

src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,70 @@ public static ProductsConfiguration CreateProducts(this ConfigurationFileProvide
1818

1919
var products = productsDto.Products.ToDictionary(
2020
kvp => kvp.Key,
21-
kvp => new Product
21+
kvp =>
2222
{
23-
Id = kvp.Key,
24-
DisplayName = kvp.Value.Display,
25-
VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)),
26-
Repository = kvp.Value.Repository ?? kvp.Key
23+
var features = ResolveFeatures(kvp.Key, kvp.Value.Features);
24+
var versioningSystem = ResolveVersioningSystem(versionsConfiguration, kvp.Value.Versioning ?? kvp.Key);
25+
26+
versioningSystem ??= !features.PublicReference
27+
? VersioningSystem.None
28+
: throw new InvalidOperationException(
29+
$"Product '{kvp.Key}' has invalid or missing versioning '{kvp.Value.Versioning ?? kvp.Key}' while 'public-reference' is enabled.");
30+
31+
return new Product
32+
{
33+
Id = kvp.Key,
34+
DisplayName = kvp.Value.Display,
35+
VersioningSystem = versioningSystem,
36+
Repository = kvp.Value.Repository ?? kvp.Key,
37+
Features = features
38+
};
2739
});
2840

41+
var publicReferenceProducts = products
42+
.Where(kvp => kvp.Value.Features.PublicReference)
43+
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
44+
2945
var productDisplayNames = productsDto.Products.ToDictionary(
3046
kvp => kvp.Key,
3147
kvp => kvp.Value.Display);
3248

3349
return new ProductsConfiguration
3450
{
3551
Products = products.ToFrozenDictionary(),
52+
PublicReferenceProducts = publicReferenceProducts.ToFrozenDictionary(),
3653
ProductDisplayNames = productDisplayNames.ToFrozenDictionary()
3754
};
3855
}
56+
57+
private static VersioningSystem? ResolveVersioningSystem(VersionsConfiguration versionsConfiguration, string id) =>
58+
VersioningSystemIdExtensions.TryParse(id, out var versioningSystemId, ignoreCase: true, allowMatchingMetadataAttribute: true)
59+
? versionsConfiguration.GetVersioningSystem(versioningSystemId)
60+
: null;
61+
62+
private static ProductFeatures ResolveFeatures(string productId, Dictionary<string, bool>? featuresDto)
63+
{
64+
if (featuresDto is null)
65+
return ProductFeatures.All;
66+
67+
var unknownKeys = featuresDto.Keys
68+
.Where(k => !ProductFeatures.KnownKeys.Contains(k))
69+
.ToList();
70+
71+
if (unknownKeys is { Count: > 0 })
72+
{
73+
var known = string.Join(", ", ProductFeatures.KnownKeys.Order());
74+
throw new InvalidOperationException(
75+
$"Product '{productId}' has unknown feature key(s): {string.Join(", ", unknownKeys)}. Known features: {known}."
76+
);
77+
}
78+
79+
return new ProductFeatures
80+
{
81+
PublicReference = featuresDto.GetValueOrDefault("public-reference", true),
82+
ReleaseNotes = featuresDto.GetValueOrDefault("release-notes", true)
83+
};
84+
}
3985
}
4086

4187
// Private DTOs for deserialization. These match the YAML structure directly.
@@ -52,5 +98,9 @@ internal sealed record ProductDto
5298

5399
[YamlMember(Alias = "versioning")]
54100
public string? Versioning { get; set; }
101+
55102
public string? Repository { get; set; }
103+
104+
[YamlMember(Alias = "features")]
105+
public Dictionary<string, bool>? Features { get; set; }
56106
}

0 commit comments

Comments
 (0)