Document logs essentials restricted ESQL commands for Kibana (#130346)

This commit is contained in:
Mark Vieira 2025-07-01 16:29:01 -07:00 committed by GitHub
parent 8b6acd06f4
commit 7d4ba31681
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 98 additions and 13 deletions

View File

@ -2,5 +2,6 @@
"comment" : "This is generated by ESQLs DocsV3Support. Do not edit it. See ../README.md for how to regenerate it.", "comment" : "This is generated by ESQLs DocsV3Support. Do not edit it. See ../README.md for how to regenerate it.",
"type" : "command", "type" : "command",
"name" : "change_point", "name" : "change_point",
"license" : "PLATINUM" "license" : "PLATINUM",
"observability_tier" : "COMPLETE"
} }

View File

@ -3,6 +3,7 @@
"type" : "grouping", "type" : "grouping",
"name" : "categorize", "name" : "categorize",
"license" : "PLATINUM", "license" : "PLATINUM",
"observability_tier" : "COMPLETE",
"description" : "Groups text messages into categories of similarly formatted text values.", "description" : "Groups text messages into categories of similarly formatted text values.",
"signatures" : [ "signatures" : [
{ {

View File

@ -35,6 +35,7 @@ public class ConsumeProcessor implements Processor {
@Override @Override
public Set<String> getSupportedAnnotationTypes() { public Set<String> getSupportedAnnotationTypes() {
return Set.of( return Set.of(
"org.elasticsearch.xpack.esql.SupportsObservabilityTier",
"org.elasticsearch.core.Nullable", "org.elasticsearch.core.Nullable",
"org.elasticsearch.injection.guice.Inject", "org.elasticsearch.injection.guice.Inject",
"org.elasticsearch.xpack.esql.expression.function.FunctionInfo", "org.elasticsearch.xpack.esql.expression.function.FunctionInfo",

View File

@ -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
}
}

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.esql.LicenseAware; 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.Expression;
import org.elasticsearch.xpack.esql.core.expression.Nullability; import org.elasticsearch.xpack.esql.core.expression.Nullability;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
@ -27,6 +28,7 @@ import org.elasticsearch.xpack.ml.MachineLearning;
import java.io.IOException; import java.io.IOException;
import java.util.List; 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.ParamOrdinal.DEFAULT;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString; 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} * For the implementation, see {@link org.elasticsearch.compute.aggregation.blockhash.CategorizeBlockHash}
* </p> * </p>
*/ */
@SupportsObservabilityTier(tier = COMPLETE)
public class Categorize extends GroupingFunction.NonEvaluatableGroupingFunction implements LicenseAware { public class Categorize extends GroupingFunction.NonEvaluatableGroupingFunction implements LicenseAware {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
Expression.class, Expression.class,

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.operator.ChangePointOperator; import org.elasticsearch.compute.operator.ChangePointOperator;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.esql.LicenseAware; import org.elasticsearch.xpack.esql.LicenseAware;
import org.elasticsearch.xpack.esql.SupportsObservabilityTier;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware; import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.Attribute;
@ -27,6 +28,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static org.elasticsearch.xpack.esql.SupportsObservabilityTier.ObservabilityTier.COMPLETE;
import static org.elasticsearch.xpack.esql.common.Failure.fail; 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 * Furthermore, ChangePoint should be called with at most 1000 data points. That's
* enforced by the Limit in the surrogate plan. * enforced by the Limit in the surrogate plan.
*/ */
@SupportsObservabilityTier(tier = COMPLETE)
public class ChangePoint extends UnaryPlan implements SurrogateLogicalPlan, PostAnalysisVerificationAware, LicenseAware { public class ChangePoint extends UnaryPlan implements SurrogateLogicalPlan, PostAnalysisVerificationAware, LicenseAware {
private final Attribute value; private final Attribute value;

View File

@ -18,6 +18,8 @@ import org.elasticsearch.logging.Logger;
import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.esql.CsvTestsDataLoader; 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.core.type.DataType;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike; import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike;
@ -572,7 +574,7 @@ public abstract class DocsV3Support {
boolean hasAppendix = renderAppendix(info.appendix()); boolean hasAppendix = renderAppendix(info.appendix());
renderFullLayout(info, hasExamples, hasAppendix, hasFunctionOptions); renderFullLayout(info, hasExamples, hasAppendix, hasFunctionOptions);
renderKibanaInlineDocs(name, null, info); 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 { private void renderFunctionNamedParams(EsqlFunctionRegistry.MapArgSignature mapArgSignature) throws IOException {
@ -666,6 +668,11 @@ public abstract class DocsV3Support {
::: :::
""".replace("$NAME$", name).replace("$SECTION$", section); """.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 */ /** 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) { if (ctor != null) {
FunctionInfo functionInfo = ctor.getAnnotation(FunctionInfo.class); FunctionInfo functionInfo = ctor.getAnnotation(FunctionInfo.class);
assert functionInfo != null; assert functionInfo != null;
renderDocsForOperators(op.name(), op.titleName(), ctor, functionInfo, op.variadic()); renderDocsForOperators(op.name(), op.titleName(), ctor, functionInfo, op.variadic(), getObservabilityTier());
} else { } else {
logger.info("Skipping rendering docs for operator '" + op.name() + "' with no @FunctionInfo"); logger.info("Skipping rendering docs for operator '" + op.name() + "' with no @FunctionInfo");
} }
@ -787,11 +794,17 @@ public abstract class DocsV3Support {
} }
}; };
String name = "not_" + baseName; 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) void renderDocsForOperators(
throws Exception { String name,
String titleName,
Constructor<?> ctor,
FunctionInfo info,
boolean variadic,
ObservabilityTier observabilityTier
) throws Exception {
renderKibanaInlineDocs(name, titleName, info); renderKibanaInlineDocs(name, titleName, info);
var params = ctor.getParameters(); 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()); renderDetailedDescription(info.detailedDescription(), info.note());
renderTypes(name, args); renderTypes(name, args);
renderExamples(info); renderExamples(info);
@ -831,17 +844,35 @@ public abstract class DocsV3Support {
writeToTempSnippetsDir("detailedDescription", rendered.toString()); 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 */ /** Command specific docs generating, currently very empty since we only render kibana definition files */
public static class CommandsDocsSupport extends DocsV3Support { public static class CommandsDocsSupport extends DocsV3Support {
private final LogicalPlan command; private final LogicalPlan command;
private final XPackLicenseState licenseState; 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); super("commands", name, testClass, Map::of);
this.command = command; this.command = command;
this.licenseState = licenseState; this.licenseState = licenseState;
this.observabilityTier = observabilityTier;
} }
@Override @Override
@ -867,6 +898,9 @@ public abstract class DocsV3Support {
if (license != null && license != License.OperationMode.BASIC) { if (license != null && license != License.OperationMode.BASIC) {
builder.field("license", license.toString()); builder.field("license", license.toString());
} }
if (observabilityTier != null && observabilityTier != ObservabilityTier.LOGS_ESSENTIALS) {
builder.field("observability_tier", observabilityTier.toString());
}
String rendered = Strings.toString(builder.endObject()); String rendered = Strings.toString(builder.endObject());
logger.info("Writing kibana command definition for [{}]:\n{}", name, rendered); logger.info("Writing kibana command definition for [{}]:\n{}", name, rendered);
writeToTempKibanaDir("definition", "json", rendered); writeToTempKibanaDir("definition", "json", rendered);
@ -1044,7 +1078,8 @@ public abstract class DocsV3Support {
String titleName, String titleName,
FunctionInfo info, FunctionInfo info,
List<EsqlFunctionRegistry.ArgSignature> args, List<EsqlFunctionRegistry.ArgSignature> args,
boolean variadic boolean variadic,
ObservabilityTier observabilityTier
) throws Exception { ) throws Exception {
try (XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().lfAtEnd().startObject()) { try (XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().lfAtEnd().startObject()) {
@ -1067,6 +1102,9 @@ public abstract class DocsV3Support {
if (license != null && license != License.OperationMode.BASIC) { if (license != null && license != License.OperationMode.BASIC) {
builder.field("license", license.toString()); 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) { if (titleName != null && titleName.equals(name) == false) {
builder.field("titleName", titleName); builder.field("titleName", titleName);
} }

View File

@ -14,6 +14,8 @@ import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger; import org.elasticsearch.logging.Logger;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.esql.LicenseAware; 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.Node;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.expression.function.DocsV3Support; 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 { private static void checkLicense(String commandName, LogicalPlan command) throws Exception {
log.info("Running function license checks"); log.info("Running function license checks");
TestCheckLicense checkLicense = new TestCheckLicense(); 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) { if (command instanceof LicenseAware licenseAware) {
log.info("Command " + commandName + " implements LicenseAware."); log.info("Command " + commandName + " implements LicenseAware.");
saveLicenseState(commandName, command, checkLicense.licenseLevel(licenseAware)); saveLicenseState(commandName, command, checkLicense.licenseLevel(licenseAware), observabilityTier);
} else { } else {
log.info("Command " + commandName + " does not implement LicenseAware."); 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( DocsV3Support.CommandsDocsSupport docs = new DocsV3Support.CommandsDocsSupport(
name.toLowerCase(Locale.ROOT), name.toLowerCase(Locale.ROOT),
CommandLicenseTests.class, CommandLicenseTests.class,
command, command,
licenseState licenseState,
observabilityTier
); );
docs.renderDocs(); docs.renderDocs();
} }