Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/_snippets/applies_to-version.md
Original file line number Diff line number Diff line change
@@ -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.
:::
4 changes: 2 additions & 2 deletions docs/syntax/_snippets/inline-level-applies-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ 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`

::::

::::{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`
```
::::
Expand Down
91 changes: 83 additions & 8 deletions docs/syntax/applies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:`<br/>` 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:
Expand Down Expand Up @@ -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+
```
:::::

Expand Down
96 changes: 91 additions & 5 deletions docs/testing/req.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand All @@ -38,3 +34,93 @@ 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` | `` {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` `` |
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -294,14 +294,14 @@ private static ProductLifecycle ParseLifecycle(string stateValue)
/// <summary>
/// Parses the version from "Added in X.Y.Z" pattern in the x-state string.
/// </summary>
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;
}

/// <summary>
Expand Down
20 changes: 10 additions & 10 deletions src/Elastic.Documentation/AppliesTo/Applicability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,12 +98,12 @@ public override string ToString()
public record Applicability : IComparable<Applicability>, 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
};


Expand All @@ -126,8 +126,8 @@ public string GetLifeCycleName() =>
/// <inheritdoc />
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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@ public static Applicability GetPrimaryApplicability(IEnumerable<Applicability> 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.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.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();
}
Expand Down
Loading
Loading