Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
60fd961
Add features mapping to products.yml
cotti Apr 6, 2026
653e58e
Add docs-builder to products.yml for release notes
cotti Apr 6, 2026
f186f95
Add tests
cotti Apr 6, 2026
4d367e9
Introduce VersioningSystem.None for products outside the public docum…
cotti Apr 6, 2026
922d259
Set sentinel non-value for None
cotti Apr 6, 2026
3681ba0
Throw if there is no valid versioning for a product with public-refer…
cotti Apr 6, 2026
757a64c
Update current version to 9.3.3 (#3007)
alaudazzi Apr 6, 2026
e9fa4e5
Merge branch 'main' into feature/product-features
cotti Apr 7, 2026
b074eb1
Have features be enabled by default
cotti Apr 8, 2026
77ef4af
add js esql dsl (#3027)
margaretjgu Apr 7, 2026
80833a7
changelogs: Add --plan flag to changelog bundle (#3028)
cotti Apr 7, 2026
db1dbc1
Bump @elastic/eui (#3032)
dependabot[bot] Apr 8, 2026
366e6cc
Bump the tailwindcss group across 1 directory with 2 updates (#3033)
dependabot[bot] Apr 8, 2026
279895e
Bump katex from 0.16.27 to 0.16.40 in /src/Elastic.Documentation.Site…
dependabot[bot] Apr 8, 2026
ec160fd
Bump @opentelemetry/context-zone in /src/Elastic.Documentation.Site (…
dependabot[bot] Apr 8, 2026
0edc022
Bump Elastic.Mapping from 0.40.0 to 0.41.0 (#3038)
dependabot[bot] Apr 8, 2026
4ea6639
Bump AWSSDK.Core and AWSSDK.SQS (#3037)
dependabot[bot] Apr 8, 2026
c466af4
Bump Errata from 0.15.0 to 0.16.0 (#3039)
dependabot[bot] Apr 8, 2026
dc9f290
Bump Markdig from 1.1.1 to 1.1.2 (#3041)
dependabot[bot] Apr 8, 2026
fedaf30
Change changelog --extract-release-notes impact on title (#3015)
lcawl Apr 8, 2026
a1554d3
Add roadmap to assembler.yml (#3053)
lcawl Apr 8, 2026
44a0c8e
Fix FileSystem rights requested for writing changelogs (#3055)
cotti Apr 8, 2026
ca2a2e7
Fix syntax link on codex home page (#3056)
shainaraskas Apr 8, 2026
7011ae9
[Automation] Bump product version numbers (#3048)
elastic-observability-automation[bot] Apr 9, 2026
7f90ca4
Add --repo --owner to changelog init (#3042)
lcawl Apr 9, 2026
52e809d
Add changelog bundle description (#3058)
lcawl Apr 9, 2026
b3b98f0
[Automation] Bump product version numbers (#3067)
elastic-observability-automation[bot] Apr 10, 2026
df23f58
Skip hidden folders on codex builds (#3068)
cotti Apr 10, 2026
588a915
[Automation] Bump product version numbers (#3093)
elastic-observability-automation[bot] Apr 13, 2026
0479103
Skip redirect validation for docset-excluded paths (#3095)
Mpdreamz Apr 14, 2026
cb87a24
Search: Add content_last_updated for content-only change tracking (#3…
reakaleek Apr 14, 2026
0ff67f9
Search: Add completion multi-field to ai_questions mapping (#3097)
reakaleek Apr 14, 2026
aeabb03
Bump MartinCostello.Logging.XUnit.v3 from 0.7.0 to 0.7.1 (#3092)
dependabot[bot] Apr 14, 2026
55b59d3
Bump Elastic.Ingest.Elasticsearch from 0.40.0 to 0.41.0 (#3091)
dependabot[bot] Apr 14, 2026
311e640
Adjust release workflow dependencies (#2718)
reakaleek Apr 14, 2026
36cf07a
Search: Remove shard/replica settings for serverless compatibility (#…
reakaleek Apr 14, 2026
85edea6
Changes API: Use content_last_updated instead of last_updated (#3101)
reakaleek Apr 14, 2026
aa633c2
Search: Use staging index + alias swap for content date lookup sync (…
reakaleek Apr 14, 2026
ef775e4
Search: Include synonym ID in synonym rule values (#2999)
reakaleek Apr 14, 2026
8f23ab0
Sitemap: Read from semantic index for content_last_updated (#3104)
reakaleek Apr 14, 2026
9d8d37b
Search: Use content-hash-versioned enrich policy names (#3105)
reakaleek Apr 14, 2026
6bd75ac
[Automation] Bump product version numbers (#3100)
elastic-observability-automation[bot] Apr 14, 2026
0350f5e
[DOCS] Split changelog how-to into subpages (#3096)
lcawl Apr 14, 2026
f6556b6
Search: Add ai_autocomplete_questions field for autocomplete (#3107)
reakaleek Apr 15, 2026
b2be52a
Dependencies: Bump Elastic.Ingest.Elasticsearch to 0.41.1 (#3111)
reakaleek Apr 15, 2026
f745055
[Automation] Bump product version numbers (#3110)
elastic-observability-automation[bot] Apr 15, 2026
419242f
[Stack 9.4.0] Update shared_configuration.stack.next (#3071)
shainaraskas Apr 15, 2026
9870949
Ingest: Add post-indexing content date resolution (#3112)
reakaleek Apr 15, 2026
820c1f5
Dependencies: Bump Elastic.Ingest.Elasticsearch to 0.41.2 (#3113)
reakaleek Apr 15, 2026
079faf0
Add changelog bundle description (#3058)
lcawl Apr 9, 2026
97d4014
Search: Add content_last_updated for content-only change tracking (#3…
reakaleek Apr 14, 2026
7a45c71
[DOCS] Split changelog how-to into subpages (#3096)
lcawl Apr 14, 2026
94fe6e9
Ingest: Add post-indexing content date resolution (#3112)
reakaleek Apr 15, 2026
d63bc3e
Merge branch 'main' into feature/product-features
cotti Apr 15, 2026
ae8cdb3
Fix after merge conflicts
cotti Apr 15, 2026
89429a0
Adjust docs
cotti Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/changelog.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
filename: timestamp

# Products configuration (optional)
# If not specified, all products from products.yml are allowed
# If not specified, all products from products.yml are allowed (including those
# that only have the 'release-notes' feature enabled).
products:
# List of available product IDs (empty = all from products.yml)
# Accepts string or list: "elasticsearch, kibana" or [elasticsearch, kibana]
Expand Down
5 changes: 5 additions & 0 deletions config/products.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ products:
versioning: 'terraform-google-edot-cf'
curator:
display: 'Elasticsearch Curator'
docs-builder:
display: 'Elastic Docs Builder'
repository: 'docs-builder'
features:
release-notes: true
ecs:
display: 'Elastic Common Schema (ECS)'
ecs-logging:
Expand Down
16 changes: 13 additions & 3 deletions docs/configure/site/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ products:
display: 'Elastic Distribution of OpenTelemetry Collector'
versioning: 'stack'
repository: 'elastic-edot-collector'
docs-builder:
display: 'Elastic Docs Builder'
repository: 'docs-builder'
features:
release-notes: true
#...
```

Expand All @@ -19,14 +24,19 @@ products:
`products`
: A YAML mapping where each key is an Elastic product.
* `display`: A friendly name for the product.
* `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)
* `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.
* `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.
* `features`: An optional mapping that controls which docs-builder subsystems the product participates in. When omitted, all features are enabled (backward compatible). When present, only the listed features are active. The available features are:
* `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.
* `release-notes`: The product participates in the changelog and release notes system.


:::{note}
Products without a `features` mapping behave exactly as before -- they participate in all subsystems. The `features` mapping is only needed for products that should participate in a subset of features, such as internal tools that need release notes but don't have public-facing documentation.
:::

## Substitutions

Writing `{{ product.<product-id> }}` renders the friendly name of the product in the documentation. For example:
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:

| Substitution | Result |
|---------------------------------|---|
Expand Down
4 changes: 2 additions & 2 deletions docs/contribute/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ The changelogs use the following schema:
:::{important}
Some of the fields in the schema accept only a specific set of values:

- 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.
- 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 only have the `release-notes` feature enabled. Invalid products will cause the `docs-builder changelog add` command to fail.
- 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.
:::

To use the `docs-builder changelog` commands in your development workflow:

1. Ensure that your products exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml).
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: { release-notes: true }`.
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.
1. Optional: Choose areas or components that your changes affect and add labels to your GitHub pull requests (such as `:Analytics/Aggregations`).
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`).
Expand Down
1 change: 1 addition & 0 deletions docs/syntax/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The products frontmatter is a list of products that the page relates to.
This is used for the "Products" filter in the Search UI.

The products frontmatter is a list of objects, each object has an `id` field.
Only products with the `public-reference` feature (or no explicit `features` mapping) in [`products.yml`](https://github.com/elastic/docs-builder/blob/main/config/products.yml) are valid here. Products that only participate in release notes cannot be used in frontmatter.

| Product ID | Product Name |
|---------------------------------------------|-----------------------------------------------|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ public ConfigurationFile(DocumentationSetFile docSetFile, IDocumentationSetConte
_substitutions[$"version.{alternativeName}.base"] = system.Base;
}

// Add product substitutions
foreach (var product in productsConfig.Products.Values)
// Add product substitutions (only for products with public-reference feature)
foreach (var product in productsConfig.PublicReferenceProducts.Values)
{
var alternativeProductId = product.Id.Replace('-', '_');
_substitutions[$"product.{product.Id}"] = product.DisplayName;
Expand Down
23 changes: 23 additions & 0 deletions src/Elastic.Documentation.Configuration/Products/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ public record ProductsConfiguration
{
public required FrozenDictionary<string, Product> Products { get; init; }

/// <summary>
/// Products with the <c>public-reference</c> feature enabled. These can appear in
/// applies_to blocks, page frontmatter, and get display-name substitutions.
/// When a product has no explicit <c>features</c> map, it is included here by default.
/// </summary>
public required FrozenDictionary<string, Product> PublicReferenceProducts { get; init; }

/// <summary>
/// Product id to display name mappings for fast lookups.
/// </summary>
Expand Down Expand Up @@ -54,12 +61,28 @@ public record ProductLink
public string Id { get; set; } = string.Empty;
}

/// <summary>Declares which docs-builder subsystems a product participates in.</summary>
public record ProductFeatures
{
/// <summary>Product can be referenced in applies_to blocks, page frontmatter, and gets display-name substitutions.</summary>
public bool PublicReference { get; init; }

/// <summary>Product participates in the changelog / release-notes system.</summary>
public bool ReleaseNotes { get; init; }

/// <summary>All features enabled -- the implicit default when no <c>features</c> map is present in YAML.</summary>
public static ProductFeatures All => new() { PublicReference = true, ReleaseNotes = true };

public static readonly FrozenSet<string> KnownKeys = FrozenSet.ToFrozenSet(["public-reference", "release-notes"], StringComparer.OrdinalIgnoreCase);
}

[YamlSerializable]
public record Product
{
public required string Id { get; init; }
public required string DisplayName { get; init; }
public VersioningSystem? VersioningSystem { get; init; }
public string? Repository { get; init; }
public ProductFeatures Features { get; init; } = ProductFeatures.All;
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,70 @@ public static ProductsConfiguration CreateProducts(this ConfigurationFileProvide

var products = productsDto.Products.ToDictionary(
kvp => kvp.Key,
kvp => new Product
kvp =>
{
Id = kvp.Key,
DisplayName = kvp.Value.Display,
VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)),
Repository = kvp.Value.Repository ?? kvp.Key
var features = ResolveFeatures(kvp.Key, kvp.Value.Features);
var versioningSystem = ResolveVersioningSystem(versionsConfiguration, kvp.Value.Versioning ?? kvp.Key);

versioningSystem ??= !features.PublicReference
? VersioningSystem.None
: throw new InvalidOperationException(
$"Product '{kvp.Key}' has invalid or missing versioning '{kvp.Value.Versioning ?? kvp.Key}' while 'public-reference' is enabled.");

return new Product
{
Id = kvp.Key,
DisplayName = kvp.Value.Display,
VersioningSystem = versioningSystem,
Repository = kvp.Value.Repository ?? kvp.Key,
Features = features
};
});

var publicReferenceProducts = products
.Where(kvp => kvp.Value.Features.PublicReference)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

var productDisplayNames = productsDto.Products.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Display);

return new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = publicReferenceProducts.ToFrozenDictionary(),
ProductDisplayNames = productDisplayNames.ToFrozenDictionary()
};
}

private static VersioningSystem? ResolveVersioningSystem(VersionsConfiguration versionsConfiguration, string id) =>
VersioningSystemIdExtensions.TryParse(id, out var versioningSystemId, ignoreCase: true, allowMatchingMetadataAttribute: true)
? versionsConfiguration.GetVersioningSystem(versioningSystemId)
: null;

private static ProductFeatures ResolveFeatures(string productId, Dictionary<string, bool>? featuresDto)
{
if (featuresDto is null)
return ProductFeatures.All;

var unknownKeys = featuresDto.Keys
.Where(k => !ProductFeatures.KnownKeys.Contains(k))
.ToList();

if (unknownKeys is { Count: > 0 })
{
var known = string.Join(", ", ProductFeatures.KnownKeys.Order());
throw new InvalidOperationException(
$"Product '{productId}' has unknown feature key(s): {string.Join(", ", unknownKeys)}. Known features: {known}."
);
}

return new ProductFeatures
{
PublicReference = featuresDto.GetValueOrDefault("public-reference"),
ReleaseNotes = featuresDto.GetValueOrDefault("release-notes")
Comment thread
cotti marked this conversation as resolved.
Outdated
};
}
}

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

[YamlMember(Alias = "versioning")]
public string? Versioning { get; set; }

public string? Repository { get; set; }

[YamlMember(Alias = "features")]
public Dictionary<string, bool>? Features { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ public enum VersioningSystemId
ElasticsearchClientRuby,
[Display(Name = "elasticsearch-client-rust")]
ElasticsearchClientRust,

[Display(Name = "none")]
None = -1,
}

[YamlSerializable]
Expand All @@ -152,6 +155,17 @@ public record VersioningSystem
/// </summary>
public const int VersionlessSentinel = 99999;

/// <summary>
/// A versionless sentinel for products that have no versioning system at all
/// (e.g. release-notes-only products without public documentation).
/// </summary>
public static VersioningSystem None { get; } = new()
{
Id = VersioningSystemId.None,
Base = new SemVersion(VersionlessSentinel, 0, 0),
Current = new SemVersion(VersionlessSentinel, 0, 0)
};

public required VersioningSystemId Id { get; init; }

[YamlMember(Alias = "base")]
Expand Down
4 changes: 2 additions & 2 deletions src/Elastic.Markdown/Myst/FrontMatter/Products.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria
if (string.IsNullOrWhiteSpace(productId))
throw new InvalidProductException("Product 'id' field is required. Example format:\nproducts:\n - id: apm", products);

if (products.Products.TryGetValue(productId.Replace('_', '-'), out var product))
if (products.PublicReferenceProducts.TryGetValue(productId.Replace('_', '-'), out var product))
return product;

throw new InvalidProductException(productId, products);
Expand All @@ -51,5 +51,5 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria
public class InvalidProductException(string invalidValue, ProductsConfiguration products)
: Exception(
$"Invalid products frontmatter value: \"{invalidValue}\"." +
(!string.IsNullOrWhiteSpace(invalidValue) ? " " + new Suggestion(products.Products.Select(p => p.Value.Id).ToHashSet(), invalidValue).GetSuggestionQuestion() : "") +
(!string.IsNullOrWhiteSpace(invalidValue) ? " " + new Suggestion(products.PublicReferenceProducts.Select(p => p.Value.Id).ToHashSet(), invalidValue).GetSuggestionQuestion() : "") +
"\nYou can find the full list at https://docs-v3-preview.elastic.dev/elastic/docs-builder/tree/main/syntax/frontmatter#products.");
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/YamlSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static T Deserialize<T>(string yaml, ProductsConfiguration products)
.WithEnumNamingConvention(HyphenatedNamingConvention.Instance)
.WithTypeConverter(new SemVersionConverter())
.WithTypeConverter(new ProductConverter(products))
.WithTypeConverter(new ApplicableToYamlConverter(products.Products.Keys))
.WithTypeConverter(new ApplicableToYamlConverter(products.PublicReferenceProducts.Keys))
.Build();

var frontMatter = deserializer.Deserialize<T>(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static IConfigurationContext CreateConfigurationContext(
productsConfiguration = new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ private static ProductsConfiguration CreateProductsConfiguration()
return new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = productDisplayNames.ToFrozenDictionary()
};
}
Expand Down
1 change: 1 addition & 0 deletions tests/Elastic.ApiExplorer.Tests/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public static IConfigurationContext CreateConfigurationContext(IFileSystem fileS
productsConfiguration = new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ protected ChangelogTestBase(ITestOutputHelper output)
var productsConfiguration = new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};

Expand Down
1 change: 1 addition & 0 deletions tests/Elastic.Documentation.Build.Tests/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static IConfigurationContext CreateConfigurationContext(
productsConfiguration = new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ private static ConfigurationFile CreateConfiguration(DocumentationSetFile docSet
var productsConfig = new ProductsConfiguration
{
Products = new Dictionary<string, Product>().ToFrozenDictionary(),
PublicReferenceProducts = new Dictionary<string, Product>().ToFrozenDictionary(),
ProductDisplayNames = new Dictionary<string, string>().ToFrozenDictionary()
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private static ProductsConfiguration CreateProductsConfiguration(VersionsConfigu
return new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};
}
Expand Down Expand Up @@ -396,6 +397,7 @@ public void InferForMarkdownWithVersionlessProductReturnsNullVersion()
var productsConfig = new ProductsConfiguration
{
Products = products.ToFrozenDictionary(),
PublicReferenceProducts = products.ToFrozenDictionary(),
ProductDisplayNames = products.ToDictionary(p => p.Key, p => p.Value.DisplayName).ToFrozenDictionary()
};
var legacyUrlMappings = new LegacyUrlMappingConfiguration { Mappings = [] };
Expand Down
Loading
Loading