From 9abe7116ecb8b5fe21f539bfb0f683db946cd027 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Sun, 7 Dec 2025 18:19:36 -0300 Subject: [PATCH 01/17] Introduce VersionSpec, a model meant for advanced version handling for applicabilities --- .../AppliesTo/ApplicableToYamlConverter.cs | 92 +++++++ .../Serialization/SourceGenerationContext.cs | 1 + src/Elastic.Documentation/VersionSpec.cs | 241 ++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 src/Elastic.Documentation/VersionSpec.cs diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index 1017207e1..2b6269034 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -256,10 +256,102 @@ private static bool TryGetApplicabilityOverTime(Dictionary dict if (target is null || (target is string s && string.IsNullOrWhiteSpace(s))) availability = AppliesCollection.GenerallyAvailable; else if (target is string stackString) + { availability = AppliesCollection.TryParse(stackString, diagnostics, out var a) ? a : null; + + if (availability is not null) + ValidateApplicabilityCollection(key, availability, diagnostics); + } return availability is not null; } + private static void ValidateApplicabilityCollection(string key, AppliesCollection collection, List<(Severity, string)> diagnostics) + { + var items = collection.ToList(); + + // Rule: Only one version declaration per lifecycle + var lifecycleGroups = items.GroupBy(a => a.Lifecycle).ToList(); + foreach (var group in lifecycleGroups) + { + var lifecycleVersionedItems = group.Where(a => a.Version is not null && + a.Version != AllVersionsSpec.Instance).ToList(); + if (lifecycleVersionedItems.Count > 1) + { + diagnostics.Add((Severity.Warning, + $"Key '{key}': Multiple version declarations for {group.Key} lifecycle. Only one version per lifecycle is allowed.")); + } + } + + // Rule: Only one item per key can use greater-than syntax + var greaterThanItems = items.Where(a => + a.Version is VersionSpec spec && spec.Kind == VersionSpecKind.GreaterThanOrEqual && + a.Version != AllVersionsSpec.Instance).ToList(); + + if (greaterThanItems.Count > 1) + { + diagnostics.Add((Severity.Warning, + $"Key '{key}': Multiple items use greater-than-or-equal syntax. Only one item per key can use this syntax.")); + } + + // Rule: In a range, the first version must be less than or equal the last version + foreach (var item in items) + { + if (item.Version is { Kind: VersionSpecKind.Range } spec) + { + if (spec.Min.CompareTo(spec.Max!) > 0) + { + diagnostics.Add((Severity.Warning, + $"Key '{key}', {item.Lifecycle}: Range has first version ({spec.Min.Major}.{spec.Min.Minor}) greater than last version ({spec.Max!.Major}.{spec.Max.Minor}).")); + } + } + } + + // Rule: No overlapping version ranges for the same key + var versionedItems = items.Where(a => a.Version is not null && + a.Version != AllVersionsSpec.Instance).ToList(); + + for (var i = 0; i < versionedItems.Count; i++) + { + for (var j = i + 1; j < versionedItems.Count; j++) + { + if (CheckVersionOverlap(versionedItems[i].Version!, versionedItems[j].Version!, out var overlapMsg)) + { + diagnostics.Add((Severity.Warning, + $"Key '{key}': Overlapping versions between {versionedItems[i].Lifecycle} and {versionedItems[j].Lifecycle}. {overlapMsg}")); + } + } + } + } + + private static bool CheckVersionOverlap(VersionSpec v1, VersionSpec v2, out string message) + { + message = string.Empty; + + // Get the effective ranges for each version spec + // For GreaterThanOrEqual: [min, infinity) + // For Range: [min, max] + // For Exact: [exact, exact] + + var (v1Min, v1Max) = GetEffectiveRange(v1); + var (v2Min, v2Max) = GetEffectiveRange(v2); + + var overlaps = v1Min.CompareTo(v2Max ?? new SemVersion(9999, 9999, 9999)) <= 0 && + v2Min.CompareTo(v1Max ?? new SemVersion(9999, 9999, 9999)) <= 0; + + if (overlaps) + message = $"Version ranges overlap."; + + return overlaps; + } + + private static (SemVersion min, SemVersion? max) GetEffectiveRange(VersionSpec spec) => spec.Kind switch + { + VersionSpecKind.Exact => (spec.Min, spec.Min), + VersionSpecKind.Range => (spec.Min, spec.Max), + VersionSpecKind.GreaterThanOrEqual => (spec.Min, null), + _ => throw new ArgumentOutOfRangeException(nameof(spec), spec.Kind, "Unknown VersionSpecKind") + }; + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => serializer.Invoke(value, type); } diff --git a/src/Elastic.Documentation/Serialization/SourceGenerationContext.cs b/src/Elastic.Documentation/Serialization/SourceGenerationContext.cs index 7a97730a3..cde2ac16e 100644 --- a/src/Elastic.Documentation/Serialization/SourceGenerationContext.cs +++ b/src/Elastic.Documentation/Serialization/SourceGenerationContext.cs @@ -28,5 +28,6 @@ namespace Elastic.Documentation.Serialization; [JsonSerializable(typeof(Applicability))] [JsonSerializable(typeof(ProductLifecycle))] [JsonSerializable(typeof(SemVersion))] +[JsonSerializable(typeof(VersionSpec))] [JsonSerializable(typeof(string[]))] public sealed partial class SourceGenerationContext : JsonSerializerContext; diff --git a/src/Elastic.Documentation/VersionSpec.cs b/src/Elastic.Documentation/VersionSpec.cs new file mode 100644 index 000000000..2cfb66774 --- /dev/null +++ b/src/Elastic.Documentation/VersionSpec.cs @@ -0,0 +1,241 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Diagnostics.CodeAnalysis; + +namespace Elastic.Documentation; + +public sealed class AllVersionsSpec : VersionSpec +{ + private static readonly SemVersion AllVersionsSemVersion = new(9999, 9999, 9999); + + private AllVersionsSpec() : base(AllVersionsSemVersion, null, VersionSpecKind.GreaterThanOrEqual) + { + } + + public static AllVersionsSpec Instance { get; } = new(); + + public override string ToString() => "all"; +} + +public enum VersionSpecKind +{ + GreaterThanOrEqual, // x.x, x.x+, x.x.x, x.x.x+ + Range, // x.x-y.y, x.x.x-y.y.y + Exact // =x.x, =x.x.x +} + +/// +/// Represents a version specification that can be a single version with greater-than-or-equal semantics, +/// a range of versions, or an exact version match. +/// +public class VersionSpec : IComparable, IEquatable +{ + /// + /// The minimum version (or the exact version for Exact kind). + /// + public SemVersion Min { get; } + + /// + /// The maximum version for ranges. Null for GreaterThanOrEqual and Exact kinds. + /// + public SemVersion? Max { get; } + + /// + /// The kind of version specification. + /// + public VersionSpecKind Kind { get; } + + // Internal constructor to prevent direct instantiation outside of TryParse + // except for AllVersionsSpec which needs to inherit from this class + protected VersionSpec(SemVersion min, SemVersion? max, VersionSpecKind kind) + { + Min = min; + Max = max; + Kind = kind; + } + + /// + /// Tries to parse a version specification string. + /// Supports: x.x, x.x+, x.x.x, x.x.x+ (gte), x.x-y.y (range), =x.x (exact) + /// + public static bool TryParse(string? input, [NotNullWhen(true)] out VersionSpec? spec) + { + spec = null; + + if (string.IsNullOrWhiteSpace(input)) + return false; + + var trimmed = input.Trim(); + + // Check for exact syntax: =x.x or =x.x.x + if (trimmed.StartsWith('=')) + { + var versionPart = trimmed[1..]; + if (!TryParseVersion(versionPart, out var version)) + return false; + + spec = new(version, null, VersionSpecKind.Exact); + return true; + } + + // Check for range syntax: x.x-y.y or x.x.x-y.y.y + var dashIndex = FindRangeSeparator(trimmed); + if (dashIndex > 0) + { + var minPart = trimmed[..dashIndex]; + var maxPart = trimmed[(dashIndex + 1)..]; + + if (!TryParseVersion(minPart, out var minVersion) || + !TryParseVersion(maxPart, out var maxVersion)) + return false; + + spec = new(minVersion, maxVersion, VersionSpecKind.Range); + return true; + } + + // Otherwise, it's greater-than-or-equal syntax + // Strip trailing + if present + var versionString = trimmed.EndsWith('+') ? trimmed[..^1] : trimmed; + + if (!TryParseVersion(versionString, out var gteVersion)) + return false; + + spec = new(gteVersion, null, VersionSpecKind.GreaterThanOrEqual); + return true; + } + + /// + /// Finds the position of the dash separator in a range specification. + /// Returns -1 if no valid range separator is found. + /// + private static int FindRangeSeparator(string input) + { + // Look for a dash that's not part of a prerelease version + // We need to distinguish between "9.0-9.1" (range) and "9.0-alpha" (prerelease) + // Strategy: Find dashes and check if what follows looks like a version number + + for (var i = 0; i < input.Length; i++) + { + if (input[i] == '-') + { + // Check if there's content before and after the dash + if (i == 0 || i == input.Length - 1) + continue; + + // Check if the character after dash is a digit (indicating a version) + if (i + 1 < input.Length && char.IsDigit(input[i + 1])) + { + // Also verify that what comes before looks like a version + var beforeDash = input[..i]; + if (TryParseVersion(beforeDash, out _)) + return i; + } + } + } + + return -1; + } + + /// + /// Tries to parse a version string, normalizing minor versions to include patch 0. + /// + private static bool TryParseVersion(string input, [NotNullWhen(true)] out SemVersion? version) + { + version = null; + + if (string.IsNullOrWhiteSpace(input)) + return false; + + var trimmed = input.Trim(); + + // Try to parse as-is first + if (SemVersion.TryParse(trimmed, out version)) + return true; + + // If that fails, try appending .0 to support minor version format (e.g., "9.2" -> "9.2.0") + if (SemVersion.TryParse(trimmed + ".0", out version)) + return true; + + return false; + } + + /// + /// Returns the canonical string representation of this version spec. + /// Format: "9.2+" for GreaterThanOrEqual, "9.0-9.1" for Range, "=9.2" for Exact + /// + public override string ToString() => Kind switch + { + VersionSpecKind.Exact => $"={Min.Major}.{Min.Minor}", + VersionSpecKind.Range => $"{Min.Major}.{Min.Minor}-{Max!.Major}.{Max.Minor}", + VersionSpecKind.GreaterThanOrEqual => $"{Min.Major}.{Min.Minor}+", + _ => throw new ArgumentOutOfRangeException(nameof(Kind), Kind, null) + }; + + /// + /// Compares this VersionSpec to another for sorting. + /// Uses Max for ranges, otherwise uses Min. + /// + public int CompareTo(VersionSpec? other) + { + if (other is null) + return 1; + + // For sorting, we want to compare the "highest" version in each spec + var thisCompareVersion = Kind == VersionSpecKind.Range && Max is not null ? Max : Min; + var otherCompareVersion = other.Kind == VersionSpecKind.Range && other.Max is not null ? other.Max : other.Min; + + return thisCompareVersion.CompareTo(otherCompareVersion); + } + + /// + /// Checks if this VersionSpec is equal to another. + /// + public bool Equals(VersionSpec? other) + { + if (other is null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + return Kind == other.Kind && Min.Equals(other.Min) && + (Max?.Equals(other.Max) ?? (other.Max is null)); + } + + public override bool Equals(object? obj) => obj is VersionSpec other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Kind, Min, Max); + + public static bool operator ==(VersionSpec? left, VersionSpec? right) + { + if (left is null) + return right is null; + return left.Equals(right); + } + + public static bool operator !=(VersionSpec? left, VersionSpec? right) => !(left == right); + + public static bool operator <(VersionSpec? left, VersionSpec? right) => + left is null ? right is not null : left.CompareTo(right) < 0; + + public static bool operator <=(VersionSpec? left, VersionSpec? right) => + left is null || left.CompareTo(right) <= 0; + + public static bool operator >(VersionSpec? left, VersionSpec? right) => + left is not null && left.CompareTo(right) > 0; + + public static bool operator >=(VersionSpec? left, VersionSpec? right) => + left is null ? right is null : left.CompareTo(right) >= 0; + + /// + /// Explicit conversion from string to VersionSpec + /// + public static explicit operator VersionSpec(string s) + { + if (TryParse(s, out var spec)) + return spec!; + throw new ArgumentException($"'{s}' is not a valid version specification string."); + } +} From 73e8dd79f96915e550c41a2e2a1274754f437b61 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 03:43:55 -0300 Subject: [PATCH 02/17] Apply usage of VersionSpec in Applicability --- docs/_snippets/applies_to-version.md | 14 +- docs/syntax/applies.md | 91 ++++++++- .../Elasticsearch/OpenApiDocumentExporter.cs | 6 +- .../AppliesTo/Applicability.cs | 20 +- .../AppliesTo/ApplicabilitySelector.cs | 10 +- .../AppliesTo/ApplicableTo.cs | 4 +- .../AppliesTo/ApplicableToJsonConverter.cs | 4 +- .../Myst/Components/ApplicabilityRenderer.cs | 177 +++++++++++++----- .../Components/ApplicableToComponent.cshtml | 2 +- ...ApplicableToJsonConverterRoundTripTests.cs | 92 ++++----- ...icableToJsonConverterSerializationTests.cs | 54 +++--- .../ProductApplicabilityToStringTests.cs | 2 +- .../Directives/ApplicabilitySwitchTests.cs | 11 +- ...DocumentationDocumentSerializationTests.cs | 40 ++-- .../Applicability/ApplicableToComponent.fs | 8 +- tests/authoring/Inline/AppliesToRole.fs | 4 +- 16 files changed, 357 insertions(+), 182 deletions(-) diff --git a/docs/_snippets/applies_to-version.md b/docs/_snippets/applies_to-version.md index f98f758d0..e5e70f63b 100644 --- a/docs/_snippets/applies_to-version.md +++ b/docs/_snippets/applies_to-version.md @@ -1,10 +1,16 @@ `applies_to` accepts the following version formats: -* `Major.Minor` -* `Major.Minor.Patch` +* **Greater than or equal to**: `x.x+`, `x.x`, `x.x.x+`, `x.x.x` (default behavior when no operator specified) +* **Range (inclusive)**: `x.x-y.y`, `x.x.x-y.y.y`, `x.x-y.y.y`, `x.x.x-y.y` +* **Exact version**: `=x.x`, `=x.x.x` -Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor.Patch` format. +**Version Display:** + +- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of the format used in source files. +- Each version represents the **latest patch** of that minor version (e.g., `9.1` means 9.1.0, 9.1.1, 9.1.6, etc.). +- The `+` symbol indicates "this version and later" (e.g., `9.1+` means 9.1.0 and all subsequent releases). +- Ranges show both versions (e.g., `9.0-9.2`) when both are released, or convert to `+` format if the end version is unreleased. :::{note} -**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 8.18.6, ga 9.1.2, ga 8.19.2, ga 9.0.6` will be displayed as `stack: ga 9.1.2, ga 9.0.6, ga 8.19.2, ga 8.18.6`. Items without versions (like `ga` without a version or `all`) are sorted last. +**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 9.1, beta 9.0, preview 8.18` will be displayed with the highest priority lifecycle and version first. Items without versions are sorted last. ::: \ No newline at end of file diff --git a/docs/syntax/applies.md b/docs/syntax/applies.md index c6621e483..59c33d720 100644 --- a/docs/syntax/applies.md +++ b/docs/syntax/applies.md @@ -29,6 +29,41 @@ Where: - The lifecycle is mandatory. - The version is optional. +### Version Syntax + +Versions can be specified using several formats to indicate different applicability scenarios: + +| Description | Syntax | Example | Badge Display | +|:------------|:-------|:--------|:--------------| +| **Greater than or equal to** (default) | `x.x+` `x.x` `x.x.x+` `x.x.x` | `ga 9.1` or `ga 9.1+` | `9.1+` | +| **Range** (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | `preview 9.0-9.2` | `9.0-9.2` or `9.0+`* | +| **Exact version** | `=x.x` `=x.x.x` | `beta =9.1` | `9.1` | + +\* Range display depends on release status of the second version. + +**Important notes:** + +- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of whether you specify patch versions in the source. +- Each version statement corresponds to the **latest patch** of the specified minor version (e.g., `9.1` represents 9.1.0, 9.1.1, 9.1.6, etc.). +- When critical patch-level differences exist, use plain text descriptions alongside the badge rather than specifying patch versions. + +### Version Validation Rules + +The build process enforces the following validation rules: + +- **One version per lifecycle**: Each lifecycle (GA, Preview, Beta, etc.) can only have one version declaration. + - ✅ `stack: ga 9.2+, beta 9.0-9.1` + - ❌ `stack: ga 9.2, ga 9.3` +- **One "greater than" per key**: Only one lifecycle per product key can use the `+` (greater than or equal to) syntax. + - ✅ `stack: ga 9.2+, beta 9.0-9.1` + - ❌ `stack: ga 9.2+, beta 9.0+` +- **Valid range order**: In ranges, the first version must be less than or equal to the second version. + - ✅ `stack: preview 9.0-9.2` + - ❌ `stack: preview 9.2-9.0` +- **No version overlaps**: Versions for the same key cannot overlap (ranges are inclusive). + - ✅ `stack: ga 9.2+, beta 9.0-9.1` + - ❌ `stack: ga 9.2+, beta 9.0-9.2` + ### Page level Page level annotations are added in the YAML frontmatter, starting with the `applies_to` key and following the [key-value reference](#key-value-reference). For example: @@ -134,6 +169,22 @@ Use the following key-value reference to find the appropriate key and value for ## Examples +### Version Syntax Examples + +The following table demonstrates the various version syntax options and their rendered output: + +| Source Syntax | Description | Badge Display | Notes | +|:-------------|:------------|:--------------|:------| +| `stack: ga 9.1` | Greater than or equal to 9.1 | `Stack│9.1+` | Default behavior, equivalent to `9.1+` | +| `stack: ga 9.1+` | Explicit greater than or equal to | `Stack│9.1+` | Explicit `+` syntax | +| `stack: preview 9.0-9.2` | Range from 9.0 to 9.2 (inclusive) | `Stack│Preview 9.0-9.2` | Shows range if 9.2.0 is released | +| `stack: preview 9.0-9.3` | Range where end is unreleased | `Stack│Preview 9.0+` | Shows `+` if 9.3.0 is not released | +| `stack: beta =9.1` | Exact version 9.1 only | `Stack│Beta 9.1` | No `+` symbol for exact versions | +| `stack: ga 9.2+, beta 9.0-9.1` | Multiple lifecycles | `Stack│9.2+` | Only highest priority lifecycle shown | +| `stack: ga 9.3, beta 9.1+` | Unreleased GA with Preview | `Stack│Beta 9.1+` | Shows Beta when GA unreleased with 2+ lifecycles | +| `serverless: ga` | No version (base 99999) | `Serverless` | No version badge for unversioned products | +| `deployment:`
` ece: ga 9.0+` | Nested deployment syntax | `ECE│9.0+` | Deployment products shown separately | + ### Versioning examples Versioned products require a `version` tag to be used with the `lifecycle` tag: @@ -240,22 +291,46 @@ applies_to: ## Look and feel +### Version Syntax Demonstrations + +:::::{dropdown} New version syntax examples + +The following examples demonstrate the new version syntax capabilities: + +**Greater than or equal to:** +- {applies_to}`stack: ga 9.1` (implicit `+`) +- {applies_to}`stack: ga 9.1+` (explicit `+`) +- {applies_to}`stack: preview 9.0+` + +**Ranges:** +- {applies_to}`stack: preview 9.0-9.2` (range display when both released) +- {applies_to}`stack: beta 9.1-9.3` (converts to `+` if end unreleased) + +**Exact versions:** +- {applies_to}`stack: beta =9.1` (no `+` symbol) +- {applies_to}`stack: deprecated =9.0` + +**Multiple lifecycles:** +- {applies_to}`stack: ga 9.2+, beta 9.0-9.1` (shows highest priority) + +::::: + ### Block :::::{dropdown} Block examples ```{applies_to} -stack: preview 9.1 +stack: preview 9.1+ serverless: ga -apm_agent_dotnet: ga 1.0.0 -apm_agent_java: beta 1.0.0 -edot_dotnet: preview 1.0.0 +apm_agent_dotnet: ga 1.0+ +apm_agent_java: beta 1.0+ +edot_dotnet: preview 1.0+ edot_python: -edot_node: ga 1.0.0 -elasticsearch: preview 9.0.0 -security: removed 9.0.0 -observability: deprecated 9.0.0 +edot_node: ga 1.0+ +elasticsearch: preview 9.0+ +security: removed 9.0 +observability: deprecated 9.0+ ``` ::::: diff --git a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs index b2c82a1ec..d94f5ca7c 100644 --- a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs +++ b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs @@ -209,7 +209,7 @@ private bool ShouldIncludeOperation(OpenApiOperation operation, string product) return true; // Could not parse version, safe to include // Get current version for the product - var versioningSystemId = product == "elasticsearch" + var versioningSystemId = product.Equals("elasticsearch", StringComparison.OrdinalIgnoreCase) ? VersioningSystemId.Stack : VersioningSystemId.Stack; // Both use Stack for now @@ -294,14 +294,14 @@ private static ProductLifecycle ParseLifecycle(string stateValue) /// /// Parses the version from "Added in X.Y.Z" pattern in the x-state string. /// - private static SemVersion? ParseVersion(string stateValue) + private static VersionSpec? ParseVersion(string stateValue) { var match = AddedInVersionRegex().Match(stateValue); if (!match.Success) return null; var versionString = match.Groups[1].Value; - return SemVersion.TryParse(versionString, out var version) ? version : null; + return VersionSpec.TryParse(versionString, out var version) ? version : null; } /// diff --git a/src/Elastic.Documentation/AppliesTo/Applicability.cs b/src/Elastic.Documentation/AppliesTo/Applicability.cs index b91fdb991..2d5abbff9 100644 --- a/src/Elastic.Documentation/AppliesTo/Applicability.cs +++ b/src/Elastic.Documentation/AppliesTo/Applicability.cs @@ -38,7 +38,7 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics return false; // Sort by version in descending order (the highest version first) - // Items without versions (AllVersions.Instance) are sorted last + // Items without versions (AllVersionsSpec.Instance) are sorted last var sortedApplications = applications.OrderDescending().ToArray(); availability = new AppliesCollection(sortedApplications); return true; @@ -98,12 +98,12 @@ public override string ToString() public record Applicability : IComparable, IComparable { public ProductLifecycle Lifecycle { get; init; } - public SemVersion? Version { get; init; } + public VersionSpec? Version { get; init; } public static Applicability GenerallyAvailable { get; } = new() { Lifecycle = ProductLifecycle.GenerallyAvailable, - Version = AllVersions.Instance + Version = AllVersionsSpec.Instance }; @@ -126,8 +126,8 @@ public string GetLifeCycleName() => /// public int CompareTo(Applicability? other) { - var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersions.Instance); - var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersions.Instance); + var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersionsSpec.Instance); + var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersionsSpec.Instance); if (xIsNonVersioned && yIsNonVersioned) return 0; @@ -158,7 +158,7 @@ public override string ToString() _ => throw new ArgumentOutOfRangeException() }; _ = sb.Append(lifecycle); - if (Version is not null && Version != AllVersions.Instance) + if (Version is not null && Version != AllVersionsSpec.Instance) _ = sb.Append(' ').Append(Version); return sb.ToString(); } @@ -224,10 +224,10 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics ? null : tokens[1] switch { - null => AllVersions.Instance, - "all" => AllVersions.Instance, - "" => AllVersions.Instance, - var t => SemVersionConverter.TryParse(t, out var v) ? v : null + null => AllVersionsSpec.Instance, + "all" => AllVersionsSpec.Instance, + "" => AllVersionsSpec.Instance, + var t => VersionSpec.TryParse(t, out var v) ? v : null }; availability = new Applicability { Version = version, Lifecycle = lifecycle }; return true; diff --git a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs index cb881fbf6..d090f2b02 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs @@ -30,25 +30,27 @@ public static Applicability GetPrimaryApplicability(IEnumerable a }; var availableApplicabilities = applicabilityList - .Where(a => a.Version is null || a.Version is AllVersions || a.Version <= currentVersion) + .Where(a => a.Version is null || a.Version is AllVersionsSpec || + (a.Version is VersionSpec vs && vs.Min <= currentVersion)) .ToList(); if (availableApplicabilities.Count != 0) { return availableApplicabilities - .OrderByDescending(a => a.Version ?? new SemVersion(0, 0, 0)) + .OrderByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) .ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999)) .First(); } var futureApplicabilities = applicabilityList - .Where(a => a.Version is not null && a.Version is not AllVersions && a.Version > currentVersion) + .Where(a => a.Version is not null && a.Version is not AllVersionsSpec && + a.Version is VersionSpec vs && vs.Min > currentVersion) .ToList(); if (futureApplicabilities.Count != 0) { return futureApplicabilities - .OrderBy(a => a.Version!.CompareTo(currentVersion)) + .OrderBy(a => a.Version!.Min.CompareTo(currentVersion)) .ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999)) .First(); } diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs index 5889a9964..94ff54d4c 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs @@ -64,9 +64,11 @@ public record ApplicableTo Product = AppliesCollection.GenerallyAvailable }; + private static readonly VersionSpec DefaultVersion = VersionSpec.TryParse("9.0", out var v) ? v! : AllVersionsSpec.Instance; + public static ApplicableTo Default { get; } = new() { - Stack = new AppliesCollection([new Applicability { Version = new SemVersion(9, 0, 0), Lifecycle = ProductLifecycle.GenerallyAvailable }]), + Stack = new AppliesCollection([new Applicability { Version = DefaultVersion, Lifecycle = ProductLifecycle.GenerallyAvailable }]), Serverless = ServerlessProjectApplicability.All }; diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs index d3779e525..c36e350dc 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs @@ -44,7 +44,7 @@ public class ApplicableToJsonConverter : JsonConverter string? type = null; string? subType = null; var lifecycle = ProductLifecycle.GenerallyAvailable; - SemVersion? version = null; + VersionSpec? version = null; while (reader.Read()) { @@ -72,7 +72,7 @@ public class ApplicableToJsonConverter : JsonConverter break; case "version": var versionStr = reader.GetString(); - if (versionStr != null && SemVersionConverter.TryParse(versionStr, out var v)) + if (versionStr != null && VersionSpecConverter.TryParse(versionStr, out var v)) version = v; break; } diff --git a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs index 8ffe34306..7984fe4a8 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs @@ -16,6 +16,7 @@ public record ApplicabilityRenderData( string Version, string TooltipText, string LifecycleClass, + string LifecycleName, bool ShowLifecycleName, bool ShowVersion, bool HasMultipleLifecycles = false @@ -29,19 +30,36 @@ public ApplicabilityRenderData RenderApplicability( { var lifecycleClass = applicability.GetLifeCycleName().ToLowerInvariant().Replace(" ", "-"); var lifecycleFull = GetLifecycleFullText(applicability.Lifecycle); - var realVersion = TryGetRealVersion(applicability, out var v) ? v : null; - var tooltipText = BuildTooltipText(applicability, applicabilityDefinition, versioningSystem, realVersion, lifecycleFull); - var badgeLifecycleText = BuildBadgeLifecycleText(applicability, versioningSystem, realVersion, allApplications); + var tooltipText = BuildTooltipText(applicability, applicabilityDefinition, versioningSystem, lifecycleFull); + var badgeLifecycleText = BuildBadgeLifecycleText(applicability, versioningSystem, allApplications); var showLifecycle = applicability.Lifecycle != ProductLifecycle.GenerallyAvailable && string.IsNullOrEmpty(badgeLifecycleText); - var showVersion = applicability.Version is not null and not AllVersions && versioningSystem.Current >= applicability.Version; - var version = applicability.Version?.ToString() ?? ""; + + // Determine if we should show version based on VersionSpec + var showVersion = false; + var versionDisplay = string.Empty; + + if (applicability.Version is not null && applicability.Version != AllVersionsSpec.Instance) + { + versionDisplay = GetBadgeVersionText(applicability.Version, versioningSystem); + showVersion = !string.IsNullOrEmpty(versionDisplay); + + // Special handling for Removed lifecycle - don't show + suffix + if (applicability.Lifecycle == ProductLifecycle.Removed && + applicability.Version.Kind == VersionSpecKind.GreaterThanOrEqual && + !string.IsNullOrEmpty(versionDisplay)) + { + versionDisplay = versionDisplay.TrimEnd('+'); + } + } + return new ApplicabilityRenderData( BadgeLifecycleText: badgeLifecycleText, - Version: version, + Version: versionDisplay, TooltipText: tooltipText, LifecycleClass: lifecycleClass, + LifecycleName: applicability.GetLifeCycleName(), ShowLifecycleName: showLifecycle, ShowVersion: showVersion ); @@ -54,9 +72,26 @@ public ApplicabilityRenderData RenderCombinedApplicability( AppliesCollection allApplications) { var applicabilityList = applicabilities.ToList(); - var primaryApplicability = ApplicabilitySelector.GetPrimaryApplicability(applicabilityList, versioningSystem.Current); - var primaryRenderData = RenderApplicability(primaryApplicability, applicabilityDefinition, versioningSystem, allApplications); + // Sort by lifecycle priority (GA > Beta > Preview > etc.) to determine display order + var sortedApplicabilities = applicabilityList + .OrderBy(a => GetLifecycleOrder(a.Lifecycle)) + .ThenByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) + .ToList(); + + var primaryLifecycle = sortedApplicabilities.First(); + + var primaryRender = RenderApplicability(primaryLifecycle, applicabilityDefinition, versioningSystem, allApplications); + + // If the primary lifecycle returns an empty badge text (indicating "use previous lifecycle") + // and we have multiple lifecycles, use the next lifecycle in priority order + var applicabilityToDisplay = string.IsNullOrEmpty(primaryRender.BadgeLifecycleText) && + string.IsNullOrEmpty(primaryRender.Version) && + sortedApplicabilities.Count >= 2 + ? sortedApplicabilities[1] + : primaryLifecycle; + + var primaryRenderData = RenderApplicability(applicabilityToDisplay, applicabilityDefinition, versioningSystem, allApplications); var combinedTooltip = BuildCombinedTooltipText(applicabilityList, applicabilityDefinition, versioningSystem); // Check if there are multiple different lifecycles @@ -70,7 +105,6 @@ public ApplicabilityRenderData RenderCombinedApplicability( }; } - private static string BuildCombinedTooltipText( List applicabilities, ApplicabilityMappings.ApplicabilityDefinition applicabilityDefinition, @@ -80,17 +114,17 @@ private static string BuildCombinedTooltipText( // Order by the same logic as primary selection: available first (by version desc), then future (by version asc) var orderedApplicabilities = applicabilities - .OrderByDescending(a => a.Version is null || a.Version is AllVersions || a.Version <= versioningSystem.Current ? 1 : 0) - .ThenByDescending(a => a.Version ?? new SemVersion(0, 0, 0)) - .ThenBy(a => a.Version ?? new SemVersion(0, 0, 0)) + .OrderByDescending(a => a.Version is null || a.Version is AllVersionsSpec || + (a.Version is VersionSpec vs && vs.Min <= versioningSystem.Current) ? 1 : 0) + .ThenByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) + .ThenBy(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) .ToList(); foreach (var applicability in orderedApplicabilities) { - var realVersion = TryGetRealVersion(applicability, out var v) ? v : null; var lifecycleFull = GetLifecycleFullText(applicability.Lifecycle); - var heading = CreateApplicabilityHeading(applicability, applicabilityDefinition, realVersion); - var tooltipText = BuildTooltipText(applicability, applicabilityDefinition, versioningSystem, realVersion, lifecycleFull); + var heading = CreateApplicabilityHeading(applicability, applicabilityDefinition); + var tooltipText = BuildTooltipText(applicability, applicabilityDefinition, versioningSystem, lifecycleFull); // language=html tooltipParts.Add($"
{heading}{tooltipText}
"); } @@ -98,11 +132,10 @@ private static string BuildCombinedTooltipText( return string.Join("\n\n", tooltipParts); } - private static string CreateApplicabilityHeading(Applicability applicability, ApplicabilityMappings.ApplicabilityDefinition applicabilityDefinition, - SemVersion? realVersion) + private static string CreateApplicabilityHeading(Applicability applicability, ApplicabilityMappings.ApplicabilityDefinition applicabilityDefinition) { var lifecycleName = applicability.GetLifeCycleName(); - var versionText = realVersion is not null ? $" {realVersion}" : ""; + var versionText = applicability.Version is not null ? $" {applicability.Version.Min}" : ""; // language=html return $"""{applicabilityDefinition.DisplayName} {lifecycleName}{versionText}:"""; } @@ -122,14 +155,13 @@ private static string BuildTooltipText( Applicability applicability, ApplicabilityMappings.ApplicabilityDefinition applicabilityDefinition, VersioningSystem versioningSystem, - SemVersion? realVersion, string lifecycleFull) { var tooltipText = ""; - tooltipText = realVersion is not null - ? realVersion <= versioningSystem.Current - ? $"{lifecycleFull} on {applicabilityDefinition.DisplayName} version {realVersion} and later unless otherwise specified." + tooltipText = applicability.Version is not null && applicability.Version != AllVersionsSpec.Instance + ? applicability.Version.Min <= versioningSystem.Current + ? $"{lifecycleFull} on {applicabilityDefinition.DisplayName} version {applicability.Version.Min} and later unless otherwise specified." : applicability.Lifecycle switch { ProductLifecycle.GenerallyAvailable @@ -167,40 +199,97 @@ or ProductLifecycle.TechnicalPreview private static string BuildBadgeLifecycleText( Applicability applicability, VersioningSystem versioningSystem, - SemVersion? realVersion, AppliesCollection allApplications) { var badgeText = ""; - if (realVersion is not null && realVersion > versioningSystem.Current) + var versionSpec = applicability.Version; + + if (versionSpec is not null && versionSpec != AllVersionsSpec.Instance) { - badgeText = applicability.Lifecycle switch + var isMinReleased = versionSpec.Min <= versioningSystem.Current; + var isMaxReleased = versionSpec.Max is not null && versionSpec.Max <= versioningSystem.Current; + + // Determine if we should show "Planned" badge + var shouldShowPlanned = (versionSpec.Kind == VersionSpecKind.GreaterThanOrEqual && !isMinReleased) + || (versionSpec.Kind == VersionSpecKind.Range && !isMaxReleased && !isMinReleased) + || (versionSpec.Kind == VersionSpecKind.Exact && !isMinReleased); + + // Check lifecycle count for "use previous lifecycle" logic + if (shouldShowPlanned) { - ProductLifecycle.TechnicalPreview => "Planned", - ProductLifecycle.Beta => "Planned", - ProductLifecycle.GenerallyAvailable => - allApplications.Any(a => a.Lifecycle is ProductLifecycle.TechnicalPreview or ProductLifecycle.Beta) - ? "GA planned" - : "Planned", - ProductLifecycle.Deprecated => "Deprecation planned", - ProductLifecycle.Removed => "Removal planned", - ProductLifecycle.Planned => "Planned", - ProductLifecycle.Unavailable => "Unavailable", - _ => badgeText - }; + var lifecycleCount = allApplications.Count; + + // If lifecycle count >= 2, we should use previous lifecycle instead of showing "Planned" + if (lifecycleCount >= 2) + return string.Empty; + + // Otherwise show planned badge (lifecycle count == 1) + badgeText = applicability.Lifecycle switch + { + ProductLifecycle.TechnicalPreview => "Planned", + ProductLifecycle.Beta => "Planned", + ProductLifecycle.GenerallyAvailable => "Planned", + ProductLifecycle.Deprecated => "Deprecation planned", + ProductLifecycle.Removed => "Removal planned", + ProductLifecycle.Planned => "Planned", + ProductLifecycle.Unavailable => "Unavailable", + _ => badgeText + }; + } } return badgeText; } - private static bool TryGetRealVersion(Applicability applicability, [NotNullWhen(true)] out SemVersion? version) + /// + /// Gets the version to display in badges, handling VersionSpec kinds + /// + private static string GetBadgeVersionText(VersionSpec? versionSpec, VersioningSystem versioningSystem) { - version = null; - if (applicability.Version is not null && applicability.Version != AllVersions.Instance) + if (versionSpec is null || versionSpec == AllVersionsSpec.Instance) + return string.Empty; + + var kind = versionSpec.Kind; + var min = versionSpec.Min; + var max = versionSpec.Max; + + // Check if versions are released + var minReleased = min <= versioningSystem.Current; + var maxReleased = max is not null && max <= versioningSystem.Current; + + return kind switch { - version = applicability.Version; - return true; - } + VersionSpecKind.GreaterThanOrEqual => minReleased + ? $"{min.Major}.{min.Minor}+" + : string.Empty, + + VersionSpecKind.Range => maxReleased + ? $"{min.Major}.{min.Minor}-{max!.Major}.{max.Minor}" + : minReleased + ? $"{min.Major}.{min.Minor}+" + : string.Empty, - return false; + VersionSpecKind.Exact => minReleased + ? $"{min.Major}.{min.Minor}" + : string.Empty, + + _ => string.Empty + }; } + private static int GetLifecycleOrder(ProductLifecycle lifecycle) => lifecycle switch + { + ProductLifecycle.GenerallyAvailable => 0, + ProductLifecycle.Beta => 1, + ProductLifecycle.TechnicalPreview => 2, + ProductLifecycle.Planned => 3, + ProductLifecycle.Deprecated => 4, + ProductLifecycle.Removed => 5, + ProductLifecycle.Unavailable => 6, + _ => 999 + }; + + /// + /// Checks if a version should be considered released + /// + private static bool IsVersionReleased(SemVersion version, VersioningSystem versioningSystem) => version <= versioningSystem.Current; } diff --git a/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml b/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml index a9831405c..815050a7a 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml +++ b/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml @@ -12,7 +12,7 @@ @if (item.RenderData.ShowLifecycleName) { - @item.PrimaryApplicability.GetLifeCycleName() + @item.RenderData.LifecycleName } @if (item.RenderData.ShowVersion) { diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs index 3b22299f8..1bf241171 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs @@ -36,8 +36,8 @@ public void RoundTripStackWithVersion() { Stack = new AppliesCollection( [ - new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = new SemVersion(8, 0, 0) }, - new Applicability { Lifecycle = ProductLifecycle.Beta, Version = new SemVersion(7, 17, 0) } + new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }, + new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"7.17.0" } ]) }; @@ -57,8 +57,8 @@ public void RoundTripDeploymentAllProperties() Deployment = new DeploymentApplicability { Self = AppliesCollection.GenerallyAvailable, - Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"3.0.0" }]), - Eck = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"2.0.0" }]), + Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"3.0.0" }]), + Eck = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"2.0.0" }]), Ess = AppliesCollection.GenerallyAvailable } }; @@ -82,8 +82,8 @@ public void RoundTripServerlessAllProperties() Serverless = new ServerlessProjectApplicability { Elasticsearch = AppliesCollection.GenerallyAvailable, - Observability = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = AllVersions.Instance }]), - Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"1.0.0" }]) + Observability = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = AllVersionsSpec.Instance }]), + Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"1.0.0" }]) } }; @@ -140,9 +140,9 @@ public void RoundTripProductApplicabilityMultipleProducts() ProductApplicability = new ProductApplicability { Ecctl = AppliesCollection.GenerallyAvailable, - Curator = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (SemVersion)"5.0.0" }]), - ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.2.0" }]), - EdotDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.9.0" }]) + Curator = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (VersionSpec)"5.0.0" }]), + ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.2.0" }]), + EdotDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.9.0" }]) } }; @@ -165,27 +165,27 @@ public void RoundTripAllProductApplicabilityProperties() ProductApplicability = new ProductApplicability { Ecctl = AppliesCollection.GenerallyAvailable, - Curator = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (SemVersion)"5.0.0" }]), - ApmAgentAndroid = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"1.0.0" }]), - ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.2.0" }]), - ApmAgentGo = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"2.0.0" }]), - ApmAgentIos = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (SemVersion)"0.5.0" }]), - ApmAgentJava = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.30.0" }]), - ApmAgentNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"3.0.0" }]), - ApmAgentPhp = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.8.0" }]), - ApmAgentPython = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"6.0.0" }]), - ApmAgentRuby = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"4.0.0" }]), - ApmAgentRumJs = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"5.0.0" }]), - EdotIos = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.9.0" }]), - EdotAndroid = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.8.0" }]), - EdotDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.9.0" }]), - EdotJava = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.7.0" }]), - EdotNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.6.0" }]), - EdotPhp = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.5.0" }]), - EdotPython = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"0.4.0" }]), - EdotCfAws = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (SemVersion)"0.3.0" }]), - EdotCfAzure = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (SemVersion)"0.2.0" }]), - EdotCollector = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.0.0" }]) + Curator = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (VersionSpec)"5.0.0" }]), + ApmAgentAndroid = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"1.0.0" }]), + ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.2.0" }]), + ApmAgentGo = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"2.0.0" }]), + ApmAgentIos = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (VersionSpec)"0.5.0" }]), + ApmAgentJava = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.30.0" }]), + ApmAgentNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"3.0.0" }]), + ApmAgentPhp = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.8.0" }]), + ApmAgentPython = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"6.0.0" }]), + ApmAgentRuby = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"4.0.0" }]), + ApmAgentRumJs = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"5.0.0" }]), + EdotIos = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.9.0" }]), + EdotAndroid = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.8.0" }]), + EdotDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.9.0" }]), + EdotJava = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.7.0" }]), + EdotNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.6.0" }]), + EdotPhp = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.5.0" }]), + EdotPython = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"0.4.0" }]), + EdotCfAws = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (VersionSpec)"0.3.0" }]), + EdotCfAzure = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (VersionSpec)"0.2.0" }]), + EdotCollector = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.0.0" }]) } }; @@ -225,27 +225,27 @@ public void RoundTripComplexAllFieldsPopulated() { Stack = new AppliesCollection( [ - new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.0.0" }, - new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"7.17.0" } + new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }, + new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"7.17.0" } ]), Deployment = new DeploymentApplicability { Self = AppliesCollection.GenerallyAvailable, - Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"3.0.0" }]), - Eck = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"2.0.0" }]), + Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"3.0.0" }]), + Eck = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"2.0.0" }]), Ess = AppliesCollection.GenerallyAvailable }, Serverless = new ServerlessProjectApplicability { Elasticsearch = AppliesCollection.GenerallyAvailable, - Observability = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = AllVersions.Instance }]), - Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"1.0.0" }]) + Observability = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = AllVersionsSpec.Instance }]), + Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"1.0.0" }]) }, Product = AppliesCollection.GenerallyAvailable, ProductApplicability = new ProductApplicability { Ecctl = AppliesCollection.GenerallyAvailable, - ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.2.0" }]) + ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.2.0" }]) } }; @@ -274,7 +274,7 @@ public void RoundTripAllLifecycles() { var lifecycles = Enum.GetValues(); var applicabilities = lifecycles.Select(lc => - new Applicability { Lifecycle = lc, Version = (SemVersion)"1.0.0" } + new Applicability { Lifecycle = lc, Version = (VersionSpec)"1.0.0" } ).ToArray(); var original = new ApplicableTo @@ -297,10 +297,10 @@ public void RoundTripMultipleApplicabilitiesInCollection() { Stack = new AppliesCollection( [ - new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.0.0" }, - new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"7.17.0" }, - new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (SemVersion)"7.16.0" }, - new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (SemVersion)"6.0.0" } + new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }, + new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"7.17.0" }, + new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (VersionSpec)"7.16.0" }, + new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (VersionSpec)"6.0.0" } ]) }; @@ -345,16 +345,16 @@ public void RoundTripAllVersionsSerializesAsSemanticVersion() { var original = new ApplicableTo { - Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = AllVersions.Instance }]) + Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = AllVersionsSpec.Instance }]) }; var json = JsonSerializer.Serialize(original, _options); - json.Should().Contain("\"version\": \"9999.9999.9999\""); + json.Should().Contain("\"version\": \"all\""); var deserialized = JsonSerializer.Deserialize(json, _options); deserialized.Should().NotBeNull(); deserialized!.Stack.Should().NotBeNull(); - deserialized.Stack!.First().Version.Should().Be(AllVersions.Instance); + deserialized.Stack!.First().Version.Should().Be(AllVersionsSpec.Instance); } [Fact] @@ -365,7 +365,7 @@ public void RoundTripProductAndProductApplicabilityBothPresent() Product = AppliesCollection.GenerallyAvailable, ProductApplicability = new ProductApplicability { - Ecctl = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"1.0.0" }]) + Ecctl = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"1.0.0" }]) } }; diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs index fbff52703..e42e47f04 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs @@ -34,7 +34,7 @@ public void SerializeStackProducesCorrectJson() "type": "stack", "sub_type": "stack", "lifecycle": "ga", - "version": "9999.9999.9999" + "version": "all" } ] """); @@ -49,7 +49,7 @@ public void SerializeStackWithVersionProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.Beta, - Version = (SemVersion)"8.0.0" + Version = (VersionSpec)"8.0.0" } ]) }; @@ -64,7 +64,7 @@ public void SerializeStackWithVersionProducesCorrectJson() "type": "stack", "sub_type": "stack", "lifecycle": "beta", - "version": "8.0.0" + "version": "8.0+" } ] """); @@ -80,12 +80,12 @@ public void SerializeMultipleApplicabilitiesProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, - Version = (SemVersion)"8.0.0" + Version = (VersionSpec)"8.0.0" }, new Applicability { Lifecycle = ProductLifecycle.Beta, - Version = (SemVersion)"7.17.0" + Version = (VersionSpec)"7.17.0" } ]) }; @@ -100,13 +100,13 @@ public void SerializeMultipleApplicabilitiesProducesCorrectJson() "type": "stack", "sub_type": "stack", "lifecycle": "ga", - "version": "8.0.0" + "version": "8.0+" }, { "type": "stack", "sub_type": "stack", "lifecycle": "beta", - "version": "7.17.0" + "version": "7.17+" } ] """); @@ -123,7 +123,7 @@ public void SerializeDeploymentProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, - Version = (SemVersion)"3.0.0" + Version = (VersionSpec)"3.0.0" } ]), Ess = AppliesCollection.GenerallyAvailable @@ -140,13 +140,13 @@ public void SerializeDeploymentProducesCorrectJson() "type": "deployment", "sub_type": "ece", "lifecycle": "ga", - "version": "3.0.0" + "version": "3.0+" }, { "type": "deployment", "sub_type": "ess", "lifecycle": "ga", - "version": "9999.9999.9999" + "version": "all" } ] """); @@ -163,7 +163,7 @@ public void SerializeServerlessProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.Beta, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" } ]), Security = AppliesCollection.GenerallyAvailable @@ -180,13 +180,13 @@ public void SerializeServerlessProducesCorrectJson() "type": "serverless", "sub_type": "elasticsearch", "lifecycle": "beta", - "version": "1.0.0" + "version": "1.0+" }, { "type": "serverless", "sub_type": "security", "lifecycle": "ga", - "version": "9999.9999.9999" + "version": "all" } ] """); @@ -201,7 +201,7 @@ public void SerializeProductProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, - Version = (SemVersion)"0.5.0" + Version = (VersionSpec)"0.5.0" } ]) }; @@ -216,7 +216,7 @@ public void SerializeProductProducesCorrectJson() "type": "product", "sub_type": "product", "lifecycle": "preview", - "version": "0.5.0" + "version": "0.5+" } ] """); @@ -233,7 +233,7 @@ public void SerializeProductApplicabilityProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.Deprecated, - Version = (SemVersion)"5.0.0" + Version = (VersionSpec)"5.0.0" } ]), ApmAgentDotnet = AppliesCollection.GenerallyAvailable @@ -250,13 +250,13 @@ public void SerializeProductApplicabilityProducesCorrectJson() "type": "product", "sub_type": "ecctl", "lifecycle": "deprecated", - "version": "5.0.0" + "version": "5.0+" }, { "type": "product", "sub_type": "apm-agent-dotnet", "lifecycle": "ga", - "version": "9999.9999.9999" + "version": "all" } ] """); @@ -272,27 +272,27 @@ public void SerializeAllLifecyclesProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" }, new Applicability { Lifecycle = ProductLifecycle.Beta, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" }, new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" }, new Applicability { Lifecycle = ProductLifecycle.Deprecated, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" }, new Applicability { Lifecycle = ProductLifecycle.Removed, - Version = (SemVersion)"1.0.0" + Version = (VersionSpec)"1.0.0" } ]) }; @@ -315,7 +315,7 @@ public void SerializeComplexProducesCorrectJson() new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, - Version = (SemVersion)"8.0.0" + Version = (VersionSpec)"8.0.0" } ]), Deployment = new DeploymentApplicability @@ -364,7 +364,7 @@ public void SerializeValidatesJsonStructure() new Applicability { Lifecycle = ProductLifecycle.Beta, - Version = (SemVersion)"3.0.0" + Version = (VersionSpec)"3.0.0" } ]) } @@ -383,12 +383,12 @@ public void SerializeValidatesJsonStructure() stackEntry.GetProperty("type").GetString().Should().Be("stack"); stackEntry.GetProperty("sub_type").GetString().Should().Be("stack"); stackEntry.GetProperty("lifecycle").GetString().Should().Be("ga"); - stackEntry.GetProperty("version").GetString().Should().Be("9999.9999.9999"); + stackEntry.GetProperty("version").GetString().Should().Be("all"); var deploymentEntry = array[1]; deploymentEntry.GetProperty("type").GetString().Should().Be("deployment"); deploymentEntry.GetProperty("sub_type").GetString().Should().Be("ece"); deploymentEntry.GetProperty("lifecycle").GetString().Should().Be("beta"); - deploymentEntry.GetProperty("version").GetString().Should().Be("3.0.0"); + deploymentEntry.GetProperty("version").GetString().Should().Be("3.0+"); } } diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ProductApplicabilityToStringTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ProductApplicabilityToStringTests.cs index 3cefe5c4a..6e76d6f84 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ProductApplicabilityToStringTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ProductApplicabilityToStringTests.cs @@ -50,7 +50,7 @@ public void ProductApplicabilityToStringWithSomePropertiesOnlyIncludesSetPropert var productApplicability = new ProductApplicability { ApmAgentDotnet = AppliesCollection.GenerallyAvailable, - Ecctl = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = new SemVersion(1, 0, 0) }]) + Ecctl = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = VersionSpec.TryParse("1.0.0", out var v) ? v : null }]) }; var result = productApplicability.ToString(); diff --git a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs index f0f372d2d..b60ea8d17 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs @@ -227,11 +227,12 @@ public void GeneratesDeterministicSyncKeysAcrossMultipleRuns() var expectedKeys = new Dictionary { // These are the actual SHA256-based hashes that should never change - { "stack: ga 9.1", "applies-031B7112" }, - { "stack: preview 9.0", "applies-361F73DC" }, - { "ess: ga 8.11", "applies-32E204F7" }, - { "deployment: { ece: ga 9.0, ess: ga 9.1 }", "applies-D099CDEF" }, - { "serverless: all", "applies-A34B17C6" }, + // (unless the version format actually changes) + { "stack: ga 9.1", "applies-A8B9CC9C" }, + { "stack: preview 9.0", "applies-66AECC4E" }, + { "ess: ga 8.11", "applies-9CA8543E" }, + { "deployment: { ece: ga 9.0, ess: ga 9.1 }", "applies-51C670D4" }, + { "serverless: all", "applies-A34B17C6" } }; foreach (var (definition, expectedKey) in expectedKeys) diff --git a/tests/Elastic.Markdown.Tests/Search/DocumentationDocumentSerializationTests.cs b/tests/Elastic.Markdown.Tests/Search/DocumentationDocumentSerializationTests.cs index e2090cd0c..def825530 100644 --- a/tests/Elastic.Markdown.Tests/Search/DocumentationDocumentSerializationTests.cs +++ b/tests/Elastic.Markdown.Tests/Search/DocumentationDocumentSerializationTests.cs @@ -47,7 +47,7 @@ public void SerializeDocumentWithStackAppliesToProducesCorrectJson() stackEntry.GetProperty("type").GetString().Should().Be("stack"); stackEntry.GetProperty("sub_type").GetString().Should().Be("stack"); stackEntry.GetProperty("lifecycle").GetString().Should().Be("ga"); - stackEntry.GetProperty("version").GetString().Should().Be("9999.9999.9999"); + stackEntry.GetProperty("version").GetString().Should().Be("all"); } [Fact] @@ -64,7 +64,7 @@ public void SerializeDocumentWithDeploymentAppliesToProducesCorrectJson() Deployment = new DeploymentApplicability { Ess = AppliesCollection.GenerallyAvailable, - Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"3.5.0" }]) + Ece = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"3.5.0" }]) } } }; @@ -82,14 +82,14 @@ public void SerializeDocumentWithDeploymentAppliesToProducesCorrectJson() essEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); essEntry.GetProperty("type").GetString().Should().Be("deployment"); essEntry.GetProperty("lifecycle").GetString().Should().Be("ga"); - essEntry.GetProperty("version").GetString().Should().Be("9999.9999.9999"); + essEntry.GetProperty("version").GetString().Should().Be("all"); // Verify ECE entry var eceEntry = appliesArray.FirstOrDefault(e => e.GetProperty("sub_type").GetString() == "ece"); eceEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); eceEntry.GetProperty("type").GetString().Should().Be("deployment"); eceEntry.GetProperty("lifecycle").GetString().Should().Be("beta"); - eceEntry.GetProperty("version").GetString().Should().Be("3.5.0"); + eceEntry.GetProperty("version").GetString().Should().Be("3.5+"); } [Fact] @@ -105,8 +105,8 @@ public void SerializeDocumentWithServerlessAppliesToProducesCorrectJson() { Serverless = new ServerlessProjectApplicability { - Elasticsearch = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.0.0" }]), - Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (SemVersion)"1.0.0" }]) + Elasticsearch = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }]), + Security = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.TechnicalPreview, Version = (VersionSpec)"1.0.0" }]) } } }; @@ -124,14 +124,14 @@ public void SerializeDocumentWithServerlessAppliesToProducesCorrectJson() esEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); esEntry.GetProperty("type").GetString().Should().Be("serverless"); esEntry.GetProperty("lifecycle").GetString().Should().Be("ga"); - esEntry.GetProperty("version").GetString().Should().Be("8.0.0"); + esEntry.GetProperty("version").GetString().Should().Be("8.0+"); // Verify security entry var secEntry = appliesArray.FirstOrDefault(e => e.GetProperty("sub_type").GetString() == "security"); secEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); secEntry.GetProperty("type").GetString().Should().Be("serverless"); secEntry.GetProperty("lifecycle").GetString().Should().Be("preview"); - secEntry.GetProperty("version").GetString().Should().Be("1.0.0"); + secEntry.GetProperty("version").GetString().Should().Be("1.0+"); } [Fact] @@ -145,7 +145,7 @@ public void SerializeDocumentWithProductAppliesToProducesCorrectJson() SearchTitle = "Product Test", Applies = new ApplicableTo { - Product = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"2.0.0" }]) + Product = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"2.0.0" }]) } }; @@ -161,7 +161,7 @@ public void SerializeDocumentWithProductAppliesToProducesCorrectJson() productEntry.GetProperty("type").GetString().Should().Be("product"); productEntry.GetProperty("sub_type").GetString().Should().Be("product"); productEntry.GetProperty("lifecycle").GetString().Should().Be("beta"); - productEntry.GetProperty("version").GetString().Should().Be("2.0.0"); + productEntry.GetProperty("version").GetString().Should().Be("2.0+"); } [Fact] @@ -177,8 +177,8 @@ public void SerializeDocumentWithProductApplicabilityProducesCorrectJson() { ProductApplicability = new ProductApplicability { - ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"1.5.0" }]), - ApmAgentNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (SemVersion)"2.0.0" }]) + ApmAgentDotnet = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"1.5.0" }]), + ApmAgentNode = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (VersionSpec)"2.0.0" }]) } } }; @@ -196,14 +196,14 @@ public void SerializeDocumentWithProductApplicabilityProducesCorrectJson() dotnetEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); dotnetEntry.GetProperty("type").GetString().Should().Be("product"); dotnetEntry.GetProperty("lifecycle").GetString().Should().Be("ga"); - dotnetEntry.GetProperty("version").GetString().Should().Be("1.5.0"); + dotnetEntry.GetProperty("version").GetString().Should().Be("1.5+"); // Verify apm-agent-node entry var nodeEntry = appliesArray.FirstOrDefault(e => e.GetProperty("sub_type").GetString() == "apm-agent-node"); nodeEntry.ValueKind.Should().NotBe(JsonValueKind.Undefined); nodeEntry.GetProperty("type").GetString().Should().Be("product"); nodeEntry.GetProperty("lifecycle").GetString().Should().Be("deprecated"); - nodeEntry.GetProperty("version").GetString().Should().Be("2.0.0"); + nodeEntry.GetProperty("version").GetString().Should().Be("2.0+"); } [Fact] @@ -217,7 +217,7 @@ public void SerializeDocumentWithComplexAppliesToProducesCorrectJson() SearchTitle = "Complex Test", Applies = new ApplicableTo { - Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.0.0" }]), + Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }]), Deployment = new DeploymentApplicability { Ess = AppliesCollection.GenerallyAvailable @@ -305,10 +305,10 @@ public void RoundTripDocumentWithAppliesToPreservesData() LastUpdated = DateTimeOffset.Parse("2024-01-15T09:00:00Z", CultureInfo.InvariantCulture), Applies = new ApplicableTo { - Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.5.0" }]), + Stack = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.5.0" }]), Deployment = new DeploymentApplicability { - Ess = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"8.6.0" }]) + Ess = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"8.6.0" }]) } }, Headings = ["Introduction", "Getting Started"], @@ -343,9 +343,9 @@ public void SerializeDocumentWithMultipleApplicabilitiesPerTypeProducesMultipleA { Stack = new AppliesCollection( [ - new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (SemVersion)"8.0.0" }, - new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (SemVersion)"7.17.0" }, - new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (SemVersion)"7.0.0" } + new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"8.0.0" }, + new Applicability { Lifecycle = ProductLifecycle.Beta, Version = (VersionSpec)"7.17.0" }, + new Applicability { Lifecycle = ProductLifecycle.Deprecated, Version = (VersionSpec)"7.0.0" } ]) } }; diff --git a/tests/authoring/Applicability/ApplicableToComponent.fs b/tests/authoring/Applicability/ApplicableToComponent.fs index 2878c4a95..bbc93c67f 100644 --- a/tests/authoring/Applicability/ApplicableToComponent.fs +++ b/tests/authoring/Applicability/ApplicableToComponent.fs @@ -413,8 +413,8 @@ If this functionality is unavailable or behaves differently when deployed on ECH This functionality may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."> Stack - - GA planned + + Preview @@ -526,7 +526,7 @@ This functionality may be changed or removed in a future release. Elastic will w Preview - 1.3.0 + 1.3+ @@ -824,7 +824,7 @@ Beta features are subject to change. The design and code is less mature than off GA - 8.0.0 + 8.0+ diff --git a/tests/authoring/Inline/AppliesToRole.fs b/tests/authoring/Inline/AppliesToRole.fs index 6583a0d79..a4f60ba3d 100644 --- a/tests/authoring/Inline/AppliesToRole.fs +++ b/tests/authoring/Inline/AppliesToRole.fs @@ -150,8 +150,8 @@ If this functionality is unavailable or behaves differently when deployed on ECH This functionality may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."> Stack - - GA planned + + Preview From e6d710f987fd0fd55587c66151185d2c318b510d Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 04:04:27 -0300 Subject: [PATCH 03/17] Show base version if we don't get a specified version for a versioned product --- .../AppliesTo/ApplicableToYamlConverter.cs | 6 ++-- src/Elastic.Documentation/SemVersion.cs | 2 +- .../Myst/Components/ApplicabilityRenderer.cs | 29 ++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index 2b6269034..fc6da5ed2 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -284,7 +284,7 @@ private static void ValidateApplicabilityCollection(string key, AppliesCollectio // Rule: Only one item per key can use greater-than syntax var greaterThanItems = items.Where(a => - a.Version is VersionSpec spec && spec.Kind == VersionSpecKind.GreaterThanOrEqual && + a.Version is { Kind: VersionSpecKind.GreaterThanOrEqual } && a.Version != AllVersionsSpec.Instance).ToList(); if (greaterThanItems.Count > 1) @@ -335,8 +335,8 @@ private static bool CheckVersionOverlap(VersionSpec v1, VersionSpec v2, out stri var (v1Min, v1Max) = GetEffectiveRange(v1); var (v2Min, v2Max) = GetEffectiveRange(v2); - var overlaps = v1Min.CompareTo(v2Max ?? new SemVersion(9999, 9999, 9999)) <= 0 && - v2Min.CompareTo(v1Max ?? new SemVersion(9999, 9999, 9999)) <= 0; + var overlaps = v1Min.CompareTo(v2Max ?? new SemVersion(99999, 99999, 99999)) <= 0 && + v2Min.CompareTo(v1Max ?? new SemVersion(99999, 99999, 99999)) <= 0; if (overlaps) message = $"Version ranges overlap."; diff --git a/src/Elastic.Documentation/SemVersion.cs b/src/Elastic.Documentation/SemVersion.cs index 0516f22e0..8ee4f2cec 100644 --- a/src/Elastic.Documentation/SemVersion.cs +++ b/src/Elastic.Documentation/SemVersion.cs @@ -8,7 +8,7 @@ namespace Elastic.Documentation; -public class AllVersions() : SemVersion(9999, 9999, 9999) +public class AllVersions() : SemVersion(99999, 99999, 99999) { public static AllVersions Instance { get; } = new(); } diff --git a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs index 7984fe4a8..73fba4a51 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs @@ -2,7 +2,6 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.Diagnostics.CodeAnalysis; using Elastic.Documentation; using Elastic.Documentation.AppliesTo; using Elastic.Documentation.Configuration.Versions; @@ -159,8 +158,10 @@ private static string BuildTooltipText( { var tooltipText = ""; - tooltipText = applicability.Version is not null && applicability.Version != AllVersionsSpec.Instance - ? applicability.Version.Min <= versioningSystem.Current + // Check if a specific version is provided + if (applicability.Version is not null && applicability.Version != AllVersionsSpec.Instance) + { + tooltipText = applicability.Version.Min <= versioningSystem.Current ? $"{lifecycleFull} on {applicabilityDefinition.DisplayName} version {applicability.Version.Min} and later unless otherwise specified." : applicability.Lifecycle switch { @@ -174,8 +175,21 @@ or ProductLifecycle.TechnicalPreview ProductLifecycle.Removed => $"We plan to remove this functionality in a future {applicabilityDefinition.DisplayName} update. Subject to change.", _ => tooltipText + }; + } + else + { + // No version specified - check if we should show base version + tooltipText = versioningSystem.Base.Major != AllVersionsSpec.Instance.Min.Major + ? applicability.Lifecycle switch + { + ProductLifecycle.Removed => + $"Removed in {applicabilityDefinition.DisplayName} {versioningSystem.Base.Major}.{versioningSystem.Base.Minor}.", + _ => + $"{lifecycleFull} since {versioningSystem.Base.Major}.{versioningSystem.Base.Minor}." } - : $"{lifecycleFull} on {applicabilityDefinition.DisplayName} unless otherwise specified."; + : $"{lifecycleFull} on {applicabilityDefinition.DisplayName} unless otherwise specified."; + } var disclaimer = GetDisclaimer(applicability.Lifecycle, versioningSystem.Id); if (disclaimer is not null) @@ -246,8 +260,15 @@ private static string BuildBadgeLifecycleText( ///
private static string GetBadgeVersionText(VersionSpec? versionSpec, VersioningSystem versioningSystem) { + // When no version is specified, check if we should show the base version if (versionSpec is null || versionSpec == AllVersionsSpec.Instance) + { + if (versioningSystem.Base.Major != AllVersionsSpec.Instance.Min.Major) + return $"{versioningSystem.Base.Major}.{versioningSystem.Base.Minor}+"; + + // Otherwise, this is an unversioned product, show no version return string.Empty; + } var kind = versionSpec.Kind; var min = versionSpec.Min; From 4bd996a2b3123ee7021841c0d048fdd0dec1eb2a Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 04:37:03 -0300 Subject: [PATCH 04/17] Adjust tests to better match the currently expected output --- .../Myst/Components/ApplicabilityRenderer.cs | 4 ++-- .../ApplicableToJsonConverterSerializationTests.cs | 4 +++- tests/authoring/Applicability/ApplicableToComponent.fs | 8 ++++---- tests/authoring/Blocks/Admonitions.fs | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs index 73fba4a51..6895236fb 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs @@ -114,7 +114,7 @@ private static string BuildCombinedTooltipText( // Order by the same logic as primary selection: available first (by version desc), then future (by version asc) var orderedApplicabilities = applicabilities .OrderByDescending(a => a.Version is null || a.Version is AllVersionsSpec || - (a.Version is VersionSpec vs && vs.Min <= versioningSystem.Current) ? 1 : 0) + (a.Version is { } vs && vs.Min <= versioningSystem.Current) ? 1 : 0) .ThenByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) .ThenBy(a => a.Version?.Min ?? new SemVersion(0, 0, 0)) .ToList(); @@ -146,7 +146,7 @@ private static string CreateApplicabilityHeading(Applicability applicability, Ap ProductLifecycle.TechnicalPreview => "Available in technical preview", ProductLifecycle.Deprecated => "Deprecated", ProductLifecycle.Removed => "Removed", - ProductLifecycle.Unavailable => "Not available", + ProductLifecycle.Unavailable => "Unavailable", _ => "" }; diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs index e42e47f04..4e47b5edd 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterSerializationTests.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Text.Encodings.Web; using System.Text.Json; using Elastic.Documentation; using Elastic.Documentation.AppliesTo; @@ -13,7 +14,8 @@ public class ApplicableToJsonConverterSerializationTests { private readonly JsonSerializerOptions _options = new() { - WriteIndented = true + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; [Fact] diff --git a/tests/authoring/Applicability/ApplicableToComponent.fs b/tests/authoring/Applicability/ApplicableToComponent.fs index bbc93c67f..af138871e 100644 --- a/tests/authoring/Applicability/ApplicableToComponent.fs +++ b/tests/authoring/Applicability/ApplicableToComponent.fs @@ -142,7 +142,7 @@ stack: ga let ``renders all versions`` () = markdown |> convertsToHtml """

- Stack @@ -479,7 +479,7 @@ stack: unavailable let ``renders unavailable`` () = markdown |> convertsToHtml """

- + Stack @@ -500,7 +500,7 @@ product: ga let ``renders product all versions`` () = markdown |> convertsToHtml """

- + @@ -694,7 +694,7 @@ stack: let ``renders missing edge cases`` () = markdown |> convertsToHtml """

- Stack diff --git a/tests/authoring/Blocks/Admonitions.fs b/tests/authoring/Blocks/Admonitions.fs index d7efdb64b..324f3c5b4 100644 --- a/tests/authoring/Blocks/Admonitions.fs +++ b/tests/authoring/Blocks/Admonitions.fs @@ -64,7 +64,7 @@ This is a custom admonition with applies_to information.

Note - Stack @@ -82,7 +82,7 @@ If this functionality is unavailable or behaves differently when deployed on ECH
Warning - + Serverless @@ -98,7 +98,7 @@ If this functionality is unavailable or behaves differently when deployed on ECH
Tip - Serverless Elasticsearch From 7ead71683bc5f84d54c7e5687e7eaba37872bcbf Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 05:04:35 -0300 Subject: [PATCH 05/17] Add VersionSpec to YamlSerialization --- src/Elastic.Markdown/Myst/YamlSerialization.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Elastic.Markdown/Myst/YamlSerialization.cs b/src/Elastic.Markdown/Myst/YamlSerialization.cs index 4bf08dad3..0728b39b8 100644 --- a/src/Elastic.Markdown/Myst/YamlSerialization.cs +++ b/src/Elastic.Markdown/Myst/YamlSerialization.cs @@ -21,6 +21,7 @@ public static T Deserialize(string yaml, ProductsConfiguration products) .IgnoreUnmatchedProperties() .WithEnumNamingConvention(HyphenatedNamingConvention.Instance) .WithTypeConverter(new SemVersionConverter()) + .WithTypeConverter(new VersionSpecConverter()) .WithTypeConverter(new ProductConverter(products)) .WithTypeConverter(new ApplicableToYamlConverter(products.Products.Keys)) .Build(); From fcae6286f82493d0d377c48195c85dec80ab90f0 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 05:09:03 -0300 Subject: [PATCH 06/17] Typo! --- .../AppliesTo/ApplicableToJsonConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs index c36e350dc..336c03d12 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs @@ -72,7 +72,7 @@ public class ApplicableToJsonConverter : JsonConverter break; case "version": var versionStr = reader.GetString(); - if (versionStr != null && VersionSpecConverter.TryParse(versionStr, out var v)) + if (versionStr != null && VersionSpec.TryParse(versionStr, out var v)) version = v; break; } From e435de96c35f4d81bc785ca99838d50e351e203f Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 05:11:50 -0300 Subject: [PATCH 07/17] No need for it to be initialized here after all. --- src/Elastic.Markdown/Myst/YamlSerialization.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/YamlSerialization.cs b/src/Elastic.Markdown/Myst/YamlSerialization.cs index 0728b39b8..4bf08dad3 100644 --- a/src/Elastic.Markdown/Myst/YamlSerialization.cs +++ b/src/Elastic.Markdown/Myst/YamlSerialization.cs @@ -21,7 +21,6 @@ public static T Deserialize(string yaml, ProductsConfiguration products) .IgnoreUnmatchedProperties() .WithEnumNamingConvention(HyphenatedNamingConvention.Instance) .WithTypeConverter(new SemVersionConverter()) - .WithTypeConverter(new VersionSpecConverter()) .WithTypeConverter(new ProductConverter(products)) .WithTypeConverter(new ApplicableToYamlConverter(products.Products.Keys)) .Build(); From 7f5f2be0f6b2d5cb0784fb1bd76fb86431c04077 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 05:28:31 -0300 Subject: [PATCH 08/17] Handle "all" explicitly --- .../AppliesTo/ApplicableToJsonConverter.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs index 336c03d12..c8d987064 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs @@ -72,8 +72,14 @@ public class ApplicableToJsonConverter : JsonConverter break; case "version": var versionStr = reader.GetString(); - if (versionStr != null && VersionSpec.TryParse(versionStr, out var v)) - version = v; + if (versionStr != null) + { + // Handle "all" explicitly for AllVersionsSpec + if (string.Equals(versionStr.Trim(), "all", StringComparison.OrdinalIgnoreCase)) + version = AllVersionsSpec.Instance; + else if (VersionSpec.TryParse(versionStr, out var v)) + version = v; + } break; } } From 00ab8d0d4d8fb8e8decc14b1ef803972b1977db6 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 05:47:57 -0300 Subject: [PATCH 09/17] Adopting a few review suggestions --- .../AppliesTo/ApplicabilitySelector.cs | 6 ++---- .../AppliesTo/ApplicableToYamlConverter.cs | 12 +++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs index d090f2b02..4dcc98494 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs @@ -30,8 +30,7 @@ public static Applicability GetPrimaryApplicability(IEnumerable a }; var availableApplicabilities = applicabilityList - .Where(a => a.Version is null || a.Version is AllVersionsSpec || - (a.Version is VersionSpec vs && vs.Min <= currentVersion)) + .Where(a => a.Version is null || a.Version is AllVersionsSpec || a.Version.Min <= currentVersion) .ToList(); if (availableApplicabilities.Count != 0) @@ -43,8 +42,7 @@ public static Applicability GetPrimaryApplicability(IEnumerable a } var futureApplicabilities = applicabilityList - .Where(a => a.Version is not null && a.Version is not AllVersionsSpec && - a.Version is VersionSpec vs && vs.Min > currentVersion) + .Where(a => a.Version is not null && a.Version is not AllVersionsSpec && a.Version.Min > currentVersion) .ToList(); if (futureApplicabilities.Count != 0) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index fc6da5ed2..3db7a6247 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -294,15 +294,13 @@ private static void ValidateApplicabilityCollection(string key, AppliesCollectio } // Rule: In a range, the first version must be less than or equal the last version - foreach (var item in items) + foreach (var item in items.Where(a => a.Version is { Kind: VersionSpecKind.Range })) { - if (item.Version is { Kind: VersionSpecKind.Range } spec) + var spec = item.Version!; + if (spec.Min.CompareTo(spec.Max!) > 0) { - if (spec.Min.CompareTo(spec.Max!) > 0) - { - diagnostics.Add((Severity.Warning, - $"Key '{key}', {item.Lifecycle}: Range has first version ({spec.Min.Major}.{spec.Min.Minor}) greater than last version ({spec.Max!.Major}.{spec.Max.Minor}).")); - } + diagnostics.Add((Severity.Warning, + $"Key '{key}', {item.Lifecycle}: Range has first version ({spec.Min.Major}.{spec.Min.Minor}) greater than last version ({spec.Max!.Major}.{spec.Max.Minor}).")); } } From 2613a137d4b33f9ee2135be34e4f43714d62154b Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 09:42:47 -0300 Subject: [PATCH 10/17] Fix warnings on docs-builder docs --- docs/syntax/_snippets/inline-level-applies-examples.md | 4 ++-- docs/testing/req.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/syntax/_snippets/inline-level-applies-examples.md b/docs/syntax/_snippets/inline-level-applies-examples.md index 58f38476c..1fedf4759 100644 --- a/docs/syntax/_snippets/inline-level-applies-examples.md +++ b/docs/syntax/_snippets/inline-level-applies-examples.md @@ -55,7 +55,7 @@ This example shows how to use directly a key from the second level of the `appli ::::{tab-item} Output - {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0` -- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0` +- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0` - {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0` :::: @@ -63,7 +63,7 @@ This example shows how to use directly a key from the second level of the `appli ::::{tab-item} Markdown ```markdown - {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0` -- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0` +- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0` - {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0` ``` :::: diff --git a/docs/testing/req.md b/docs/testing/req.md index 95907215f..8db88e205 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -9,7 +9,7 @@ mapped_pages: # Requirements ```{applies_to} -stack: preview 9.0, ga 9.1 +stack: preview =9.0, ga 9.1 ``` 1. Select **Create** to create a new policy, or select **Edit** {icon}`pencil` to open an existing policy. From d4cce986d167c0004ed5166a3f1b1eb11a0851f5 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 12:58:57 -0300 Subject: [PATCH 11/17] Include applicability table in req.md --- docs/testing/req.md | 74 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index 8db88e205..ef778ed3e 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -20,13 +20,9 @@ stack: preview =9.0, ga 9.1 This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0. -what - - To follow this tutorial you will need to install the following components: - - An installation of Elasticsearch, based on our hosted [Elastic Cloud](https://www.elastic.co/cloud) service (which includes a free trial period), or a self-hosted service that you run on your own computer. See the Install Elasticsearch section above for installation instructions. - A [Python](https://python.org) interpreter. Make sure it is a recent version, such as Python 3.8 or newer. @@ -38,3 +34,73 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen {applies_to}`ece: removed` + +## Applies To Badge Scenarios + +Below is a table of `applies_to` badge scenarios. + +### No version specified (serverless) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`serverless: ga` | ````markdown
{applies_to}`serverless: ga`
```` | +| {applies_to}`serverless: preview` | ````markdown
{applies_to}`serverless: preview`
```` | +| {applies_to}`serverless: beta` | ````markdown
{applies_to}`serverless: beta`
```` | +| {applies_to}`serverless: deprecated` | ````markdown
{applies_to}`serverless: deprecated`
```` | +| {applies_to}`serverless: removed` | ````markdown
{applies_to}`serverless: removed`
```` | + +### No version specified (stack) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`stack: ga` | ````markdown
{applies_to}`stack: ga`
```` | +| {applies_to}`stack: preview` | ````markdown
{applies_to}`stack: preview`
```` | +| {applies_to}`stack: beta` | ````markdown
{applies_to}`stack: beta`
```` | +| {applies_to}`stack: deprecated` | ````markdown
{applies_to}`stack: deprecated`
```` | +| {applies_to}`stack: removed` | ````markdown
{applies_to}`stack: removed`
```` | + +### Greater than or equal to (x.x+ / x.x) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`stack: ga 9.1` | ````markdown
{applies_to}`stack: ga 9.1`
```` | +| {applies_to}`stack: ga 9.1+` | ````markdown
{applies_to}`stack: ga 9.1+`
```` | +| {applies_to}`stack: preview 9.0+` | ````markdown
{applies_to}`stack: preview 9.0+`
```` | +| {applies_to}`stack: beta 9.1+` | ````markdown
{applies_to}`stack: beta 9.1+`
```` | +| {applies_to}`stack: deprecated 9.0+` | ````markdown
{applies_to}`stack: deprecated 9.0+`
```` | +| {applies_to}`stack: removed 9.0` | ````markdown
{applies_to}`stack: removed 9.0`
```` | + +### Range (x.x-y.y) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`stack: ga 9.0-9.2` | ````markdown
{applies_to}`stack: ga 9.0-9.2`
```` | +| {applies_to}`stack: preview 9.0-9.2` | ````markdown
{applies_to}`stack: preview 9.0-9.2`
```` | +| {applies_to}`stack: beta 9.0-9.1` | ````markdown
{applies_to}`stack: beta 9.0-9.1`
```` | +| {applies_to}`stack: deprecated 9.0-9.2` | ````markdown
{applies_to}`stack: deprecated 9.0-9.2`
```` | + +### Exact version (=x.x) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`stack: ga =9.1` | ````markdown
{applies_to}`stack: ga =9.1`
```` | +| {applies_to}`stack: preview =9.0` | ````markdown
{applies_to}`stack: preview =9.0`
```` | +| {applies_to}`stack: beta =9.1` | ````markdown
{applies_to}`stack: beta =9.1`
```` | +| {applies_to}`stack: deprecated =9.0` | ````markdown
{applies_to}`stack: deprecated =9.0`
```` | +| {applies_to}`stack: removed =9.0` | ````markdown
{applies_to}`stack: removed =9.0`
```` | + +### Multiple lifecycles + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`stack: ga 9.2+, beta 9.0-9.1` | ````markdown
{applies_to}`stack: ga 9.2+, beta 9.0-9.1`
```` | +| {applies_to}`stack: ga 9.2+, preview 9.0-9.1` | ````markdown
{applies_to}`stack: ga 9.2+, preview 9.0-9.1`
```` | + +### Deployment types + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`ece: ga 9.0+` | ````markdown
{applies_to}`ece: ga 9.0+`
```` | +| {applies_to}`eck: preview 9.1+` | ````markdown
{applies_to}`eck: preview 9.1+`
```` | +| {applies_to}`ece: deprecated 6.7+` | ````markdown
{applies_to}`ece: deprecated 6.7+`
```` | +| {applies_to}`ece: removed` | ````markdown
{applies_to}`ece: removed`
```` | From 043c470aff54a16ce0fe144d4853847634c10fcd Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 13:10:48 -0300 Subject: [PATCH 12/17] Fix markdown formatting --- docs/testing/req.md | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index ef778ed3e..9ebb6e4ff 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -43,64 +43,64 @@ Below is a table of `applies_to` badge scenarios. | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`serverless: ga` | ````markdown
{applies_to}`serverless: ga`
```` | -| {applies_to}`serverless: preview` | ````markdown
{applies_to}`serverless: preview`
```` | -| {applies_to}`serverless: beta` | ````markdown
{applies_to}`serverless: beta`
```` | -| {applies_to}`serverless: deprecated` | ````markdown
{applies_to}`serverless: deprecated`
```` | -| {applies_to}`serverless: removed` | ````markdown
{applies_to}`serverless: removed`
```` | +| {applies_to}`serverless: ga` | `` {applies_to}`serverless: ga` `` | +| {applies_to}`serverless: preview` | `` {applies_to}`serverless: preview` `` | +| {applies_to}`serverless: beta` | `` {applies_to}`serverless: beta` `` | +| {applies_to}`serverless: deprecated` | `` {applies_to}`serverless: deprecated` `` | +| {applies_to}`serverless: removed` | `` {applies_to}`serverless: removed` `` | ### No version specified (stack) | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`stack: ga` | ````markdown
{applies_to}`stack: ga`
```` | -| {applies_to}`stack: preview` | ````markdown
{applies_to}`stack: preview`
```` | -| {applies_to}`stack: beta` | ````markdown
{applies_to}`stack: beta`
```` | -| {applies_to}`stack: deprecated` | ````markdown
{applies_to}`stack: deprecated`
```` | -| {applies_to}`stack: removed` | ````markdown
{applies_to}`stack: removed`
```` | +| {applies_to}`stack: ga` | `` {applies_to}`stack: ga` `` | +| {applies_to}`stack: preview` | `` {applies_to}`stack: preview` `` | +| {applies_to}`stack: beta` | `` {applies_to}`stack: beta` `` | +| {applies_to}`stack: deprecated` | `` {applies_to}`stack: deprecated` `` | +| {applies_to}`stack: removed` | `` {applies_to}`stack: removed` `` | ### Greater than or equal to (x.x+ / x.x) | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`stack: ga 9.1` | ````markdown
{applies_to}`stack: ga 9.1`
```` | -| {applies_to}`stack: ga 9.1+` | ````markdown
{applies_to}`stack: ga 9.1+`
```` | -| {applies_to}`stack: preview 9.0+` | ````markdown
{applies_to}`stack: preview 9.0+`
```` | -| {applies_to}`stack: beta 9.1+` | ````markdown
{applies_to}`stack: beta 9.1+`
```` | -| {applies_to}`stack: deprecated 9.0+` | ````markdown
{applies_to}`stack: deprecated 9.0+`
```` | -| {applies_to}`stack: removed 9.0` | ````markdown
{applies_to}`stack: removed 9.0`
```` | +| {applies_to}`stack: ga 9.1` | `` {applies_to}`stack: ga 9.1` `` | +| {applies_to}`stack: ga 9.1+` | `` {applies_to}`stack: ga 9.1+` `` | +| {applies_to}`stack: preview 9.0+` | `` {applies_to}`stack: preview 9.0+` `` | +| {applies_to}`stack: beta 9.1+` | `` {applies_to}`stack: beta 9.1+` `` | +| {applies_to}`stack: deprecated 9.0+` | `` {applies_to}`stack: deprecated 9.0+` `` | +| {applies_to}`stack: removed 9.0` | `` {applies_to}`stack: removed 9.0` `` | ### Range (x.x-y.y) | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`stack: ga 9.0-9.2` | ````markdown
{applies_to}`stack: ga 9.0-9.2`
```` | -| {applies_to}`stack: preview 9.0-9.2` | ````markdown
{applies_to}`stack: preview 9.0-9.2`
```` | -| {applies_to}`stack: beta 9.0-9.1` | ````markdown
{applies_to}`stack: beta 9.0-9.1`
```` | -| {applies_to}`stack: deprecated 9.0-9.2` | ````markdown
{applies_to}`stack: deprecated 9.0-9.2`
```` | +| {applies_to}`stack: ga 9.0-9.2` | `` {applies_to}`stack: ga 9.0-9.2` `` | +| {applies_to}`stack: preview 9.0-9.2` | `` {applies_to}`stack: preview 9.0-9.2` `` | +| {applies_to}`stack: beta 9.0-9.1` | `` {applies_to}`stack: beta 9.0-9.1` `` | +| {applies_to}`stack: deprecated 9.0-9.2` | `` {applies_to}`stack: deprecated 9.0-9.2` `` | ### Exact version (=x.x) | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`stack: ga =9.1` | ````markdown
{applies_to}`stack: ga =9.1`
```` | -| {applies_to}`stack: preview =9.0` | ````markdown
{applies_to}`stack: preview =9.0`
```` | -| {applies_to}`stack: beta =9.1` | ````markdown
{applies_to}`stack: beta =9.1`
```` | -| {applies_to}`stack: deprecated =9.0` | ````markdown
{applies_to}`stack: deprecated =9.0`
```` | -| {applies_to}`stack: removed =9.0` | ````markdown
{applies_to}`stack: removed =9.0`
```` | +| {applies_to}`stack: ga =9.1` | `` {applies_to}`stack: ga =9.1` `` | +| {applies_to}`stack: preview =9.0` | `` {applies_to}`stack: preview =9.0` `` | +| {applies_to}`stack: beta =9.1` | `` {applies_to}`stack: beta =9.1` `` | +| {applies_to}`stack: deprecated =9.0` | `` {applies_to}`stack: deprecated =9.0` `` | +| {applies_to}`stack: removed =9.0` | `` {applies_to}`stack: removed =9.0` `` | ### Multiple lifecycles | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`stack: ga 9.2+, beta 9.0-9.1` | ````markdown
{applies_to}`stack: ga 9.2+, beta 9.0-9.1`
```` | -| {applies_to}`stack: ga 9.2+, preview 9.0-9.1` | ````markdown
{applies_to}`stack: ga 9.2+, preview 9.0-9.1`
```` | +| {applies_to}`stack: ga 9.2+, beta 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, beta 9.0-9.1` `` | +| {applies_to}`stack: ga 9.2+, preview 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, preview 9.0-9.1` `` | ### Deployment types | Badge | Raw Markdown | |-------|--------------| -| {applies_to}`ece: ga 9.0+` | ````markdown
{applies_to}`ece: ga 9.0+`
```` | -| {applies_to}`eck: preview 9.1+` | ````markdown
{applies_to}`eck: preview 9.1+`
```` | -| {applies_to}`ece: deprecated 6.7+` | ````markdown
{applies_to}`ece: deprecated 6.7+`
```` | -| {applies_to}`ece: removed` | ````markdown
{applies_to}`ece: removed`
```` | +| {applies_to}`ece: ga 9.0+` | `` {applies_to}`ece: ga 9.0+` `` | +| {applies_to}`eck: preview 9.1+` | `` {applies_to}`eck: preview 9.1+` `` | +| {applies_to}`ece: deprecated 6.7+` | `` {applies_to}`ece: deprecated 6.7+` `` | +| {applies_to}`ece: removed` | `` {applies_to}`ece: removed` `` | From 11580bdcb5372c2f21eb9fa1e43c09df8544810d Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 8 Dec 2025 14:27:07 -0300 Subject: [PATCH 13/17] Products with versions should show their base versions in badges without a specified version --- docs/testing/req.md | 20 +++++++++++++ .../AppliesTo/ApplicableToYamlConverter.cs | 4 +-- src/Elastic.Documentation/SemVersion.cs | 2 +- src/Elastic.Documentation/VersionSpec.cs | 2 +- .../Myst/Components/ApplicabilityRenderer.cs | 29 +++++++------------ .../Applicability/ApplicableToComponent.fs | 12 +++++++- tests/authoring/Blocks/Admonitions.fs | 27 ++++++++++++----- 7 files changed, 64 insertions(+), 32 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index 9ebb6e4ff..6e5b83337 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -59,6 +59,17 @@ Below is a table of `applies_to` badge scenarios. | {applies_to}`stack: deprecated` | `` {applies_to}`stack: deprecated` `` | | {applies_to}`stack: removed` | `` {applies_to}`stack: removed` `` | +### No version specified (product) + +| Badge | Raw Markdown | +|-------|--------------| +| {applies_to}`apm_agent_python: ga` | `` {applies_to}`apm_agent_python: ga` `` | +| {applies_to}`apm_agent_python: preview` | `` {applies_to}`apm_agent_python: preview` `` | +| {applies_to}`apm_agent_python: beta` | `` {applies_to}`apm_agent_python: beta` `` | +| {applies_to}`apm_agent_python: deprecated` | `` {applies_to}`apm_agent_python: deprecated` `` | +| {applies_to}`apm_agent_python: removed` | `` {applies_to}`apm_agent_python: removed` `` | + + ### Greater than or equal to (x.x+ / x.x) | Badge | Raw Markdown | @@ -69,6 +80,13 @@ Below is a table of `applies_to` badge scenarios. | {applies_to}`stack: beta 9.1+` | `` {applies_to}`stack: beta 9.1+` `` | | {applies_to}`stack: deprecated 9.0+` | `` {applies_to}`stack: deprecated 9.0+` `` | | {applies_to}`stack: removed 9.0` | `` {applies_to}`stack: removed 9.0` `` | +| {applies_to}`apm_agent_python: ga 6.0` | `` {applies_to}`apm_agent_python: ga 6.0` `` | +| {applies_to}`apm_agent_python: ga 6.5+` | `` {applies_to}`apm_agent_python: ga 6.5+` `` | +| {applies_to}`apm_agent_python: preview 6.24+` | `` {applies_to}`apm_agent_python: preview 6.24+` `` | +| {applies_to}`apm_agent_python: beta 6.1+` | `` {applies_to}`apm_agent_python: beta 6.1+` `` | +| {applies_to}`apm_agent_python: deprecated 6.0+` | `` {applies_to}`apm_agent_python: deprecated 6.0+` `` | +| {applies_to}`apm_agent_python: removed 6.0` | `` {applies_to}`apm_agent_python: removed 6.0` `` | + ### Range (x.x-y.y) @@ -78,6 +96,7 @@ Below is a table of `applies_to` badge scenarios. | {applies_to}`stack: preview 9.0-9.2` | `` {applies_to}`stack: preview 9.0-9.2` `` | | {applies_to}`stack: beta 9.0-9.1` | `` {applies_to}`stack: beta 9.0-9.1` `` | | {applies_to}`stack: deprecated 9.0-9.2` | `` {applies_to}`stack: deprecated 9.0-9.2` `` | +| {applies_to}`apm_agent_python: ga 6.0-6.23` | `` {applies_to}`apm_agent_python: ga 6.0-6.23` `` | ### Exact version (=x.x) @@ -88,6 +107,7 @@ Below is a table of `applies_to` badge scenarios. | {applies_to}`stack: beta =9.1` | `` {applies_to}`stack: beta =9.1` `` | | {applies_to}`stack: deprecated =9.0` | `` {applies_to}`stack: deprecated =9.0` `` | | {applies_to}`stack: removed =9.0` | `` {applies_to}`stack: removed =9.0` `` | +| {applies_to}`apm_agent_python: ga =6.20` | `` {applies_to}`apm_agent_python: ga =6.20` `` | ### Multiple lifecycles diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index 3db7a6247..d86e06541 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -333,8 +333,8 @@ private static bool CheckVersionOverlap(VersionSpec v1, VersionSpec v2, out stri var (v1Min, v1Max) = GetEffectiveRange(v1); var (v2Min, v2Max) = GetEffectiveRange(v2); - var overlaps = v1Min.CompareTo(v2Max ?? new SemVersion(99999, 99999, 99999)) <= 0 && - v2Min.CompareTo(v1Max ?? new SemVersion(99999, 99999, 99999)) <= 0; + var overlaps = v1Min.CompareTo(v2Max ?? new SemVersion(99999, 0, 0)) <= 0 && + v2Min.CompareTo(v1Max ?? new SemVersion(99999, 0, 0)) <= 0; if (overlaps) message = $"Version ranges overlap."; diff --git a/src/Elastic.Documentation/SemVersion.cs b/src/Elastic.Documentation/SemVersion.cs index 8ee4f2cec..e1cb736da 100644 --- a/src/Elastic.Documentation/SemVersion.cs +++ b/src/Elastic.Documentation/SemVersion.cs @@ -8,7 +8,7 @@ namespace Elastic.Documentation; -public class AllVersions() : SemVersion(99999, 99999, 99999) +public class AllVersions() : SemVersion(99999, 0, 0) { public static AllVersions Instance { get; } = new(); } diff --git a/src/Elastic.Documentation/VersionSpec.cs b/src/Elastic.Documentation/VersionSpec.cs index 2cfb66774..abf80d168 100644 --- a/src/Elastic.Documentation/VersionSpec.cs +++ b/src/Elastic.Documentation/VersionSpec.cs @@ -8,7 +8,7 @@ namespace Elastic.Documentation; public sealed class AllVersionsSpec : VersionSpec { - private static readonly SemVersion AllVersionsSemVersion = new(9999, 9999, 9999); + private static readonly SemVersion AllVersionsSemVersion = new(99999, 0, 0); private AllVersionsSpec() : base(AllVersionsSemVersion, null, VersionSpecKind.GreaterThanOrEqual) { diff --git a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs index 6895236fb..f223ae7b8 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs +++ b/src/Elastic.Markdown/Myst/Components/ApplicabilityRenderer.cs @@ -36,21 +36,14 @@ public ApplicabilityRenderData RenderApplicability( var showLifecycle = applicability.Lifecycle != ProductLifecycle.GenerallyAvailable && string.IsNullOrEmpty(badgeLifecycleText); // Determine if we should show version based on VersionSpec - var showVersion = false; - var versionDisplay = string.Empty; + var versionDisplay = GetBadgeVersionText(applicability.Version, versioningSystem); + var showVersion = !string.IsNullOrEmpty(versionDisplay); - if (applicability.Version is not null && applicability.Version != AllVersionsSpec.Instance) + // Special handling for Removed lifecycle - don't show + suffix + if (applicability is { Lifecycle: ProductLifecycle.Removed, Version.Kind: VersionSpecKind.GreaterThanOrEqual } && + !string.IsNullOrEmpty(versionDisplay)) { - versionDisplay = GetBadgeVersionText(applicability.Version, versioningSystem); - showVersion = !string.IsNullOrEmpty(versionDisplay); - - // Special handling for Removed lifecycle - don't show + suffix - if (applicability.Lifecycle == ProductLifecycle.Removed && - applicability.Version.Kind == VersionSpecKind.GreaterThanOrEqual && - !string.IsNullOrEmpty(versionDisplay)) - { - versionDisplay = versionDisplay.TrimEnd('+'); - } + versionDisplay = versionDisplay.TrimEnd('+'); } return new ApplicabilityRenderData( @@ -261,13 +254,11 @@ private static string BuildBadgeLifecycleText( private static string GetBadgeVersionText(VersionSpec? versionSpec, VersioningSystem versioningSystem) { // When no version is specified, check if we should show the base version - if (versionSpec is null || versionSpec == AllVersionsSpec.Instance) + if (versionSpec is null) { - if (versioningSystem.Base.Major != AllVersionsSpec.Instance.Min.Major) - return $"{versioningSystem.Base.Major}.{versioningSystem.Base.Minor}+"; - - // Otherwise, this is an unversioned product, show no version - return string.Empty; + return versioningSystem.Base != AllVersionsSpec.Instance.Min + ? $"{versioningSystem.Base.Major}.{versioningSystem.Base.Minor}+" + : string.Empty; // Otherwise, this is an unversioned product, show no version } var kind = versionSpec.Kind; diff --git a/tests/authoring/Applicability/ApplicableToComponent.fs b/tests/authoring/Applicability/ApplicableToComponent.fs index af138871e..25a9349fa 100644 --- a/tests/authoring/Applicability/ApplicableToComponent.fs +++ b/tests/authoring/Applicability/ApplicableToComponent.fs @@ -146,7 +146,11 @@ stack: ga If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page."> Stack - + + + + 8.0+ +

@@ -484,6 +488,9 @@ stack: unavailable Unavailable + + 8.0+ +
@@ -503,6 +510,9 @@ product: ga + + 8.0+ +

diff --git a/tests/authoring/Blocks/Admonitions.fs b/tests/authoring/Blocks/Admonitions.fs index 324f3c5b4..85e738a23 100644 --- a/tests/authoring/Blocks/Admonitions.fs +++ b/tests/authoring/Blocks/Admonitions.fs @@ -68,10 +68,14 @@ This is a custom admonition with applies_to information. If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page."> Stack - - -
-
+ + + + 8.0+ + + +
+
@@ -84,10 +88,14 @@ If this functionality is unavailable or behaves differently when deployed on ECH Serverless - - - - + + + + 8.0+ + + + +
@@ -105,6 +113,9 @@ This functionality may be changed or removed in a future release. Elastic will w Preview + + 8.0+ + From 56af075b0957c222508e036fac0eed4919fa63a3 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Thu, 11 Dec 2025 13:52:28 -0300 Subject: [PATCH 14/17] Introduce implicit semantics for multiple lifecycles --- .../AppliesTo/Applicability.cs | 75 +++++++++++++++++++ src/Elastic.Documentation/VersionSpec.cs | 15 ++++ .../Applicability/ApplicableToComponent.fs | 5 +- .../Applicability/AppliesToFrontMatter.fs | 5 +- tests/authoring/Inline/AppliesToRole.fs | 15 ++-- 5 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/Applicability.cs b/src/Elastic.Documentation/AppliesTo/Applicability.cs index 2d5abbff9..0c8ae9d4a 100644 --- a/src/Elastic.Documentation/AppliesTo/Applicability.cs +++ b/src/Elastic.Documentation/AppliesTo/Applicability.cs @@ -37,6 +37,9 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics if (applications.Count == 0) return false; + // Infer version semantics when multiple items have GreaterThanOrEqual versions + applications = InferVersionSemantics(applications); + // Sort by version in descending order (the highest version first) // Items without versions (AllVersionsSpec.Instance) are sorted last var sortedApplications = applications.OrderDescending().ToArray(); @@ -44,6 +47,78 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics return true; } + /// + /// Infers versioning semantics according to the following ruleset: + /// - The highest version keeps GreaterThanOrEqual (e.g., 9.4+) + /// - Lower versions become Exact if consecutive, or Range to fill gaps + /// - This rule only applies when all versions are at minor level (patch = 0). + /// + private static List InferVersionSemantics(List applications) + { + // Get items with actual GreaterThanOrEqual versions (not AllVersionsSpec, not null, not ranges/exact) + var gteItems = applications + .Where(a => a.Version is { Kind: VersionSpecKind.GreaterThanOrEqual } + && a.Version != AllVersionsSpec.Instance) + .ToList(); + + // If 0 or 1 GTE items, no inference needed + if (gteItems.Count <= 1) + return applications; + + // Only apply inference when all entries are on patch version 0 + if (gteItems.Any(a => a.Version!.Min.Patch != 0)) + return applications; + + // Sort GTE items by version ascending to process from lowest to highest + var sortedGteVersions = gteItems + .Select(a => a.Version!.Min) + .Distinct() + .OrderBy(v => v) + .ToList(); + + if (sortedGteVersions.Count <= 1) + return applications; + + var versionMapping = new Dictionary(); + + for (var i = 0; i < sortedGteVersions.Count; i++) + { + var currentVersion = sortedGteVersions[i]; + + if (i == sortedGteVersions.Count - 1) + { + // Highest version keeps GreaterThanOrEqual + versionMapping[currentVersion] = VersionSpec.GreaterThanOrEqual(currentVersion); + } + else + { + var nextVersion = sortedGteVersions[i + 1]; + + // Define an Exact or Range VersionSpec according to the numeric difference between lifecycles + if (currentVersion.Major == nextVersion.Major + && nextVersion.Minor == currentVersion.Minor + 1) + versionMapping[currentVersion] = VersionSpec.Exact(currentVersion); + else + { + var rangeEnd = new SemVersion(nextVersion.Major, nextVersion.Minor - 1, 0); + versionMapping[currentVersion] = VersionSpec.Range(currentVersion, rangeEnd); + } + } + } + + // Apply the mapping to create updated applications + return applications.Select(a => + { + if (a.Version is null || a.Version == AllVersionsSpec.Instance || a is not { Version.Kind: VersionSpecKind.GreaterThanOrEqual }) + return a; + + if (versionMapping.TryGetValue(a.Version.Min, out var newSpec)) + return a with { Version = newSpec }; + + return a; + }).ToList(); + } + public virtual bool Equals(AppliesCollection? other) { if ((object)this == other) diff --git a/src/Elastic.Documentation/VersionSpec.cs b/src/Elastic.Documentation/VersionSpec.cs index abf80d168..34558ef65 100644 --- a/src/Elastic.Documentation/VersionSpec.cs +++ b/src/Elastic.Documentation/VersionSpec.cs @@ -56,6 +56,21 @@ protected VersionSpec(SemVersion min, SemVersion? max, VersionSpecKind kind) Kind = kind; } + /// + /// Creates an Exact version spec from a SemVersion. + /// + public static VersionSpec Exact(SemVersion version) => new(version, null, VersionSpecKind.Exact); + + /// + /// Creates a Range version spec from two SemVersions. + /// + public static VersionSpec Range(SemVersion min, SemVersion max) => new(min, max, VersionSpecKind.Range); + + /// + /// Creates a GreaterThanOrEqual version spec from a SemVersion. + /// + public static VersionSpec GreaterThanOrEqual(SemVersion min) => new(min, null, VersionSpecKind.GreaterThanOrEqual); + /// /// Tries to parse a version specification string. /// Supports: x.x, x.x+, x.x.x, x.x.x+ (gte), x.x-y.y (range), =x.x (exact) diff --git a/tests/authoring/Applicability/ApplicableToComponent.fs b/tests/authoring/Applicability/ApplicableToComponent.fs index 25a9349fa..5619211fd 100644 --- a/tests/authoring/Applicability/ApplicableToComponent.fs +++ b/tests/authoring/Applicability/ApplicableToComponent.fs @@ -405,7 +405,7 @@ stack: ga 8.8.0, preview 8.1.0 """ [] - let ``renders GA planned when preview exists alongside GA`` () = + let ``renders Preview when GA and Preview both exist for an unreleased entry`` () = markdown |> convertsToHtml """

GA - 8.0+ + 8.0 diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs index 2d1f02b95..95483372e 100644 --- a/tests/authoring/Applicability/AppliesToFrontMatter.fs +++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs @@ -163,10 +163,7 @@ applies_to: [] let ``apply matches expected`` () = markdown |> appliesTo (ApplicableTo( - Product=AppliesCollection([ - Applicability.op_Explicit "removed 9.7"; - Applicability.op_Explicit "preview 9.5" - ] |> Array.ofList) + Product=AppliesCollection.op_Explicit "removed 9.7, preview 9.5" )) type ``lenient to defining types at top level`` () = diff --git a/tests/authoring/Inline/AppliesToRole.fs b/tests/authoring/Inline/AppliesToRole.fs index a4f60ba3d..e6a3f09a1 100644 --- a/tests/authoring/Inline/AppliesToRole.fs +++ b/tests/authoring/Inline/AppliesToRole.fs @@ -125,7 +125,7 @@ type ``parses multiple applies_to in one line`` () = type ``render 'GA Planned' if preview exists alongside ga`` () = static let markdown = Setup.Markdown """ -This is an inline {applies_to}`stack: preview 9.0, ga 9.1` element. +This is an inline {applies_to}`stack: preview 8.0, ga 8.1` element. """ [] @@ -133,7 +133,7 @@ This is an inline {applies_to}`stack: preview 9.0, ga 9.1` element. let directives = markdown |> converts "index.md" |> parses test <@ directives.Length = 1 @> directives |> appliesToDirective (ApplicableTo( - Stack=AppliesCollection.op_Explicit "ga 9.1, preview 9.0" + Stack=AppliesCollection.op_Explicit "ga 8.1, preview 8.0" )) [] @@ -141,17 +141,20 @@ This is an inline {applies_to}`stack: preview 9.0, ga 9.1` element. markdown |> convertsToHtml """

This is an inline - +If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page.

"> Stack Preview + + 8.0 + From 7d0f792ba2a445e2204c4ab145260fb716a91528 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Thu, 11 Dec 2025 14:22:16 -0300 Subject: [PATCH 15/17] Add more examples in docs --- docs/_snippets/applies_to-version.md | 65 +++++- .../_snippets/multiple-lifecycle-states.md | 42 +++- docs/syntax/_snippets/versioned-lifecycle.md | 28 ++- docs/testing/req.md | 185 +++++++++--------- 4 files changed, 211 insertions(+), 109 deletions(-) diff --git a/docs/_snippets/applies_to-version.md b/docs/_snippets/applies_to-version.md index e5e70f63b..ef6ed62be 100644 --- a/docs/_snippets/applies_to-version.md +++ b/docs/_snippets/applies_to-version.md @@ -1,15 +1,64 @@ `applies_to` accepts the following version formats: -* **Greater than or equal to**: `x.x+`, `x.x`, `x.x.x+`, `x.x.x` (default behavior when no operator specified) -* **Range (inclusive)**: `x.x-y.y`, `x.x.x-y.y.y`, `x.x-y.y.y`, `x.x.x-y.y` -* **Exact version**: `=x.x`, `=x.x.x` +### Version specifiers -**Version Display:** +You can use version specifiers to precisely control how versions are interpreted: -- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of the format used in source files. -- Each version represents the **latest patch** of that minor version (e.g., `9.1` means 9.1.0, 9.1.1, 9.1.6, etc.). -- The `+` symbol indicates "this version and later" (e.g., `9.1+` means 9.1.0 and all subsequent releases). -- Ranges show both versions (e.g., `9.0-9.2`) when both are released, or convert to `+` format if the end version is unreleased. +| Specifier | Syntax | Description | Example | +|-----------|--------|-------------|---------| +| Greater than or equal (default) | `x.x` `x.x+` `x.x.x` `x.x.x+` | Feature available from this version onwards | `ga 9.2+` or `ga 9.2` | +| Range (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | Feature available only in this version range | `beta 9.0-9.1` | +| Exact version | `=x.x` `=x.x.x` | Feature available only in this specific version | `preview =9.0` | + +Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor` format in badges. + +:::{note} +The `+` suffix is optional for greater-than-or-equal syntax. Both `ga 9.2` and `ga 9.2+` have the same meaning. +::: + +### Examples + +```yaml +# Greater than or equal (feature available from 9.2 onwards) +stack: ga 9.2 +stack: ga 9.2+ + +# Range (feature was in beta from 9.0 to 9.1, then became GA) +stack: ga 9.2+, beta 9.0-9.1 + +# Exact version (feature was in preview only in 9.0) +stack: ga 9.1+, preview =9.0 +``` + +### Implicit version inference for multiple lifecycles {#implicit-version-inference} + +When you specify multiple lifecycles with simple versions (without explicit specifiers), the system automatically infers the version ranges: + +**Input:** +```yaml +stack: preview 9.0, alpha 9.1, beta 9.2, ga 9.4 +``` + +**Interpreted as:** +```yaml +stack: preview =9.0, alpha =9.1, beta 9.2-9.3, ga 9.4+ +``` + +The inference rules are: +1. **Consecutive versions**: If a lifecycle is immediately followed by another in the next minor version, it's treated as an **exact version** (`=x.x`). +2. **Non-consecutive versions**: If there's a gap between one lifecycle's version and the next lifecycle's version, it becomes a **range** from the start version to one version before the next lifecycle. +3. **Last lifecycle**: The highest versioned lifecycle is always treated as **greater-than-or-equal** (`x.x+`). + +This makes it easy to document features that evolve through multiple lifecycle stages. For example, a feature that goes through preview → beta → GA can be written simply as: + +```yaml +stack: preview 9.0, beta 9.1, ga 9.3 +``` + +Which is automatically interpreted as: +```yaml +stack: preview =9.0, beta 9.1-9.2, ga 9.3+ +``` :::{note} **Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 9.1, beta 9.0, preview 8.18` will be displayed with the highest priority lifecycle and version first. Items without versions are sorted last. diff --git a/docs/syntax/_snippets/multiple-lifecycle-states.md b/docs/syntax/_snippets/multiple-lifecycle-states.md index bb0bedf40..8c9dcd069 100644 --- a/docs/syntax/_snippets/multiple-lifecycle-states.md +++ b/docs/syntax/_snippets/multiple-lifecycle-states.md @@ -1,12 +1,35 @@ -`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions. For example: +`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions. -* A feature is added in 9.1 as tech preview and becomes GA in 9.4: +When you specify multiple lifecycles with simple versions, the system automatically infers whether each version represents an exact version, a range, or an open-ended range. Refer to [Implicit version inference](/_snippets/applies_to-version.md#implicit-version-inference) for details. + +### Examples + +* A feature is added in 9.0 as tech preview and becomes GA in 9.1: + + ```yml + applies_to: + stack: preview 9.0, ga 9.1 + ``` + + The preview is automatically interpreted as `=9.0` (exact), and GA as `9.1+` (open-ended). + +* A feature goes through multiple stages before becoming GA: + + ```yml + applies_to: + stack: preview 9.0, beta 9.1, ga 9.3 + ``` + + Interpreted as: `preview =9.0`, `beta 9.1-9.2`, `ga 9.3+` + +* A feature is unavailable for one version, beta for another, preview for a range, then GA: ```yml applies_to: - stack: preview 9.1, ga 9.4 + stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4 ``` + Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3`, `ga 9.4+` * A feature is deprecated in ECE 4.0 and is removed in 4.8. At the same time, it has already been removed in {{ech}}: @@ -15,4 +38,17 @@ deployment: ece: deprecated 4.0, removed 4.8 ess: removed + ``` + + The deprecated lifecycle is interpreted as `4.0-4.7` (range until removal). + +* Use explicit specifiers when you need precise control: + + ```yml + applies_to: + # Explicit exact version + stack: preview =9.0, ga 9.1+ + + # Explicit range + stack: beta 9.0-9.1, ga 9.2+ ``` \ No newline at end of file diff --git a/docs/syntax/_snippets/versioned-lifecycle.md b/docs/syntax/_snippets/versioned-lifecycle.md index ae6af6fee..cfe372e69 100644 --- a/docs/syntax/_snippets/versioned-lifecycle.md +++ b/docs/syntax/_snippets/versioned-lifecycle.md @@ -7,6 +7,8 @@ --- ``` + This means the feature is available from version 9.3 onwards (equivalent to `ga 9.3+`). + * When a change is introduced as preview or beta, use `preview` or `beta` as value for the corresponding key within the `applies_to`: ``` @@ -16,6 +18,28 @@ --- ``` +* When a feature is available only in a specific version range, use the range syntax: + + ``` + --- + applies_to: + stack: beta 9.0-9.1, ga 9.2 + --- + ``` + + This means the feature was in beta from 9.0 to 9.1, then became GA in 9.2+. + +* When a feature was in a specific lifecycle for exactly one version, use the exact syntax: + + ``` + --- + applies_to: + stack: preview =9.0, ga 9.1 + --- + ``` + + This means the feature was in preview only in 9.0, then became GA in 9.1+. + * When a change introduces a deprecation, use `deprecated` as value for the corresponding key within the `applies_to`: ``` @@ -33,4 +57,6 @@ applies_to: stack: deprecated 9.1, removed 9.4 --- - ``` \ No newline at end of file + ``` + + With the implicit version inference, this is interpreted as `deprecated 9.1-9.3, removed 9.4+`. \ No newline at end of file diff --git a/docs/testing/req.md b/docs/testing/req.md index 6e5b83337..454255dd9 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -8,20 +8,102 @@ mapped_pages: --- # Requirements +This page demonstrates various `applies_to` version syntax examples. + +## Version specifier examples + +### Greater than or equal (default) + +```{applies_to} +stack: ga 9.0 +``` + +This is equivalent to `ga 9.0+` — the feature is available from version 9.0 onwards. + +### Explicit range + +```{applies_to} +stack: beta 9.0-9.1, ga 9.2 +``` + +The feature was in beta from 9.0 to 9.1 (inclusive), then became GA in 9.2+. + +### Exact version + ```{applies_to} stack: preview =9.0, ga 9.1 ``` -1. Select **Create** to create a new policy, or select **Edit** {icon}`pencil` to open an existing policy. -1. Select **Create** to create a new policy, or select **Edit** {icon}`logo_vulnerability_management` to open an existing policy. +The feature was in preview only in version 9.0 (exactly), then became GA in 9.1+. +## Implicit version inference examples -{applies_to}`stack: preview 9.0` This tutorial is based on Elasticsearch 9.0. -This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0. -This tutorial is based on Elasticsearch 9.0. +### Simple two-stage lifecycle -To follow this tutorial you will need to install the following components: +```{applies_to} +stack: preview 9.0, ga 9.1 +``` + +Interpreted as: `preview =9.0` (exact), `ga 9.1+` (open-ended). + +### Multi-stage lifecycle with consecutive versions + +```{applies_to} +stack: preview 9.0, beta 9.1, ga 9.2 +``` + +Interpreted as: `preview =9.0`, `beta =9.1`, `ga 9.2+`. + +### Multi-stage lifecycle with gaps + +```{applies_to} +stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4 +``` +Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3` (range to fill the gap), `ga 9.4+`. + +### Four stages with varying gaps + +```{applies_to} +stack: preview 9.0, beta 9.2, ga 9.5 +``` + +Interpreted as: `preview 9.0-9.1`, `beta 9.2-9.4`, `ga 9.5+`. + +## Inline examples + +{applies_to}`stack: preview 9.0` This feature is in preview in 9.0. + +{applies_to}`stack: beta 9.0-9.1` This feature was in beta from 9.0 to 9.1. + +{applies_to}`stack: ga 9.2+` This feature is generally available since 9.2. + +{applies_to}`stack: preview =9.0` This feature was in preview only in 9.0 (exact). + +## Deprecation and removal examples + +```{applies_to} +stack: deprecated 9.2, removed 9.5 +``` + +Interpreted as: `deprecated 9.2-9.4`, `removed 9.5+`. + +{applies_to}`stack: deprecated 9.0` This feature is deprecated starting in 9.0. + +{applies_to}`stack: removed 9.2` This feature was removed in 9.2. + +## Mixed deployment examples + +```{applies_to} +stack: ga 9.0 +deployment: + ece: ga 4.0 + eck: beta 3.0, ga 3.1 +``` + +## Additional content + +To follow this tutorial you will need to install the following components: - An installation of Elasticsearch, based on our hosted [Elastic Cloud](https://www.elastic.co/cloud) service (which includes a free trial period), or a self-hosted service that you run on your own computer. See the Install Elasticsearch section above for installation instructions. - A [Python](https://python.org) interpreter. Make sure it is a recent version, such as Python 3.8 or newer. @@ -32,95 +114,4 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen - The [Flask](https://flask.palletsprojects.com/) web framework for Python. - The command prompt or terminal application in your operating system. - {applies_to}`ece: removed` - -## Applies To Badge Scenarios - -Below is a table of `applies_to` badge scenarios. - -### No version specified (serverless) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`serverless: ga` | `` {applies_to}`serverless: ga` `` | -| {applies_to}`serverless: preview` | `` {applies_to}`serverless: preview` `` | -| {applies_to}`serverless: beta` | `` {applies_to}`serverless: beta` `` | -| {applies_to}`serverless: deprecated` | `` {applies_to}`serverless: deprecated` `` | -| {applies_to}`serverless: removed` | `` {applies_to}`serverless: removed` `` | - -### No version specified (stack) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`stack: ga` | `` {applies_to}`stack: ga` `` | -| {applies_to}`stack: preview` | `` {applies_to}`stack: preview` `` | -| {applies_to}`stack: beta` | `` {applies_to}`stack: beta` `` | -| {applies_to}`stack: deprecated` | `` {applies_to}`stack: deprecated` `` | -| {applies_to}`stack: removed` | `` {applies_to}`stack: removed` `` | - -### No version specified (product) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`apm_agent_python: ga` | `` {applies_to}`apm_agent_python: ga` `` | -| {applies_to}`apm_agent_python: preview` | `` {applies_to}`apm_agent_python: preview` `` | -| {applies_to}`apm_agent_python: beta` | `` {applies_to}`apm_agent_python: beta` `` | -| {applies_to}`apm_agent_python: deprecated` | `` {applies_to}`apm_agent_python: deprecated` `` | -| {applies_to}`apm_agent_python: removed` | `` {applies_to}`apm_agent_python: removed` `` | - - -### Greater than or equal to (x.x+ / x.x) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`stack: ga 9.1` | `` {applies_to}`stack: ga 9.1` `` | -| {applies_to}`stack: ga 9.1+` | `` {applies_to}`stack: ga 9.1+` `` | -| {applies_to}`stack: preview 9.0+` | `` {applies_to}`stack: preview 9.0+` `` | -| {applies_to}`stack: beta 9.1+` | `` {applies_to}`stack: beta 9.1+` `` | -| {applies_to}`stack: deprecated 9.0+` | `` {applies_to}`stack: deprecated 9.0+` `` | -| {applies_to}`stack: removed 9.0` | `` {applies_to}`stack: removed 9.0` `` | -| {applies_to}`apm_agent_python: ga 6.0` | `` {applies_to}`apm_agent_python: ga 6.0` `` | -| {applies_to}`apm_agent_python: ga 6.5+` | `` {applies_to}`apm_agent_python: ga 6.5+` `` | -| {applies_to}`apm_agent_python: preview 6.24+` | `` {applies_to}`apm_agent_python: preview 6.24+` `` | -| {applies_to}`apm_agent_python: beta 6.1+` | `` {applies_to}`apm_agent_python: beta 6.1+` `` | -| {applies_to}`apm_agent_python: deprecated 6.0+` | `` {applies_to}`apm_agent_python: deprecated 6.0+` `` | -| {applies_to}`apm_agent_python: removed 6.0` | `` {applies_to}`apm_agent_python: removed 6.0` `` | - - -### Range (x.x-y.y) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`stack: ga 9.0-9.2` | `` {applies_to}`stack: ga 9.0-9.2` `` | -| {applies_to}`stack: preview 9.0-9.2` | `` {applies_to}`stack: preview 9.0-9.2` `` | -| {applies_to}`stack: beta 9.0-9.1` | `` {applies_to}`stack: beta 9.0-9.1` `` | -| {applies_to}`stack: deprecated 9.0-9.2` | `` {applies_to}`stack: deprecated 9.0-9.2` `` | -| {applies_to}`apm_agent_python: ga 6.0-6.23` | `` {applies_to}`apm_agent_python: ga 6.0-6.23` `` | - -### Exact version (=x.x) - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`stack: ga =9.1` | `` {applies_to}`stack: ga =9.1` `` | -| {applies_to}`stack: preview =9.0` | `` {applies_to}`stack: preview =9.0` `` | -| {applies_to}`stack: beta =9.1` | `` {applies_to}`stack: beta =9.1` `` | -| {applies_to}`stack: deprecated =9.0` | `` {applies_to}`stack: deprecated =9.0` `` | -| {applies_to}`stack: removed =9.0` | `` {applies_to}`stack: removed =9.0` `` | -| {applies_to}`apm_agent_python: ga =6.20` | `` {applies_to}`apm_agent_python: ga =6.20` `` | - -### Multiple lifecycles - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`stack: ga 9.2+, beta 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, beta 9.0-9.1` `` | -| {applies_to}`stack: ga 9.2+, preview 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, preview 9.0-9.1` `` | - -### Deployment types - -| Badge | Raw Markdown | -|-------|--------------| -| {applies_to}`ece: ga 9.0+` | `` {applies_to}`ece: ga 9.0+` `` | -| {applies_to}`eck: preview 9.1+` | `` {applies_to}`eck: preview 9.1+` `` | -| {applies_to}`ece: deprecated 6.7+` | `` {applies_to}`ece: deprecated 6.7+` `` | -| {applies_to}`ece: removed` | `` {applies_to}`ece: removed` `` | From 13301d08daa58969b707b37bf98b8a3028d716cf Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Thu, 11 Dec 2025 14:25:03 -0300 Subject: [PATCH 16/17] Typo --- docs/testing/req.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index 454255dd9..b4f71f821 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -62,7 +62,7 @@ stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4 Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3` (range to fill the gap), `ga 9.4+`. -### Four stages with varying gaps +### Three stages with varying gaps ```{applies_to} stack: preview 9.0, beta 9.2, ga 9.5 From 7cd899b1802825e953b3322f9e7cce1a51b69f65 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Thu, 11 Dec 2025 14:29:05 -0300 Subject: [PATCH 17/17] Fix interpretation of lifecycle - using ranges after current stack version mighyt cause confusion --- docs/testing/req.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index b4f71f821..21a16a3ba 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -65,10 +65,10 @@ Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3` (range to fil ### Three stages with varying gaps ```{applies_to} -stack: preview 9.0, beta 9.2, ga 9.5 +stack: preview 8.0, beta 9.0, ga 9.2 ``` -Interpreted as: `preview 9.0-9.1`, `beta 9.2-9.4`, `ga 9.5+`. +Interpreted as: `preview 8.0-8.19`, `beta 9.0-9.1`, `ga 9.2+`. ## Inline examples