From 7d4ba316813d4e1641b048780a0428af25546f3c Mon Sep 17 00:00:00 2001
From: Mark Vieira
Date: Tue, 1 Jul 2025 16:29:01 -0700
Subject: [PATCH] Document logs essentials restricted ESQL commands for Kibana
(#130346)
---
.../definition/commands/change_point.json | 3 +-
.../definition/functions/categorize.json | 1 +
.../compute/gen/ConsumeProcessor.java | 1 +
.../xpack/esql/SupportsObservabilityTier.java | 25 +++++++++
.../function/grouping/Categorize.java | 3 ++
.../xpack/esql/plan/logical/ChangePoint.java | 3 ++
.../expression/function/DocsV3Support.java | 54 ++++++++++++++++---
.../plan/logical/CommandLicenseTests.java | 21 ++++++--
8 files changed, 98 insertions(+), 13 deletions(-)
create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/SupportsObservabilityTier.java
diff --git a/docs/reference/query-languages/esql/kibana/definition/commands/change_point.json b/docs/reference/query-languages/esql/kibana/definition/commands/change_point.json
index 80e0ded7f985..f68622e72129 100644
--- a/docs/reference/query-languages/esql/kibana/definition/commands/change_point.json
+++ b/docs/reference/query-languages/esql/kibana/definition/commands/change_point.json
@@ -2,5 +2,6 @@
"comment" : "This is generated by ESQL’s DocsV3Support. Do not edit it. See ../README.md for how to regenerate it.",
"type" : "command",
"name" : "change_point",
- "license" : "PLATINUM"
+ "license" : "PLATINUM",
+ "observability_tier" : "COMPLETE"
}
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/categorize.json b/docs/reference/query-languages/esql/kibana/definition/functions/categorize.json
index 8329afef234a..86c754b19a69 100644
--- a/docs/reference/query-languages/esql/kibana/definition/functions/categorize.json
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/categorize.json
@@ -3,6 +3,7 @@
"type" : "grouping",
"name" : "categorize",
"license" : "PLATINUM",
+ "observability_tier" : "COMPLETE",
"description" : "Groups text messages into categories of similarly formatted text values.",
"signatures" : [
{
diff --git a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java
index db18ff98e0bc..649c94b3ebf9 100644
--- a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java
+++ b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java
@@ -35,6 +35,7 @@ public class ConsumeProcessor implements Processor {
@Override
public Set getSupportedAnnotationTypes() {
return Set.of(
+ "org.elasticsearch.xpack.esql.SupportsObservabilityTier",
"org.elasticsearch.core.Nullable",
"org.elasticsearch.injection.guice.Inject",
"org.elasticsearch.xpack.esql.expression.function.FunctionInfo",
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/SupportsObservabilityTier.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/SupportsObservabilityTier.java
new file mode 100644
index 000000000000..7edbccb722d0
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/SupportsObservabilityTier.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SupportsObservabilityTier {
+
+ ObservabilityTier tier();
+
+ enum ObservabilityTier {
+ COMPLETE,
+ LOGS_ESSENTIALS
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/Categorize.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/Categorize.java
index 81ac52cad151..15b462158945 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/Categorize.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/Categorize.java
@@ -12,6 +12,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.esql.LicenseAware;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.Nullability;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
@@ -27,6 +28,7 @@ import org.elasticsearch.xpack.ml.MachineLearning;
import java.io.IOException;
import java.util.List;
+import static org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier.COMPLETE;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
@@ -39,6 +41,7 @@ import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isStr
* For the implementation, see {@link org.elasticsearch.compute.aggregation.blockhash.CategorizeBlockHash}
*
*/
+@SupportsObservabilityTier(tier = COMPLETE)
public class Categorize extends GroupingFunction.NonEvaluatableGroupingFunction implements LicenseAware {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
Expression.class,
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/ChangePoint.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/ChangePoint.java
index b672ee589e01..82bd5b1f69bf 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/ChangePoint.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/ChangePoint.java
@@ -10,6 +10,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.operator.ChangePointOperator;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.esql.LicenseAware;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
@@ -27,6 +28,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Objects;
+import static org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier.COMPLETE;
import static org.elasticsearch.xpack.esql.common.Failure.fail;
/**
@@ -41,6 +43,7 @@ import static org.elasticsearch.xpack.esql.common.Failure.fail;
* Furthermore, ChangePoint should be called with at most 1000 data points. That's
* enforced by the Limit in the surrogate plan.
*/
+@SupportsObservabilityTier(tier = COMPLETE)
public class ChangePoint extends UnaryPlan implements SurrogateLogicalPlan, PostAnalysisVerificationAware, LicenseAware {
private final Attribute value;
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
index 585f507e1e03..129a81170fe2 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java
@@ -18,6 +18,8 @@ import org.elasticsearch.logging.Logger;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.esql.CsvTestsDataLoader;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike;
@@ -572,7 +574,7 @@ public abstract class DocsV3Support {
boolean hasAppendix = renderAppendix(info.appendix());
renderFullLayout(info, hasExamples, hasAppendix, hasFunctionOptions);
renderKibanaInlineDocs(name, null, info);
- renderKibanaFunctionDefinition(name, null, info, description.args(), description.variadic());
+ renderKibanaFunctionDefinition(name, null, info, description.args(), description.variadic(), getObservabilityTier());
}
private void renderFunctionNamedParams(EsqlFunctionRegistry.MapArgSignature mapArgSignature) throws IOException {
@@ -666,6 +668,11 @@ public abstract class DocsV3Support {
:::
""".replace("$NAME$", name).replace("$SECTION$", section);
}
+
+ private ObservabilityTier getObservabilityTier() {
+ SupportsObservabilityTier supportsObservabilityTier = definition.clazz().getAnnotation(SupportsObservabilityTier.class);
+ return supportsObservabilityTier != null ? supportsObservabilityTier.tier() : null;
+ }
}
/** Operator specific docs generating, since it is currently quite different from the function docs generating */
@@ -712,7 +719,7 @@ public abstract class DocsV3Support {
if (ctor != null) {
FunctionInfo functionInfo = ctor.getAnnotation(FunctionInfo.class);
assert functionInfo != null;
- renderDocsForOperators(op.name(), op.titleName(), ctor, functionInfo, op.variadic());
+ renderDocsForOperators(op.name(), op.titleName(), ctor, functionInfo, op.variadic(), getObservabilityTier());
} else {
logger.info("Skipping rendering docs for operator '" + op.name() + "' with no @FunctionInfo");
}
@@ -787,11 +794,17 @@ public abstract class DocsV3Support {
}
};
String name = "not_" + baseName;
- renderDocsForOperators(name, null, ctor, functionInfo, op.variadic());
+ renderDocsForOperators(name, null, ctor, functionInfo, op.variadic(), getObservabilityTier());
}
- void renderDocsForOperators(String name, String titleName, Constructor> ctor, FunctionInfo info, boolean variadic)
- throws Exception {
+ void renderDocsForOperators(
+ String name,
+ String titleName,
+ Constructor> ctor,
+ FunctionInfo info,
+ boolean variadic,
+ ObservabilityTier observabilityTier
+ ) throws Exception {
renderKibanaInlineDocs(name, titleName, info);
var params = ctor.getParameters();
@@ -808,7 +821,7 @@ public abstract class DocsV3Support {
}
}
}
- renderKibanaFunctionDefinition(name, titleName, info, args, variadic);
+ renderKibanaFunctionDefinition(name, titleName, info, args, variadic, observabilityTier);
renderDetailedDescription(info.detailedDescription(), info.note());
renderTypes(name, args);
renderExamples(info);
@@ -831,17 +844,35 @@ public abstract class DocsV3Support {
writeToTempSnippetsDir("detailedDescription", rendered.toString());
}
}
+
+ private ObservabilityTier getObservabilityTier() {
+ if (op != null) {
+ SupportsObservabilityTier supportsObservabilityTier = op.clazz().getAnnotation(SupportsObservabilityTier.class);
+ if (supportsObservabilityTier != null) {
+ return supportsObservabilityTier.tier();
+ }
+ }
+ return null;
+ }
}
/** Command specific docs generating, currently very empty since we only render kibana definition files */
public static class CommandsDocsSupport extends DocsV3Support {
private final LogicalPlan command;
private final XPackLicenseState licenseState;
+ private final ObservabilityTier observabilityTier;
- public CommandsDocsSupport(String name, Class> testClass, LogicalPlan command, XPackLicenseState licenseState) {
+ public CommandsDocsSupport(
+ String name,
+ Class> testClass,
+ LogicalPlan command,
+ XPackLicenseState licenseState,
+ ObservabilityTier observabilityTier
+ ) {
super("commands", name, testClass, Map::of);
this.command = command;
this.licenseState = licenseState;
+ this.observabilityTier = observabilityTier;
}
@Override
@@ -867,6 +898,9 @@ public abstract class DocsV3Support {
if (license != null && license != License.OperationMode.BASIC) {
builder.field("license", license.toString());
}
+ if (observabilityTier != null && observabilityTier != ObservabilityTier.LOGS_ESSENTIALS) {
+ builder.field("observability_tier", observabilityTier.toString());
+ }
String rendered = Strings.toString(builder.endObject());
logger.info("Writing kibana command definition for [{}]:\n{}", name, rendered);
writeToTempKibanaDir("definition", "json", rendered);
@@ -1044,7 +1078,8 @@ public abstract class DocsV3Support {
String titleName,
FunctionInfo info,
List args,
- boolean variadic
+ boolean variadic,
+ ObservabilityTier observabilityTier
) throws Exception {
try (XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().lfAtEnd().startObject()) {
@@ -1067,6 +1102,9 @@ public abstract class DocsV3Support {
if (license != null && license != License.OperationMode.BASIC) {
builder.field("license", license.toString());
}
+ if (observabilityTier != null && observabilityTier != ObservabilityTier.LOGS_ESSENTIALS) {
+ builder.field("observability_tier", observabilityTier.toString());
+ }
if (titleName != null && titleName.equals(name) == false) {
builder.field("titleName", titleName);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java
index 12658824019d..0adfec84fba4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java
@@ -14,6 +14,8 @@ import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.esql.LicenseAware;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier;
+import org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier;
import org.elasticsearch.xpack.esql.core.tree.Node;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.expression.function.DocsV3Support;
@@ -74,21 +76,32 @@ public class CommandLicenseTests extends ESTestCase {
private static void checkLicense(String commandName, LogicalPlan command) throws Exception {
log.info("Running function license checks");
TestCheckLicense checkLicense = new TestCheckLicense();
+ ObservabilityTier observabilityTier = null;
+ SupportsObservabilityTier supportsObservabilityTier = command.getClass().getAnnotation(SupportsObservabilityTier.class);
+ if (supportsObservabilityTier != null) {
+ observabilityTier = supportsObservabilityTier.tier();
+ }
if (command instanceof LicenseAware licenseAware) {
log.info("Command " + commandName + " implements LicenseAware.");
- saveLicenseState(commandName, command, checkLicense.licenseLevel(licenseAware));
+ saveLicenseState(commandName, command, checkLicense.licenseLevel(licenseAware), observabilityTier);
} else {
log.info("Command " + commandName + " does not implement LicenseAware.");
- saveLicenseState(commandName, command, checkLicense.basicLicense);
+ saveLicenseState(commandName, command, checkLicense.basicLicense, observabilityTier);
}
}
- private static void saveLicenseState(String name, LogicalPlan command, XPackLicenseState licenseState) throws Exception {
+ private static void saveLicenseState(
+ String name,
+ LogicalPlan command,
+ XPackLicenseState licenseState,
+ SupportsObservabilityTier.ObservabilityTier observabilityTier
+ ) throws Exception {
DocsV3Support.CommandsDocsSupport docs = new DocsV3Support.CommandsDocsSupport(
name.toLowerCase(Locale.ROOT),
CommandLicenseTests.class,
command,
- licenseState
+ licenseState,
+ observabilityTier
);
docs.renderDocs();
}