diff --git a/CHANGELOG.md b/CHANGELOG.md index fe358a240f110..a7eb5ad48dc81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Search Pipeline] Handle default pipeline for multiple indices ([#13276](https://github.com/opensearch-project/OpenSearch/pull/13276)) - Add support for deep copying SearchRequest ([#12295](https://github.com/opensearch-project/OpenSearch/pull/12295)) - Support multi ranges traversal when doing date histogram rewrite optimization. ([#13317](https://github.com/opensearch-project/OpenSearch/pull/13317)) +- Add User Behavior Insights. ([#13545](https://github.com/opensearch-project/OpenSearch/issues/13545)) ### Dependencies - Bump `org.apache.commons:commons-configuration2` from 2.10.0 to 2.10.1 ([#12896](https://github.com/opensearch-project/OpenSearch/pull/12896)) diff --git a/gradle/missing-javadoc.gradle b/gradle/missing-javadoc.gradle index e9a6d798b8323..20dc74603df95 100644 --- a/gradle/missing-javadoc.gradle +++ b/gradle/missing-javadoc.gradle @@ -124,6 +124,7 @@ configure([ project(":modules:repository-url"), project(":modules:systemd"), project(":modules:transport-netty4"), + project(":modules:ubi"), project(":plugins:analysis-icu"), project(":plugins:analysis-kuromoji"), project(":plugins:analysis-nori"), diff --git a/modules/ubi/README.md b/modules/ubi/README.md new file mode 100644 index 0000000000000..261c547d8f491 --- /dev/null +++ b/modules/ubi/README.md @@ -0,0 +1,95 @@ +# User Behavior Insights (UBI) + +UBI facilitates storing queries and events for the purposes of improving search relevance as described by [[RFC] User Behavior Insights](https://github.com/opensearch-project/OpenSearch/issues/12084). + +## Indexes + +UBI creates two indexes the first time a search request containing a `ubi` block in the `ext`. The indexes are: +* `ubi_queries` - For storing queries. +* `ubi_events` - For storing client-side events. + +## Indexing Queries + +For UBI to index a query, add a `ubi` block to the `ext` in the search request containing a `query_id`: + +``` +curl -s http://localhost:9200/ecommerce/_search -H "Content-type: application/json" -d' +{ + "query": { + "match": { + "title": "toner OR ink" + } + }, + "ext": { + "ubi": { + "query_id": "1234512345" + } + } +} +``` + +There are optional values that can be included in the `ubi` block along with the `query_id`. Those values are: +* `client_id` - A unique identifier for the source of the query. This may represent a user or some other mechanism. +* `user_query` - The user-entered query for this search. For example, in the search request above, the `user_query` may have been `toner ink`. +* `object_id` - The name of a field in the index. The value of this field will be used as the unique identifier for a search hit. If not provided, the value of the search hit `_id` field will be used. + +With these optional values, a sample query is: + +``` +curl -s http://localhost:9200/ecommerce/_search -H "Content-type: application/json" -d' +{ + "query": { + "match": { + "title": "toner OR ink" + } + }, + "ext": { + "ubi": { + "query_id": "1234512345", + "client_id": "abcdefg", + "user_query": "toner ink" + } + } +} +``` + +If a search request does not contain a `ubi` block containing a `query_id` in `ext`, the query will *not* be indexed. + +## Indexing Events + +UBI facilitates indexing both queries and client-side events. These client-side events may be product clicks, scroll-depth, +adding a product to a cart, or other actions. UBI indexes these events in an index called `ubi_events`. This index is +automatically created the first time a query containing a `ubi` section in `ext` (example above). + +Client-side events can be indexed into the `ubi_events` index by your method of choice. + +## Example Usage of UBI + +Do a query over an index: + +``` +curl http://localhost:9200/ecommerce/_search -H "Content-Type: application/json" -d +{ + "query": { + "match": { + "title": "toner OR ink" + } + }, + "ext": { + "ubi": { + "query_id": "1234512345", + "client_id": "abcdefg", + "user_query": "toner ink" + } + } +} +``` + +Look to see the new `ubi_queries` and `ubi_queries` indexes. Note that the `ubi_queries` contains a document and it is the query that was just performed. + +``` +curl http://localhost:9200/_cat/indices +green open ubi_queries KamFVJmQQBe7ztocj6kIUA 1 0 1 0 4.9kb 4.9kb +green open ecommerce KFaxwpbiQGWaG7Z8t0G7uA 1 0 25 0 138.4kb 138.4kb +green open ubi_events af0XlfmxSS-Evi4Xg1XrVg 1 0 0 0 208b 208b +``` diff --git a/modules/ubi/build.gradle b/modules/ubi/build.gradle new file mode 100644 index 0000000000000..6c3231bfb5adc --- /dev/null +++ b/modules/ubi/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'opensearch.yaml-rest-test' + +opensearchplugin { + description 'Integrates OpenSearch with systemd' + classname 'org.opensearch.ubi.UbiModulePlugin' +} + +dependencies { + // required for the yaml test to run + yamlRestTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" + runtimeOnly "org.apache.logging.log4j:log4j-core:${versions.log4j}" +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/QueryRequest.java b/modules/ubi/src/main/java/org/opensearch/ubi/QueryRequest.java new file mode 100644 index 0000000000000..5074fcaf037ac --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/QueryRequest.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +/** + * A query received by OpenSearch. + */ +public class QueryRequest { + + private final long timestamp; + private final String queryId; + private final String userId; + private final String userQuery; + private final QueryResponse queryResponse; + + /** + * Creates a query request. + * @param queryId The ID of the query. + * @param userQuery The user-entered query. + * @param userId The ID of the user that initiated the query. + * @param queryResponse The {@link QueryResponse} for this query request. + */ + public QueryRequest(final String queryId, final String userQuery, final String userId, final QueryResponse queryResponse) { + this.timestamp = System.currentTimeMillis(); + this.queryId = queryId; + this.userId = userId; + this.userQuery = userQuery; + this.queryResponse = queryResponse; + } + + /** + * Gets the timestamp. + * @return The timestamp. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Gets the query ID. + * @return The query ID. + */ + public String getQueryId() { + return queryId; + } + + /** + * Gets the user query. + * @return The user query. + */ + public String getUserQuery() { + return userQuery; + } + + /** + * Gets the user ID. + * @return The user ID. + */ + public String getUserId() { + return userId; + } + + /** + * Gets the query response for this query request. + * @return The {@link QueryResponse} for this query request. + */ + public QueryResponse getQueryResponse() { + return queryResponse; + } + +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/QueryResponse.java b/modules/ubi/src/main/java/org/opensearch/ubi/QueryResponse.java new file mode 100644 index 0000000000000..9d3f9326861fc --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/QueryResponse.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +import java.util.List; + +/** + * A query response. + */ +public class QueryResponse { + + private final String queryId; + private final String queryResponseId; + private final List queryResponseObjectIds; + + /** + * Creates a query response. + * @param queryId The ID of the query. + * @param queryResponseId The ID of the query response. + * @param queryResponseObjectIds A list of IDs for the hits in the query. + */ + public QueryResponse(final String queryId, final String queryResponseId, final List queryResponseObjectIds) { + this.queryId = queryId; + this.queryResponseId = queryResponseId; + this.queryResponseObjectIds = queryResponseObjectIds; + } + + /** + * Gets the query ID. + * @return The query ID. + */ + public String getQueryId() { + return queryId; + } + + /** + * Gets the query response ID. + * @return The query response ID. + */ + public String getQueryResponseId() { + return queryResponseId; + } + + /** + * Gets the list of query response hit IDs. + * @return A list of query response hit IDs. + */ + public List getQueryResponseObjectIds() { + return queryResponseObjectIds; + } + +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java b/modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java new file mode 100644 index 0000000000000..8e83c2641b69f --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java @@ -0,0 +1,231 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.support.ActionFilter; +import org.opensearch.action.support.ActionFilterChain; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.io.Streams; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.search.SearchHit; +import org.opensearch.tasks.Task; +import org.opensearch.ubi.ext.UbiParameters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * An implementation of {@link ActionFilter} that listens for OpenSearch + * queries and persists the queries to the UBI store. + */ +public class UbiActionFilter implements ActionFilter { + + private static final Logger LOGGER = LogManager.getLogger(UbiActionFilter.class); + + private static final String UBI_QUERIES_INDEX = "ubi_queries"; + private static final String UBI_EVENTS_INDEX = "ubi_events"; + + private static final String EVENTS_MAPPING_FILE = "/events-mapping.json"; + private static final String QUERIES_MAPPING_FILE = "/queries-mapping.json"; + + private final Client client; + + /** + * Creates a new filter. + * @param client An OpenSearch {@link Client}. + */ + public UbiActionFilter(Client client) { + this.client = client; + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public void apply( + Task task, + String action, + Request request, + ActionListener listener, + ActionFilterChain chain + ) { + + if (!(request instanceof SearchRequest)) { + chain.proceed(task, action, request, listener); + return; + } + + chain.proceed(task, action, request, new ActionListener<>() { + + @Override + public void onResponse(Response response) { + + final SearchRequest searchRequest = (SearchRequest) request; + + if (response instanceof SearchResponse) { + + final UbiParameters ubiParameters = UbiParameters.getUbiParameters(searchRequest); + + if (ubiParameters != null) { + + final String queryId = ubiParameters.getQueryId(); + + if (queryId != null) { + + final String userQuery = ubiParameters.getUserQuery(); + final String clientId = ubiParameters.getClientId(); + final String objectId = ubiParameters.getObjectId(); + + final List queryResponseHitIds = new LinkedList<>(); + + for (final SearchHit hit : ((SearchResponse) response).getHits()) { + + if (objectId == null || objectId.isEmpty()) { + // Use the result's docId since no object_id was given for the search. + queryResponseHitIds.add(String.valueOf(hit.docId())); + } else { + final Map source = hit.getSourceAsMap(); + queryResponseHitIds.add((String) source.get(objectId)); + } + + } + + final String queryResponseId = UUID.randomUUID().toString(); + final QueryResponse queryResponse = new QueryResponse(queryId, queryResponseId, queryResponseHitIds); + final QueryRequest queryRequest = new QueryRequest(queryId, userQuery, clientId, queryResponse); + + indexQuery(queryRequest); + + } + + } + + } + + listener.onResponse(response); + + } + + @Override + public void onFailure(Exception ex) { + listener.onFailure(ex); + } + + }); + + } + + private void indexQuery(final QueryRequest queryRequest) { + + final IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(UBI_EVENTS_INDEX, UBI_QUERIES_INDEX); + + client.admin().indices().exists(indicesExistsRequest, new ActionListener<>() { + + @Override + public void onResponse(IndicesExistsResponse indicesExistsResponse) { + + if (!indicesExistsResponse.isExists()) { + + final Settings indexSettings = Settings.builder() + .put(IndexMetadata.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1) + .put(IndexMetadata.INDEX_AUTO_EXPAND_REPLICAS_SETTING.getKey(), "0-2") + .put(IndexMetadata.SETTING_PRIORITY, Integer.MAX_VALUE) + .build(); + + // Create the UBI events index. + final CreateIndexRequest createEventsIndexRequest = new CreateIndexRequest(UBI_EVENTS_INDEX).mapping( + getResourceFile(EVENTS_MAPPING_FILE) + ).settings(indexSettings); + + client.admin().indices().create(createEventsIndexRequest); + + // Create the UBI queries index. + final CreateIndexRequest createQueriesIndexRequest = new CreateIndexRequest(UBI_QUERIES_INDEX).mapping( + getResourceFile(QUERIES_MAPPING_FILE) + ).settings(indexSettings); + + client.admin().indices().create(createQueriesIndexRequest); + + } + + LOGGER.debug( + "Indexing query ID {} with response ID {}", + queryRequest.getQueryId(), + queryRequest.getQueryResponse().getQueryResponseId() + ); + + // What will be indexed - adheres to the queries-mapping.json + final Map source = new HashMap<>(); + source.put("timestamp", queryRequest.getTimestamp()); + source.put("query_id", queryRequest.getQueryId()); + source.put("query_response_id", queryRequest.getQueryResponse().getQueryResponseId()); + source.put("query_response_object_ids", queryRequest.getQueryResponse().getQueryResponseObjectIds()); + source.put("user_id", queryRequest.getUserId()); + source.put("user_query", queryRequest.getUserQuery()); + + // Build the index request. + final IndexRequest indexRequest = new IndexRequest(UBI_QUERIES_INDEX).source(source, XContentType.JSON); + + client.index(indexRequest, new ActionListener<>() { + + @Override + public void onResponse(IndexResponse indexResponse) {} + + @Override + public void onFailure(Exception e) { + LOGGER.error("Unable to index query into UBI index.", e); + } + + }); + + } + + @Override + public void onFailure(Exception ex) { + LOGGER.error("Error creating UBI indexes.", ex); + } + + }); + + } + + private String getResourceFile(final String fileName) { + try (InputStream is = UbiActionFilter.class.getResourceAsStream(fileName)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.copy(is, out); + return out.toString(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Unable to get mapping from resource [" + fileName + "]", e); + } + } + +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/UbiModulePlugin.java b/modules/ubi/src/main/java/org/opensearch/ubi/UbiModulePlugin.java new file mode 100644 index 0000000000000..15b399a5cc39c --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/UbiModulePlugin.java @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +import org.opensearch.action.support.ActionFilter; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SearchPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.ubi.ext.UbiParametersExtBuilder; +import org.opensearch.watcher.ResourceWatcherService; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Collections.singletonList; + +/** + * OpenSearch User Behavior Insights + */ +public class UbiModulePlugin extends Plugin implements ActionPlugin, SearchPlugin { + + private ActionFilter userBehaviorLoggingFilter; + + /** + * Creates a new instance of {@link UbiModulePlugin}. + */ + public UbiModulePlugin() {} + + @Override + public List getActionFilters() { + return singletonList(userBehaviorLoggingFilter); + } + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + + this.userBehaviorLoggingFilter = new UbiActionFilter(client); + + return Collections.emptyList(); + + } + + @Override + public List> getSearchExts() { + + final List> searchExts = new ArrayList<>(); + + searchExts.add( + new SearchExtSpec<>(UbiParametersExtBuilder.UBI_PARAMETER_NAME, UbiParametersExtBuilder::new, UbiParametersExtBuilder::parse) + ); + + return searchExts; + + } + +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParameters.java b/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParameters.java new file mode 100644 index 0000000000000..96260b83474d0 --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParameters.java @@ -0,0 +1,220 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi.ext; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.core.ParseField; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.ObjectParser; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.search.SearchExtBuilder; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +/** + * The UBI parameters available in the ext. + */ +public class UbiParameters implements Writeable, ToXContentObject { + + private static final ObjectParser PARSER; + private static final ParseField QUERY_ID = new ParseField("query_id"); + private static final ParseField USER_QUERY = new ParseField("user_query"); + private static final ParseField CLIENT_ID = new ParseField("client_id"); + private static final ParseField OBJECT_ID = new ParseField("object_id"); + + static { + PARSER = new ObjectParser<>(UbiParametersExtBuilder.UBI_PARAMETER_NAME, UbiParameters::new); + PARSER.declareString(UbiParameters::setQueryId, QUERY_ID); + PARSER.declareString(UbiParameters::setUserQuery, USER_QUERY); + PARSER.declareString(UbiParameters::setClientId, CLIENT_ID); + PARSER.declareString(UbiParameters::setObjectId, OBJECT_ID); + } + + private String queryId; + private String userQuery; + private String clientId; + private String objectId; + + /** + * Get the {@link UbiParameters} from a {@link SearchRequest}. + * @param request A {@link SearchRequest}, + * @return The UBI {@link UbiParameters parameters}. + */ + public static UbiParameters getUbiParameters(final SearchRequest request) { + + UbiParametersExtBuilder builder = null; + + if (request.source() != null && request.source().ext() != null && !request.source().ext().isEmpty()) { + final Optional b = request.source() + .ext() + .stream() + .filter(bldr -> UbiParametersExtBuilder.UBI_PARAMETER_NAME.equals(bldr.getWriteableName())) + .findFirst(); + if (b.isPresent()) { + builder = (UbiParametersExtBuilder) b.get(); + } + } + + if (builder != null) { + return builder.getParams(); + } else { + return null; + } + + } + + /** + * Creates a new instance. + */ + public UbiParameters() {} + + /** + * Creates a new instance. + * @param input The {@link StreamInput} to read parameters from. + * @throws IOException Thrown if the parameters cannot be read. + */ + public UbiParameters(StreamInput input) throws IOException { + this.queryId = input.readString(); + this.userQuery = input.readOptionalString(); + this.clientId = input.readOptionalString(); + this.objectId = input.readOptionalString(); + } + + /** + * Creates a new instance. + * @param queryId The query ID. + * @param userQuery The user-entered search query. + * @param clientId The client ID. + * @param objectId The object ID. + */ + public UbiParameters(String queryId, String userQuery, String clientId, String objectId) { + this.queryId = queryId; + this.userQuery = userQuery; + this.clientId = clientId; + this.objectId = objectId; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + return xContentBuilder.field(QUERY_ID.getPreferredName(), this.queryId) + .field(USER_QUERY.getPreferredName(), this.userQuery) + .field(CLIENT_ID.getPreferredName(), this.clientId) + .field(OBJECT_ID.getPreferredName(), this.objectId); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(getQueryId()); + out.writeOptionalString(userQuery); + out.writeOptionalString(clientId); + out.writeOptionalString(objectId); + } + + /** + * Create the {@link UbiParameters} from a {@link XContentParser}. + * @param parser An {@link XContentParser}. + * @return The {@link UbiParameters}. + * @throws IOException Thrown if the parameters cannot be read. + */ + public static UbiParameters parse(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + UbiParameters other = (UbiParameters) o; + return Objects.equals(this.queryId, other.getQueryId()) + && Objects.equals(this.userQuery, other.getUserQuery()) + && Objects.equals(this.clientId, other.getClientId()) + && Objects.equals(this.objectId, other.getObjectId()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getClass(), this.queryId); + } + + /** + * Get the query ID. + * @return The query ID, or a random UUID if the query ID is null. + */ + public String getQueryId() { + return queryId; + } + + /** + * Set the query ID. + * @param queryId The query ID. + */ + public void setQueryId(String queryId) { + this.queryId = queryId; + } + + /** + * Get the client ID. + * @return The client ID. + */ + public String getClientId() { + return clientId; + } + + /** + * Set the client ID. + * @param clientId The client ID. + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * Get the object ID. + * @return The object ID. + */ + public String getObjectId() { + return objectId; + } + + /** + * Set the object ID. + * @param objectId The object ID. + */ + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + /** + * Get the user query. + * @return The user query. + */ + public String getUserQuery() { + return userQuery; + } + + /** + * Set the user query. + * @param userQuery The user query. + */ + public void setUserQuery(String userQuery) { + this.userQuery = userQuery; + } + +} diff --git a/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParametersExtBuilder.java b/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParametersExtBuilder.java new file mode 100644 index 0000000000000..31d0f5f1e907d --- /dev/null +++ b/modules/ubi/src/main/java/org/opensearch/ubi/ext/UbiParametersExtBuilder.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi.ext; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.search.SearchExtBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Subclass of {@link SearchExtBuilder} to access UBI parameters. + */ +public class UbiParametersExtBuilder extends SearchExtBuilder { + + /** + * The name of the "ext" section containing UBI parameters. + */ + public static final String UBI_PARAMETER_NAME = "ubi"; + + private UbiParameters params; + + /** + * Creates a new instance. + */ + public UbiParametersExtBuilder() {} + + /** + * Creates a new instance from a {@link StreamInput}. + * @param input A {@link StreamInput} containing the parameters. + * @throws IOException Thrown if the stream cannot be read. + */ + public UbiParametersExtBuilder(StreamInput input) throws IOException { + this.params = new UbiParameters(input); + } + + @Override + public int hashCode() { + return Objects.hash(this.getClass(), this.params); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof UbiParametersExtBuilder)) { + return false; + } + + return this.params.equals(((UbiParametersExtBuilder) obj).getParams()); + } + + @Override + public String getWriteableName() { + return UBI_PARAMETER_NAME; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + this.params.writeTo(out); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.value(this.params); + } + + /** + * Parses the ubi section of the ext block. + * @param parser A {@link XContentParser parser}. + * @return The {@link UbiParameters paramers}. + * @throws IOException Thrown if the UBI parameters cannot be read. + */ + public static UbiParametersExtBuilder parse(XContentParser parser) throws IOException { + final UbiParametersExtBuilder builder = new UbiParametersExtBuilder(); + builder.setParams(UbiParameters.parse(parser)); + return builder; + } + + /** + * Gets the {@link UbiParameters params}. + * @return The {@link UbiParameters params}. + */ + public UbiParameters getParams() { + return params; + } + + /** + * Set the {@link UbiParameters params}. + * @param params The {@link UbiParameters params}. + */ + public void setParams(UbiParameters params) { + this.params = params; + } + +} diff --git a/modules/ubi/src/main/resources/events-mapping.json b/modules/ubi/src/main/resources/events-mapping.json new file mode 100644 index 0000000000000..f6d1f37a2d0e1 --- /dev/null +++ b/modules/ubi/src/main/resources/events-mapping.json @@ -0,0 +1,114 @@ +{ + "properties": { + "action_name": { + "type": "keyword" + }, + "user_id": { + "type": "keyword" + }, + "query_id": { + "type": "keyword" + }, + "page_id": { + "type": "keyword" + }, + "message": { + "type": "keyword" + }, + "message_type": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "doc_values": true + }, + "event_attributes": { + "properties": { + "user_name": { + "type": "keyword" + }, + "user_id": { + "type": "keyword" + }, + "email": { + "type": "keyword" + }, + "price": { + "type": "float" + }, + "ip": { + "type": "ip", + "ignore_malformed": true + }, + "browser": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "position": { + "properties": { + "ordinal": { + "type": "integer" + }, + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + }, + "page_depth": { + "type": "integer" + }, + "scroll_depth": { + "type": "integer" + }, + "trail": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "object": { + "properties": { + "key_value": { + "type": "keyword" + }, + "object_id": { + "type": "keyword" + }, + "object_type": { + "type": "keyword" + }, + "transaction_id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "description": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "to_user_id": { + "type": "keyword" + }, + "object_detail": { + "type": "object" + } + } + } + } + } + } +} diff --git a/modules/ubi/src/main/resources/queries-mapping.json b/modules/ubi/src/main/resources/queries-mapping.json new file mode 100644 index 0000000000000..11ead3377b57a --- /dev/null +++ b/modules/ubi/src/main/resources/queries-mapping.json @@ -0,0 +1,16 @@ +{ + "dynamic": false, + "properties": { + "timestamp": { + "type": "date" + }, + "query_id": { "type": "keyword" }, + "query": { + "type": "text" + }, + "query_response_id": { "type": "keyword" }, + "query_response_object_ids": { "type": "keyword" }, + "user_id": { "type": "keyword" }, + "user_query": { "type": "keyword" } + } +} diff --git a/modules/ubi/src/test/java/org.opensearch.ubi/UbiActionFilterTests.java b/modules/ubi/src/test/java/org.opensearch.ubi/UbiActionFilterTests.java new file mode 100644 index 0000000000000..c3288c1cb6902 --- /dev/null +++ b/modules/ubi/src/test/java/org.opensearch.ubi/UbiActionFilterTests.java @@ -0,0 +1,149 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +import org.apache.lucene.search.TotalHits; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.support.ActionFilterChain; +import org.opensearch.client.AdminClient; +import org.opensearch.client.Client; +import org.opensearch.client.IndicesAdminClient; +import org.opensearch.common.action.ActionFuture; +import org.opensearch.core.action.ActionListener; +import org.opensearch.search.SearchExtBuilder; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.tasks.Task; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.ubi.ext.UbiParameters; +import org.opensearch.ubi.ext.UbiParametersExtBuilder; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UbiActionFilterTests extends OpenSearchTestCase { + + @SuppressWarnings("unchecked") + public void testApplyWithoutUbiBlock() { + + final Client client = mock(Client.class); + final AdminClient adminClient = mock(AdminClient.class); + final IndicesAdminClient indicesAdminClient = mock(IndicesAdminClient.class); + + when(client.admin()).thenReturn(adminClient); + when(adminClient.indices()).thenReturn(indicesAdminClient); + + final ActionFuture actionFuture = mock(ActionFuture.class); + when(indicesAdminClient.exists(any(IndicesExistsRequest.class))).thenReturn(actionFuture); + + final UbiActionFilter ubiActionFilter = new UbiActionFilter(client); + final ActionListener listener = mock(ActionListener.class); + + final SearchRequest request = mock(SearchRequest.class); + SearchHit[] searchHit = {}; + final SearchHits searchHits = new SearchHits(searchHit, new TotalHits(0, TotalHits.Relation.EQUAL_TO), 0); + + final SearchResponse response = mock(SearchResponse.class); + when(response.getHits()).thenReturn(searchHits); + + final Task task = mock(Task.class); + + final ActionFilterChain chain = mock(ActionFilterChain.class); + + doAnswer(invocation -> { + ActionListener actionListener = invocation.getArgument(3); + actionListener.onResponse(response); + return null; + }).when(chain).proceed(eq(task), anyString(), eq(request), any()); + + UbiParametersExtBuilder builder = mock(UbiParametersExtBuilder.class); + final List builders = new ArrayList<>(); + builders.add(builder); + + when(builder.getWriteableName()).thenReturn(UbiParametersExtBuilder.UBI_PARAMETER_NAME); + when(builder.getParams()).thenReturn(null); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.ext(builders); + + when(request.source()).thenReturn(searchSourceBuilder); + + ubiActionFilter.apply(task, "ubi", request, listener, chain); + + verify(client, never()).index(any(), any()); + + } + + @SuppressWarnings("unchecked") + public void testApplyWithUbiBlockWithoutQueryId() { + + final Client client = mock(Client.class); + final AdminClient adminClient = mock(AdminClient.class); + final IndicesAdminClient indicesAdminClient = mock(IndicesAdminClient.class); + + when(client.admin()).thenReturn(adminClient); + when(adminClient.indices()).thenReturn(indicesAdminClient); + + final ActionFuture actionFuture = mock(ActionFuture.class); + when(indicesAdminClient.exists(any(IndicesExistsRequest.class))).thenReturn(actionFuture); + + final UbiActionFilter ubiActionFilter = new UbiActionFilter(client); + final ActionListener listener = mock(ActionListener.class); + + final SearchRequest request = mock(SearchRequest.class); + SearchHit[] searchHit = {}; + final SearchHits searchHits = new SearchHits(searchHit, new TotalHits(0, TotalHits.Relation.EQUAL_TO), 0); + + final SearchResponse response = mock(SearchResponse.class); + when(response.getHits()).thenReturn(searchHits); + + final Task task = mock(Task.class); + + final ActionFilterChain chain = mock(ActionFilterChain.class); + + doAnswer(invocation -> { + ActionListener actionListener = invocation.getArgument(3); + actionListener.onResponse(response); + return null; + }).when(chain).proceed(eq(task), anyString(), eq(request), any()); + + UbiParametersExtBuilder builder = mock(UbiParametersExtBuilder.class); + final List builders = new ArrayList<>(); + builders.add(builder); + + final UbiParameters ubiParameters = new UbiParameters(); + + when(builder.getWriteableName()).thenReturn(UbiParametersExtBuilder.UBI_PARAMETER_NAME); + when(builder.getParams()).thenReturn(ubiParameters); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.ext(builders); + + when(request.source()).thenReturn(searchSourceBuilder); + + ubiActionFilter.apply(task, "ubi", request, listener, chain); + + verify(client, never()).index(any(), any()); + + } + +} diff --git a/modules/ubi/src/test/java/org.opensearch.ubi/UbiModulePluginTests.java b/modules/ubi/src/test/java/org.opensearch.ubi/UbiModulePluginTests.java new file mode 100644 index 0000000000000..41cc0534952a3 --- /dev/null +++ b/modules/ubi/src/test/java/org.opensearch.ubi/UbiModulePluginTests.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi; + +import org.opensearch.client.Client; +import org.opensearch.plugins.SearchPlugin; +import org.opensearch.test.OpenSearchTestCase; +import org.junit.Assert; +import org.junit.Before; + +import java.util.List; + +import static org.mockito.Mockito.mock; + +public class UbiModulePluginTests extends OpenSearchTestCase { + + private UbiModulePlugin ubiModulePlugin; + + private final Client client = mock(Client.class); + + @Before + public void setup() { + ubiModulePlugin = new UbiModulePlugin(); + } + + public void testCreateComponent() { + List components = (List) ubiModulePlugin.createComponents( + client, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + Assert.assertEquals(0, components.size()); + } + + public void testGetSearchExts() { + + final List> searchExts = ubiModulePlugin.getSearchExts(); + Assert.assertEquals(1, searchExts.size()); + + } + +} diff --git a/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbIParametersTests.java b/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbIParametersTests.java new file mode 100644 index 0000000000000..e4518cd1c83ec --- /dev/null +++ b/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbIParametersTests.java @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi.ext; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentGenerator; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UbIParametersTests extends OpenSearchTestCase { + + static class DummyStreamOutput extends StreamOutput { + + List list = new ArrayList<>(); + List intValues = new ArrayList<>(); + + @Override + public void writeString(String str) { + list.add(str); + } + + public List getList() { + return list; + } + + @Override + public void writeInt(int i) { + intValues.add(i); + } + + public List getIntValues() { + return this.intValues; + } + + @Override + public void writeByte(byte b) throws IOException { + + } + + @Override + public void writeBytes(byte[] b, int offset, int length) throws IOException { + + } + + @Override + public void flush() throws IOException { + + } + + @Override + public void close() throws IOException { + + } + + @Override + public void reset() throws IOException { + + } + } + + public void testUbiParameters() { + final UbiParameters params = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + UbiParametersExtBuilder extBuilder = new UbiParametersExtBuilder(); + extBuilder.setParams(params); + SearchSourceBuilder srcBulder = SearchSourceBuilder.searchSource().ext(List.of(extBuilder)); + SearchRequest request = new SearchRequest("my_index").source(srcBulder); + UbiParameters actual = UbiParameters.getUbiParameters(request); + assertEquals(params, actual); + } + + public void testWriteTo() throws IOException { + final UbiParameters params = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + StreamOutput output = new DummyStreamOutput(); + params.writeTo(output); + List actual = ((DummyStreamOutput) output).getList(); + assertEquals("query_id", actual.get(0)); + assertEquals("user_query", actual.get(1)); + assertEquals("client_id", actual.get(2)); + assertEquals("object_id", actual.get(3)); + } + + public void testToXContent() throws IOException { + final UbiParameters params = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + XContent xc = mock(XContent.class); + OutputStream os = mock(OutputStream.class); + XContentGenerator generator = mock(XContentGenerator.class); + when(xc.createGenerator(any(), any(), any())).thenReturn(generator); + XContentBuilder builder = new XContentBuilder(xc, os); + assertNotNull(params.toXContent(builder, null)); + } + + public void testToXContentAllOptionalParameters() throws IOException { + final UbiParameters params = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + XContent xc = mock(XContent.class); + OutputStream os = mock(OutputStream.class); + XContentGenerator generator = mock(XContentGenerator.class); + when(xc.createGenerator(any(), any(), any())).thenReturn(generator); + XContentBuilder builder = new XContentBuilder(xc, os); + assertNotNull(params.toXContent(builder, null)); + } + +} diff --git a/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbiParametersExtBuilderTests.java b/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbiParametersExtBuilderTests.java new file mode 100644 index 0000000000000..8ccc04c7820ce --- /dev/null +++ b/modules/ubi/src/test/java/org/opensearch/ubi/ext/UbiParametersExtBuilderTests.java @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.ubi.ext; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.XContentHelper; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.EOFException; +import java.io.IOException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UbiParametersExtBuilderTests extends OpenSearchTestCase { + + public void testCtor() throws IOException { + + final UbiParametersExtBuilder builder = new UbiParametersExtBuilder(); + final UbiParameters parameters = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + builder.setParams(parameters); + assertEquals(parameters, builder.getParams()); + + final UbiParametersExtBuilder builder1 = new UbiParametersExtBuilder(new StreamInput() { + @Override + public byte readByte() throws IOException { + return 0; + } + + @Override + public void readBytes(byte[] b, int offset, int len) throws IOException { + + } + + @Override + public void close() throws IOException { + + } + + @Override + public int available() throws IOException { + return 0; + } + + @Override + protected void ensureCanReadBytes(int length) throws EOFException { + + } + + @Override + public int read() throws IOException { + return 0; + } + }); + + assertNotNull(builder1); + + } + + public void testParse() throws IOException { + XContentParser xcParser = mock(XContentParser.class); + when(xcParser.nextToken()).thenReturn(XContentParser.Token.START_OBJECT).thenReturn(XContentParser.Token.END_OBJECT); + UbiParametersExtBuilder builder = UbiParametersExtBuilder.parse(xcParser); + assertNotNull(builder); + assertNotNull(builder.getParams()); + } + + public void testXContentRoundTrip() throws IOException { + UbiParameters param1 = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + UbiParametersExtBuilder extBuilder = new UbiParametersExtBuilder(); + extBuilder.setParams(param1); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference serialized = XContentHelper.toXContent(extBuilder, xContentType, true); + XContentParser parser = createParser(xContentType.xContent(), serialized); + UbiParametersExtBuilder deserialized = UbiParametersExtBuilder.parse(parser); + assertEquals(extBuilder, deserialized); + UbiParameters parameters = deserialized.getParams(); + assertEquals("query_id", parameters.getQueryId()); + assertEquals("user_query", parameters.getUserQuery()); + assertEquals("client_id", parameters.getClientId()); + assertEquals("object_id", parameters.getObjectId()); + } + + public void testXContentRoundTripAllValues() throws IOException { + UbiParameters param1 = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + UbiParametersExtBuilder extBuilder = new UbiParametersExtBuilder(); + extBuilder.setParams(param1); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference serialized = XContentHelper.toXContent(extBuilder, xContentType, true); + XContentParser parser = createParser(xContentType.xContent(), serialized); + UbiParametersExtBuilder deserialized = UbiParametersExtBuilder.parse(parser); + assertEquals(extBuilder, deserialized); + } + + public void testStreamRoundTrip() throws IOException { + UbiParameters param1 = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + UbiParametersExtBuilder extBuilder = new UbiParametersExtBuilder(); + extBuilder.setParams(param1); + BytesStreamOutput bso = new BytesStreamOutput(); + extBuilder.writeTo(bso); + UbiParametersExtBuilder deserialized = new UbiParametersExtBuilder(bso.bytes().streamInput()); + assertEquals(extBuilder, deserialized); + UbiParameters parameters = deserialized.getParams(); + assertEquals("query_id", parameters.getQueryId()); + assertEquals("user_query", parameters.getUserQuery()); + assertEquals("client_id", parameters.getClientId()); + assertEquals("object_id", parameters.getObjectId()); + } + + public void testStreamRoundTripAllValues() throws IOException { + UbiParameters param1 = new UbiParameters("query_id", "user_query", "client_id", "object_id"); + UbiParametersExtBuilder extBuilder = new UbiParametersExtBuilder(); + extBuilder.setParams(param1); + BytesStreamOutput bso = new BytesStreamOutput(); + extBuilder.writeTo(bso); + UbiParametersExtBuilder deserialized = new UbiParametersExtBuilder(bso.bytes().streamInput()); + assertEquals(extBuilder, deserialized); + } + +} diff --git a/modules/ubi/src/yamlRestTest/java/org/opensearch/ubi/UbiYamlTestSuiteIT.java b/modules/ubi/src/yamlRestTest/java/org/opensearch/ubi/UbiYamlTestSuiteIT.java new file mode 100644 index 0000000000000..7cec5e988f1bb --- /dev/null +++ b/modules/ubi/src/yamlRestTest/java/org/opensearch/ubi/UbiYamlTestSuiteIT.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package org.opensearch.ubi; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.opensearch.test.rest.yaml.ClientYamlTestCandidate; +import org.opensearch.test.rest.yaml.OpenSearchClientYamlSuiteTestCase; + +public class UbiYamlTestSuiteIT extends OpenSearchClientYamlSuiteTestCase { + + public UbiYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + @ParametersFactory + public static Iterable parameters() throws Exception { + return OpenSearchClientYamlSuiteTestCase.createParameters(); + } + +} diff --git a/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/10_queries_without_ubi.yml b/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/10_queries_without_ubi.yml new file mode 100644 index 0000000000000..c8ce4149a54c9 --- /dev/null +++ b/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/10_queries_without_ubi.yml @@ -0,0 +1,77 @@ +--- +"Query without ubi block": + + - do: + indices.create: + index: ecommerce + body: + mappings: + { "properties": { "category": { "type": "text" } } } + + - match: { acknowledged: true } + - match: { index: "ecommerce"} + + - do: + index: + index: ecommerce + id: 1 + body: { category: notebook } + + - match: { result: created } + + - do: + indices.refresh: + index: [ "ecommerce" ] + + - do: + search: + rest_total_hits_as_int: true + index: ecommerce + body: "{\"query\": {\"match\": {\"category\": \"notebook\"}}}" + + - gte: { hits.total: 1 } + + - do: + indices.exists: + index: ubi_queries + + - is_false: '' + +--- +"Query without query_id": + + - do: + indices.create: + index: ecommerce + body: + mappings: + { "properties": { "category": { "type": "text" } } } + + - match: { acknowledged: true } + - match: { index: "ecommerce"} + + - do: + index: + index: ecommerce + id: 1 + body: { category: notebook } + + - match: { result: created } + + - do: + indices.refresh: + index: [ "ecommerce" ] + + - do: + search: + rest_total_hits_as_int: true + index: ecommerce + body: "{\"query\": {\"match\": {\"category\": \"notebook\"}}, \"ext\": {\"ubi\": {\"user_query\": \"notebook\"}}}" + + - gte: { hits.total: 1 } + + - do: + indices.exists: + index: ubi_queries + + - is_false: '' diff --git a/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/20_queries_with_ubi.yml b/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/20_queries_with_ubi.yml new file mode 100644 index 0000000000000..fa0e22f966496 --- /dev/null +++ b/modules/ubi/src/yamlRestTest/resources/rest-api-spec/test/_plugins.ubi/20_queries_with_ubi.yml @@ -0,0 +1,54 @@ +--- +"Query": + + - do: + indices.create: + index: ecommerce + body: + mappings: + { "properties": { "category": { "type": "text" } } } + + - match: { acknowledged: true } + - match: { index: "ecommerce"} + + - do: + index: + index: ecommerce + id: 1 + body: { category: notebook } + + - match: { result: created } + + - do: + indices.refresh: + index: [ "ecommerce" ] + + - do: + search: + rest_total_hits_as_int: true + index: ecommerce + body: "{\"query\": {\"match\": {\"category\": \"notebook\"}}, \"ext\": {\"ubi\": {\"query_id\": \"1234512345\", \"client_id\": \"abcabc\", \"user_query\": \"notebook\"}}}" + + - gte: { hits.total: 1 } + + - do: + cluster.health: + index: [ubi_queries] + wait_for_no_initializing_shards: true + + - do: + indices.exists: + index: ubi_queries + + - is_true: '' + + - do: + indices.refresh: + index: [ "ubi_queries" ] + + - do: + count: + index: ubi_queries + body: {} + + - match: {count : 1}