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(); }