Skip to content

Commit

Permalink
Adding rest and unit test for ubi without a query_id. Requiring ubi b…
Browse files Browse the repository at this point in the history
…lock to have a query_id.

Signed-off-by: jzonthemtn <[email protected]>
  • Loading branch information
jzonthemtn committed May 7, 2024
1 parent 2fb835f commit 333e45e
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 80 deletions.
46 changes: 41 additions & 5 deletions modules/ubi/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# User Behavior Insights (UBI)

UBI facilitates storing queries and events for the purposes of improving search relevance as descrbed by [[RFC] User Behavior Insights](https://github.com/opensearch-project/OpenSearch/issues/12084).
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

Expand All @@ -25,8 +31,9 @@ curl -s http://localhost:9200/ecommerce/_search -H "Content-type: application/js
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 would look like:
With these optional values, a sample query is:

```
curl -s http://localhost:9200/ecommerce/_search -H "Content-type: application/json" -d'
Expand All @@ -46,9 +53,7 @@ curl -s http://localhost:9200/ecommerce/_search -H "Content-type: application/js
}
```

If a search request does not contain a `ubi` block in `ext`, the query will *not* be indexed.

Queries are indexed into an index called `ubi_queries`.
If a search request does not contain a `ubi` block containing a `query_id` in `ext`, the query will *not* be indexed.

## Indexing Events

Expand All @@ -57,3 +62,34 @@ adding a product to a cart, or other actions. UBI indexes these events in an ind
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
```
125 changes: 67 additions & 58 deletions modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,34 @@ public void onResponse(Response response) {
if (ubiParameters != null) {

final String queryId = ubiParameters.getQueryId();
final String userQuery = ubiParameters.getUserQuery();
final String userId = ubiParameters.getClientId();
final String objectId = ubiParameters.getObjectId();

final List<String> queryResponseHitIds = new LinkedList<>();
if (queryId != null) {

for (final SearchHit hit : ((SearchResponse) response).getHits()) {
final String userQuery = ubiParameters.getUserQuery();
final String clientId = ubiParameters.getClientId();
final String objectId = ubiParameters.getObjectId();

Check warning on line 106 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L104-L106

Added lines #L104 - L106 were not covered by tests

final List<String> queryResponseHitIds = new LinkedList<>();

Check warning on line 108 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L108

Added line #L108 was not covered by tests

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

Check warning on line 114 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L114

Added line #L114 was not covered by tests
} else {
final Map<String, Object> source = hit.getSourceAsMap();
queryResponseHitIds.add((String) source.get(objectId));

Check warning on line 117 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L116-L117

Added lines #L116 - L117 were not covered by tests
}

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<String, Object> source = hit.getSourceAsMap();
queryResponseHitIds.add((String) source.get(objectId));
}

Check warning on line 120 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L120

Added line #L120 was not covered by tests

}
final String queryResponseId = UUID.randomUUID().toString();
final QueryResponse queryResponse = new QueryResponse(queryId, queryResponseId, queryResponseHitIds);
final QueryRequest queryRequest = new QueryRequest(queryId, userQuery, clientId, queryResponse);

Check warning on line 124 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L122-L124

Added lines #L122 - L124 were not covered by tests

final String queryResponseId = UUID.randomUUID().toString();
final QueryResponse queryResponse = new QueryResponse(queryId, queryResponseId, queryResponseHitIds);
final QueryRequest queryRequest = new QueryRequest(queryId, userQuery, userId, queryResponse);
indexQuery(queryRequest);

Check warning on line 126 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L126

Added line #L126 was not covered by tests

indexUbiQuery(queryRequest);
}

}

Expand All @@ -139,7 +144,7 @@ public void onFailure(Exception ex) {

}

private void indexUbiQuery(final QueryRequest queryRequest) {
private void indexQuery(final QueryRequest queryRequest) {

final IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(UBI_EVENTS_INDEX, UBI_QUERIES_INDEX);

Check warning on line 149 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L149

Added line #L149 was not covered by tests

Expand All @@ -148,61 +153,65 @@ private void indexUbiQuery(final QueryRequest queryRequest) {
@Override
public void onResponse(IndicesExistsResponse indicesExistsResponse) {

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();
if (!indicesExistsResponse.isExists()) {

// Create the UBI events index.
final CreateIndexRequest createEventsIndexRequest = new CreateIndexRequest(UBI_EVENTS_INDEX).mapping(
getResourceFile(EVENTS_MAPPING_FILE)
).settings(indexSettings);
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();

Check warning on line 162 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L158-L162

Added lines #L158 - L162 were not covered by tests

client.admin().indices().create(createEventsIndexRequest);
// Create the UBI events index.
final CreateIndexRequest createEventsIndexRequest = new CreateIndexRequest(UBI_EVENTS_INDEX).mapping(
getResourceFile(EVENTS_MAPPING_FILE)
).settings(indexSettings);

Check warning on line 167 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L165-L167

Added lines #L165 - L167 were not covered by tests

// Create the UBI queries index.
final CreateIndexRequest createQueriesIndexRequest = new CreateIndexRequest(UBI_QUERIES_INDEX).mapping(
getResourceFile(QUERIES_MAPPING_FILE)
).settings(indexSettings);
client.admin().indices().create(createEventsIndexRequest);

Check warning on line 169 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L169

Added line #L169 was not covered by tests

client.admin().indices().create(createQueriesIndexRequest);
// Create the UBI queries index.
final CreateIndexRequest createQueriesIndexRequest = new CreateIndexRequest(UBI_QUERIES_INDEX).mapping(
getResourceFile(QUERIES_MAPPING_FILE)
).settings(indexSettings);

Check warning on line 174 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L172-L174

Added lines #L172 - L174 were not covered by tests

}
client.admin().indices().create(createQueriesIndexRequest);

Check warning on line 176 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L176

Added line #L176 was not covered by tests

@Override
public void onFailure(Exception ex) {
LOGGER.error("Error creating UBI indexes.", ex);
}
}

});
LOGGER.debug(

Check warning on line 180 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L180

Added line #L180 was not covered by tests
"Indexing query ID {} with response ID {}",
queryRequest.getQueryId(),
queryRequest.getQueryResponse().getQueryResponseId()

Check warning on line 183 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L182-L183

Added lines #L182 - L183 were not covered by tests
);

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<String, Object> 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());

Check warning on line 193 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L187-L193

Added lines #L187 - L193 were not covered by tests

// What will be indexed - adheres to the queries-mapping.json
final Map<String, Object> 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);

Check warning on line 196 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L196

Added line #L196 was not covered by tests

// Build the index request.
final IndexRequest indexRequest = new IndexRequest(UBI_QUERIES_INDEX).source(source, XContentType.JSON);
client.index(indexRequest, new ActionListener<>() {

Check warning on line 198 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L198

Added line #L198 was not covered by tests

client.index(indexRequest, new ActionListener<>() {
@Override
public void onResponse(IndexResponse indexResponse) {}

Check warning on line 201 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L201

Added line #L201 was not covered by tests

@Override
public void onResponse(IndexResponse indexResponse) {}
@Override
public void onFailure(Exception e) {
LOGGER.error("Unable to index query into UBI index.", e);
}

Check warning on line 206 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L205-L206

Added lines #L205 - L206 were not covered by tests

});

}

Check warning on line 210 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L210

Added line #L210 was not covered by tests

@Override
public void onFailure(Exception e) {
LOGGER.error("Unable to index query into UBI index.", e);
public void onFailure(Exception ex) {
LOGGER.error("Error creating UBI indexes.", ex);
}

Check warning on line 215 in modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java

View check run for this annotation

Codecov / codecov/patch

modules/ubi/src/main/java/org/opensearch/ubi/UbiActionFilter.java#L214-L215

Added lines #L214 - L215 were not covered by tests

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

/**
* The UBI parameters available in the ext.
Expand Down Expand Up @@ -117,7 +116,7 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(queryId);
out.writeString(getQueryId());
out.writeOptionalString(userQuery);
out.writeOptionalString(clientId);
out.writeOptionalString(objectId);
Expand Down Expand Up @@ -159,11 +158,7 @@ public int hashCode() {
* @return The query ID, or a random UUID if the query ID is <code>null</code>.
*/
public String getQueryId() {
if (queryId == null || queryId.isEmpty()) {
return UUID.randomUUID().toString();
} else {
return queryId;
}
return queryId;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down Expand Up @@ -42,7 +43,7 @@
public class UbiActionFilterTests extends OpenSearchTestCase {

@SuppressWarnings("unchecked")
public void testApplyWithUbi() {
public void testApplyWithoutUbiBlock() {

final Client client = mock(Client.class);
final AdminClient adminClient = mock(AdminClient.class);
Expand All @@ -52,7 +53,7 @@ public void testApplyWithUbi() {
when(adminClient.indices()).thenReturn(indicesAdminClient);

final ActionFuture<IndicesExistsResponse> actionFuture = mock(ActionFuture.class);
when(indicesAdminClient.exists(any())).thenReturn(actionFuture);
when(indicesAdminClient.exists(any(IndicesExistsRequest.class))).thenReturn(actionFuture);

final UbiActionFilter ubiActionFilter = new UbiActionFilter(client);
final ActionListener<SearchResponse> listener = mock(ActionListener.class);
Expand All @@ -74,14 +75,12 @@ public void testApplyWithUbi() {
return null;
}).when(chain).proceed(eq(task), anyString(), eq(request), any());

final UbiParameters params = new UbiParameters("query_id", "user_query", "client_id", "object_id");

UbiParametersExtBuilder builder = mock(UbiParametersExtBuilder.class);
final List<SearchExtBuilder> builders = new ArrayList<>();
builders.add(builder);

when(builder.getWriteableName()).thenReturn(UbiParametersExtBuilder.UBI_PARAMETER_NAME);
when(builder.getParams()).thenReturn(params);
when(builder.getParams()).thenReturn(null);

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.ext(builders);
Expand All @@ -90,12 +89,12 @@ public void testApplyWithUbi() {

ubiActionFilter.apply(task, "ubi", request, listener, chain);

verify(client).index(any(), any());
verify(client, never()).index(any(), any());

}

@SuppressWarnings("unchecked")
public void testApplyWithoutUbi() {
public void testApplyWithUbiBlockWithoutQueryId() {

final Client client = mock(Client.class);
final AdminClient adminClient = mock(AdminClient.class);
Expand All @@ -105,7 +104,7 @@ public void testApplyWithoutUbi() {
when(adminClient.indices()).thenReturn(indicesAdminClient);

final ActionFuture<IndicesExistsResponse> actionFuture = mock(ActionFuture.class);
when(indicesAdminClient.exists(any())).thenReturn(actionFuture);
when(indicesAdminClient.exists(any(IndicesExistsRequest.class))).thenReturn(actionFuture);

final UbiActionFilter ubiActionFilter = new UbiActionFilter(client);
final ActionListener<SearchResponse> listener = mock(ActionListener.class);
Expand All @@ -131,8 +130,10 @@ public void testApplyWithoutUbi() {
final List<SearchExtBuilder> builders = new ArrayList<>();
builders.add(builder);

final UbiParameters ubiParameters = new UbiParameters();

when(builder.getWriteableName()).thenReturn(UbiParametersExtBuilder.UBI_PARAMETER_NAME);
when(builder.getParams()).thenReturn(null);
when(builder.getParams()).thenReturn(ubiParameters);

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.ext(builders);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"Query":
"Query without ubi block":

- do:
indices.create:
Expand Down Expand Up @@ -36,3 +36,42 @@
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: ''

0 comments on commit 333e45e

Please sign in to comment.