Bläddra i källkod

[Connector API] Support filtering by name, index name in list action (#105131)

Jedr Blaszyk 1 år sedan
förälder
incheckning
6054ca36cf

+ 5 - 0
docs/changelog/105131.yaml

@@ -0,0 +1,5 @@
+pr: 105131
+summary: "[Connector API] Support filtering by name, index name in list action"
+area: Application
+type: enhancement
+issues: []

+ 8 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/connector.list.json

@@ -31,6 +31,14 @@
         "type": "int",
         "default": 100,
         "description": "specifies a max number of results to get (default: 100)"
+      },
+      "index_name": {
+        "type": "string",
+        "description": "connector index name(s) to fetch connector documents for"
+      },
+      "connector_name": {
+        "type": "string",
+        "description": "connector name(s) to fetch connector documents for"
       }
     }
   }

+ 61 - 3
x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/310_connector_list.yml

@@ -9,7 +9,7 @@ setup:
         connector_id: connector-a
         body:
           index_name: search-1-test
-          name: my-connector
+          name: my-connector-1
           language: pl
           is_native: false
           service_type: super-connector
@@ -18,7 +18,7 @@ setup:
         connector_id: connector-c
         body:
           index_name: search-3-test
-          name: my-connector
+          name: my-connector-3
           language: nl
           is_native: false
           service_type: super-connector
@@ -27,7 +27,7 @@ setup:
         connector_id: connector-b
         body:
           index_name: search-2-test
-          name: my-connector
+          name: my-connector-2
           language: en
           is_native: true
           service_type: super-connector
@@ -106,3 +106,61 @@ setup:
 
   - match: { count: 0 }
 
+
+---
+"List Connector - filter by index names":
+  - do:
+      connector.list:
+        index_name: search-1-test
+
+  - match: { count: 1 }
+  - match: { results.0.index_name: "search-1-test" }
+
+  - do:
+      connector.list:
+        index_name: search-1-test,search-2-test
+
+  - match: { count: 2 }
+  - match: { results.0.index_name: "search-1-test" }
+  - match: { results.1.index_name: "search-2-test" }
+
+
+---
+"List Connector - filter by index names, illegal name":
+  - do:
+      catch: "bad_request"
+      connector.list:
+        index_name: ~.!$$#index-name$$$
+
+
+---
+"List Connector - filter by connector names":
+  - do:
+      connector.list:
+        connector_name: my-connector-1
+
+  - match: { count: 1 }
+  - match: { results.0.name: "my-connector-1" }
+
+  - do:
+      connector.list:
+        connector_name: my-connector-1,my-connector-2
+
+  - match: { count: 2 }
+  - match: { results.0.name: "my-connector-1" }
+  - match: { results.1.name: "my-connector-2" }
+
+
+---
+"List Connector - filter by index name and name":
+  - do:
+      connector.list:
+        connector_name: my-connector-1,my-connector-2
+        index_name: search-2-test
+
+  - match: { count: 1 }
+  - match: { results.0.index_name: "search-2-test" }
+  - match: { results.0.name: "my-connector-2" }
+
+
+

+ 39 - 2
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java

@@ -29,7 +29,9 @@ import org.elasticsearch.index.IndexNotFoundException;
 import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.IdsQueryBuilder;
 import org.elasticsearch.index.query.MatchAllQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.TermQueryBuilder;
+import org.elasticsearch.index.query.TermsQueryBuilder;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.ScriptType;
@@ -280,13 +282,21 @@ public class ConnectorIndexService {
      *
      * @param from From index to start the search from.
      * @param size The maximum number of {@link Connector}s to return.
+     * @param indexNames A list of index names to filter the connectors.
+     * @param connectorNames A list of connector names to further filter the search results.
      * @param listener The action listener to invoke on response/failure.
      */
-    public void listConnectors(int from, int size, ActionListener<ConnectorIndexService.ConnectorResult> listener) {
+    public void listConnectors(
+        int from,
+        int size,
+        List<String> indexNames,
+        List<String> connectorNames,
+        ActionListener<ConnectorIndexService.ConnectorResult> listener
+    ) {
         try {
             final SearchSourceBuilder source = new SearchSourceBuilder().from(from)
                 .size(size)
-                .query(new MatchAllQueryBuilder())
+                .query(buildListQuery(indexNames, connectorNames))
                 .fetchSource(true)
                 .sort(Connector.INDEX_NAME_FIELD.getPreferredName(), SortOrder.ASC);
             final SearchRequest req = new SearchRequest(CONNECTOR_INDEX_NAME).source(source);
@@ -314,6 +324,33 @@ public class ConnectorIndexService {
         }
     }
 
+    /**
+     * Constructs a query for filtering instances of {@link Connector} based on index and/or connector names.
+     * Returns a {@link MatchAllQueryBuilder} if both parameters are empty or null,
+     * otherwise constructs a boolean query to filter by the provided lists.
+     *
+     * @param indexNames List of index names to filter by, or null/empty for no index name filtering.
+     * @param connectorNames List of connector names to filter by, or null/empty for no name filtering.
+     * @return A {@link QueryBuilder} tailored to the specified filters.
+     */
+    private QueryBuilder buildListQuery(List<String> indexNames, List<String> connectorNames) {
+        boolean filterByIndexNames = indexNames != null && indexNames.isEmpty() == false;
+        boolean filterByConnectorNames = indexNames != null && connectorNames.isEmpty() == false;
+        boolean usesFilter = filterByIndexNames || filterByConnectorNames;
+
+        BoolQueryBuilder boolFilterQueryBuilder = new BoolQueryBuilder();
+
+        if (usesFilter) {
+            if (filterByIndexNames) {
+                boolFilterQueryBuilder.must().add(new TermsQueryBuilder(Connector.INDEX_NAME_FIELD.getPreferredName(), indexNames));
+            }
+            if (filterByConnectorNames) {
+                boolFilterQueryBuilder.must().add(new TermsQueryBuilder(Connector.NAME_FIELD.getPreferredName(), connectorNames));
+            }
+        }
+        return usesFilter ? boolFilterQueryBuilder : new MatchAllQueryBuilder();
+    }
+
     /**
      * Updates the {@link ConnectorConfiguration} property of a {@link Connector}.
      * The update process is non-additive; it completely replaces all existing configuration fields with the new configuration mapping,

+ 49 - 7
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/ListConnectorAction.java

@@ -11,8 +11,10 @@ import org.elasticsearch.action.ActionRequest;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.action.ActionType;
+import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.indices.InvalidIndexNameException;
 import org.elasticsearch.xcontent.ConstructingObjectParser;
 import org.elasticsearch.xcontent.ParseField;
 import org.elasticsearch.xcontent.ToXContentObject;
@@ -26,7 +28,9 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Objects;
 
+import static org.elasticsearch.action.ValidateActions.addValidationError;
 import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
 
 public class ListConnectorAction {
 
@@ -38,54 +42,88 @@ public class ListConnectorAction {
     public static class Request extends ActionRequest implements ToXContentObject {
 
         private final PageParams pageParams;
+        private final List<String> indexNames;
+        private final List<String> connectorNames;
 
         private static final ParseField PAGE_PARAMS_FIELD = new ParseField("pageParams");
+        private static final ParseField INDEX_NAMES_FIELD = new ParseField("index_names");
+        private static final ParseField NAMES_FIELD = new ParseField("names");
 
         public Request(StreamInput in) throws IOException {
             super(in);
             this.pageParams = new PageParams(in);
+            this.indexNames = in.readOptionalStringCollectionAsList();
+            this.connectorNames = in.readOptionalStringCollectionAsList();
         }
 
-        public Request(PageParams pageParams) {
+        public Request(PageParams pageParams, List<String> indexNames, List<String> connectorNames) {
             this.pageParams = pageParams;
+            this.indexNames = indexNames;
+            this.connectorNames = connectorNames;
         }
 
         public PageParams getPageParams() {
             return pageParams;
         }
 
+        public List<String> getIndexNames() {
+            return indexNames;
+        }
+
+        public List<String> getConnectorNames() {
+            return connectorNames;
+        }
+
         @Override
         public ActionRequestValidationException validate() {
+            ActionRequestValidationException validationException = null;
             // Pagination validation is done as part of PageParams constructor
-            return null;
+
+            if (indexNames != null && indexNames.isEmpty() == false) {
+                for (String indexName : indexNames) {
+                    try {
+                        MetadataCreateIndexService.validateIndexOrAliasName(indexName, InvalidIndexNameException::new);
+                    } catch (InvalidIndexNameException e) {
+                        validationException = addValidationError(e.toString(), validationException);
+                    }
+                }
+            }
+            return validationException;
         }
 
         @Override
         public void writeTo(StreamOutput out) throws IOException {
             super.writeTo(out);
             pageParams.writeTo(out);
+            out.writeOptionalStringCollection(indexNames);
+            out.writeOptionalStringCollection(connectorNames);
         }
 
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
-            ListConnectorAction.Request that = (ListConnectorAction.Request) o;
-            return Objects.equals(pageParams, that.pageParams);
+            ListConnectorAction.Request request = (ListConnectorAction.Request) o;
+            return Objects.equals(pageParams, request.pageParams)
+                && Objects.equals(indexNames, request.indexNames)
+                && Objects.equals(connectorNames, request.connectorNames);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(pageParams);
+            return Objects.hash(pageParams, indexNames, connectorNames);
         }
 
+        @SuppressWarnings("unchecked")
         private static final ConstructingObjectParser<ListConnectorAction.Request, String> PARSER = new ConstructingObjectParser<>(
             "list_connector_request",
-            p -> new ListConnectorAction.Request((PageParams) p[0])
+            p -> new ListConnectorAction.Request((PageParams) p[0], (List<String>) p[1], (List<String>) p[2])
         );
 
         static {
             PARSER.declareObject(constructorArg(), (p, c) -> PageParams.fromXContent(p), PAGE_PARAMS_FIELD);
+            PARSER.declareStringArray(optionalConstructorArg(), INDEX_NAMES_FIELD);
+            PARSER.declareStringArray(optionalConstructorArg(), NAMES_FIELD);
         }
 
         public static ListConnectorAction.Request parse(XContentParser parser) {
@@ -95,7 +133,11 @@ public class ListConnectorAction {
         @Override
         public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
             builder.startObject();
-            builder.field(PAGE_PARAMS_FIELD.getPreferredName(), pageParams);
+            {
+                builder.field(PAGE_PARAMS_FIELD.getPreferredName(), pageParams);
+                builder.field(INDEX_NAMES_FIELD.getPreferredName(), indexNames);
+                builder.field(NAMES_FIELD.getPreferredName(), connectorNames);
+            }
             builder.endObject();
             return builder;
         }

+ 5 - 1
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestListConnectorAction.java

@@ -14,6 +14,7 @@ import org.elasticsearch.rest.Scope;
 import org.elasticsearch.rest.ServerlessScope;
 import org.elasticsearch.rest.action.RestToXContentListener;
 import org.elasticsearch.xpack.application.EnterpriseSearch;
+import org.elasticsearch.xpack.application.connector.Connector;
 import org.elasticsearch.xpack.core.action.util.PageParams;
 
 import java.io.IOException;
@@ -38,7 +39,10 @@ public class RestListConnectorAction extends BaseRestHandler {
     protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
         int from = restRequest.paramAsInt("from", PageParams.DEFAULT_FROM);
         int size = restRequest.paramAsInt("size", PageParams.DEFAULT_SIZE);
-        ListConnectorAction.Request request = new ListConnectorAction.Request(new PageParams(from, size));
+        List<String> indexNames = List.of(restRequest.paramAsStringArray(Connector.INDEX_NAME_FIELD.getPreferredName(), new String[0]));
+        List<String> connectorNames = List.of(restRequest.paramAsStringArray("connector_name", new String[0]));
+
+        ListConnectorAction.Request request = new ListConnectorAction.Request(new PageParams(from, size), indexNames, connectorNames);
 
         return channel -> client.execute(ListConnectorAction.INSTANCE, request, new RestToXContentListener<>(channel));
     }

+ 3 - 0
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportListConnectorAction.java

@@ -42,9 +42,12 @@ public class TransportListConnectorAction extends HandledTransportAction<ListCon
     @Override
     protected void doExecute(Task task, ListConnectorAction.Request request, ActionListener<ListConnectorAction.Response> listener) {
         final PageParams pageParams = request.getPageParams();
+
         connectorIndexService.listConnectors(
             pageParams.getFrom(),
             pageParams.getSize(),
+            request.getIndexNames(),
+            request.getConnectorNames(),
             listener.map(r -> new ListConnectorAction.Response(r.connectors(), r.totalResults()))
         );
     }

+ 3 - 2
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java

@@ -534,11 +534,12 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
         return resp.get();
     }
 
-    private ConnectorIndexService.ConnectorResult awaitListConnector(int from, int size) throws Exception {
+    private ConnectorIndexService.ConnectorResult awaitListConnector(int from, int size, List<String> indexNames, List<String> names)
+        throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         final AtomicReference<ConnectorIndexService.ConnectorResult> resp = new AtomicReference<>(null);
         final AtomicReference<Exception> exc = new AtomicReference<>(null);
-        connectorIndexService.listConnectors(from, size, new ActionListener<>() {
+        connectorIndexService.listConnectors(from, size, indexNames, names, new ActionListener<>() {
             @Override
             public void onResponse(ConnectorIndexService.ConnectorResult result) {
                 resp.set(result);

+ 7 - 2
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/ListConnectorActionRequestBWCSerializingTests.java

@@ -15,6 +15,7 @@ import org.elasticsearch.xpack.core.action.util.PageParams;
 import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase;
 
 import java.io.IOException;
+import java.util.List;
 
 public class ListConnectorActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase<ListConnectorAction.Request> {
     @Override
@@ -25,7 +26,11 @@ public class ListConnectorActionRequestBWCSerializingTests extends AbstractBWCSe
     @Override
     protected ListConnectorAction.Request createTestInstance() {
         PageParams pageParams = SearchApplicationTestUtils.randomPageParams();
-        return new ListConnectorAction.Request(pageParams);
+        return new ListConnectorAction.Request(
+            pageParams,
+            List.of(generateRandomStringArray(10, 10, false)),
+            List.of(generateRandomStringArray(10, 10, false))
+        );
     }
 
     @Override
@@ -40,6 +45,6 @@ public class ListConnectorActionRequestBWCSerializingTests extends AbstractBWCSe
 
     @Override
     protected ListConnectorAction.Request mutateInstanceForVersion(ListConnectorAction.Request instance, TransportVersion version) {
-        return new ListConnectorAction.Request(instance.getPageParams());
+        return new ListConnectorAction.Request(instance.getPageParams(), instance.getIndexNames(), instance.getConnectorNames());
     }
 }