Browse Source

[Connector API] Add update filtering validation and activate draft endpoints (#107457)

Jedr Blaszyk 1 year ago
parent
commit
30be6c8f84
18 changed files with 1016 additions and 10 deletions
  1. 34 0
      rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_active_filtering.json
  2. 38 0
      rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_filtering_validation.json
  3. 146 0
      x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/332_connector_update_filtering.yml
  4. 13 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java
  5. 106 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java
  6. 46 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorActiveFilteringAction.java
  7. 48 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/RestUpdateConnectorFilteringValidationAction.java
  8. 55 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorActiveFilteringAction.java
  9. 56 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/TransportUpdateConnectorFilteringValidationAction.java
  10. 85 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorActiveFilteringAction.java
  11. 141 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFilteringValidationAction.java
  12. 1 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/filtering/FilteringRules.java
  13. 4 0
      x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/filtering/FilteringValidationInfo.java
  14. 119 0
      x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java
  15. 19 10
      x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java
  16. 51 0
      x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorActiveFilteringActionRequestBWCSerializingTests.java
  17. 52 0
      x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFilteringValidationActionRequestBWCSerializingTests.java
  18. 2 0
      x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java

+ 34 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_active_filtering.json

@@ -0,0 +1,34 @@
+{
+  "connector.update_active_filtering": {
+    "documentation": {
+      "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/update-connector-filtering-api.html",
+      "description": "Activates the draft filtering rules if they are in a validated state."
+    },
+    "stability": "experimental",
+    "visibility": "public",
+    "headers": {
+      "accept": [
+        "application/json"
+      ],
+      "content_type": [
+        "application/json"
+      ]
+    },
+    "url": {
+      "paths": [
+        {
+          "path": "/_connector/{connector_id}/_filtering/_activate",
+          "methods": [
+            "PUT"
+          ],
+          "parts": {
+            "connector_id": {
+              "type": "string",
+              "description": "The unique identifier of the connector to be updated."
+            }
+          }
+        }
+      ]
+    }
+  }
+}

+ 38 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/connector.update_filtering_validation.json

@@ -0,0 +1,38 @@
+{
+  "connector.update_filtering_validation": {
+    "documentation": {
+      "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/update-connector-filtering-api.html",
+      "description": "Updates the validation info of the draft filtering rules."
+    },
+    "stability": "experimental",
+    "visibility": "public",
+    "headers": {
+      "accept": [
+        "application/json"
+      ],
+      "content_type": [
+        "application/json"
+      ]
+    },
+    "url": {
+      "paths": [
+        {
+          "path": "/_connector/{connector_id}/_filtering/_validation",
+          "methods": [
+            "PUT"
+          ],
+          "parts": {
+            "connector_id": {
+              "type": "string",
+              "description": "The unique identifier of the connector to be updated."
+            }
+          }
+        }
+      ]
+    },
+    "body": {
+      "description": "Validation info for the draft filtering rules",
+      "required": true
+    }
+  }
+}

+ 146 - 0
x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/332_connector_update_filtering.yml

@@ -246,3 +246,149 @@ setup:
           advanced_snippet:
             updated_at: "wrong datetime"
             value: { }
+
+
+---
+"Update Connector Filtering - Update filtering draft validation":
+  - do:
+      connector.update_filtering_validation:
+        connector_id: test-connector
+        body:
+          validation:
+            state: invalid
+            errors:
+              - ids: ["1", "2"]
+                messages: ["some error 1", "some error 2"]
+              - ids: [ "3", "4" ]
+                messages: [ "some error 3", "some error 4" ]
+
+  - match: { result: updated }
+
+  - do:
+      connector.get:
+        connector_id: test-connector
+
+  - match: { filtering.0.draft.validation.state: invalid }
+  - match: { filtering.0.draft.validation.errors.0.ids.0: "1"}
+  - match: { filtering.0.draft.validation.errors.0.messages.1: "some error 2"}
+  - match: { filtering.0.draft.validation.errors.1.ids.0: "3"}
+  - match: { filtering.0.draft.validation.errors.1.messages.1: "some error 4"}
+
+
+---
+"Update Connector Filtering - Update validation missing validation state":
+  - do:
+      catch: "bad_request"
+      connector.update_filtering_validation:
+        connector_id: test-connector
+        body:
+          validation:
+            errors: []
+
+---
+"Update Connector Filtering - Update validation missing validation errors":
+  - do:
+      catch: "bad_request"
+      connector.update_filtering_validation:
+        connector_id: test-connector
+        body:
+          validation:
+            state: "valid"
+
+
+
+---
+"Update Connector Filtering - Activate valid draft":
+
+  - do:
+      connector.update_filtering:
+        connector_id: test-connector
+        body:
+          advanced_snippet:
+            created_at: "2023-05-25T12:30:00.000Z"
+            updated_at: "2023-05-25T12:30:00.000Z"
+            value:
+              - tables:
+                  - some_table
+                query: 'SELECT id, st_geohash(coordinates) FROM my_db.some_table;'
+          rules:
+            - created_at: "2023-06-25T12:30:00.000Z"
+              field: _
+              id: DEFAULT
+              order: 0
+              policy: include
+              rule: regex
+              updated_at: "2023-05-25T12:30:00.000Z"
+              value: ".*"
+
+  - match: { result: updated }
+
+  - do:
+      connector.update_filtering_validation:
+        connector_id: test-connector
+        body:
+          validation:
+            state: valid
+            errors: []
+
+  - match: { result: updated }
+
+
+  - do:
+      connector.update_active_filtering:
+        connector_id: test-connector
+
+  - match: { result: updated }
+
+  - do:
+      connector.get:
+        connector_id: test-connector
+
+  - match: { filtering.0.draft.advanced_snippet.created_at: "2023-05-25T12:30:00.000Z" }
+  - match: { filtering.0.draft.advanced_snippet.value.0.tables.0.: "some_table" }
+  - match: { filtering.0.draft.rules.0.created_at: "2023-06-25T12:30:00.000Z" }
+  - match: { filtering.0.active.advanced_snippet.created_at: "2023-05-25T12:30:00.000Z" }
+  - match: { filtering.0.active.advanced_snippet.value.0.tables.0.: "some_table" }
+  - match: { filtering.0.active.rules.0.created_at: "2023-06-25T12:30:00.000Z" }
+
+---
+"Update Connector Filtering - Activate invalid draft":
+
+  - do:
+      connector.update_filtering_validation:
+        connector_id: test-connector
+        body:
+          validation:
+            state: invalid
+            errors: []
+
+  - match: { result: updated }
+
+  - do:
+      catch: "bad_request"
+      connector.update_active_filtering:
+        connector_id: test-connector
+
+
+---
+"Update Connector Filtering - Activate edited draft":
+
+  - do:
+      connector.update_filtering:
+        connector_id: test-connector
+        body:
+          advanced_snippet:
+            created_at: "2023-05-25T12:30:00.000Z"
+            updated_at: "2023-05-25T12:30:00.000Z"
+            value:
+              - tables:
+                  - some_table
+                query: 'SELECT id, st_geohash(coordinates) FROM my_db.some_table;'
+
+  - match: { result: updated }
+
+
+  - do:
+      catch: "bad_request"
+      connector.update_active_filtering:
+        connector_id: test-connector

+ 13 - 0
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearch.java

@@ -54,10 +54,12 @@ import org.elasticsearch.xpack.application.connector.action.RestGetConnectorActi
 import org.elasticsearch.xpack.application.connector.action.RestListConnectorAction;
 import org.elasticsearch.xpack.application.connector.action.RestPostConnectorAction;
 import org.elasticsearch.xpack.application.connector.action.RestPutConnectorAction;
+import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorActiveFilteringAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorApiKeyIdAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorConfigurationAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorErrorAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorFilteringAction;
+import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorFilteringValidationAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorIndexNameAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorLastSeenAction;
 import org.elasticsearch.xpack.application.connector.action.RestUpdateConnectorLastSyncStatsAction;
@@ -72,10 +74,12 @@ import org.elasticsearch.xpack.application.connector.action.TransportGetConnecto
 import org.elasticsearch.xpack.application.connector.action.TransportListConnectorAction;
 import org.elasticsearch.xpack.application.connector.action.TransportPostConnectorAction;
 import org.elasticsearch.xpack.application.connector.action.TransportPutConnectorAction;
+import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorActiveFilteringAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorApiKeyIdAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorConfigurationAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorErrorAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorFilteringAction;
+import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorFilteringValidationAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorIndexNameAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorLastSeenAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorLastSyncStatsAction;
@@ -85,10 +89,12 @@ import org.elasticsearch.xpack.application.connector.action.TransportUpdateConne
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorSchedulingAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorServiceTypeAction;
 import org.elasticsearch.xpack.application.connector.action.TransportUpdateConnectorStatusAction;
+import org.elasticsearch.xpack.application.connector.action.UpdateConnectorActiveFilteringAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorApiKeyIdAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorConfigurationAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorErrorAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorFilteringAction;
+import org.elasticsearch.xpack.application.connector.action.UpdateConnectorFilteringValidationAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorIndexNameAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorLastSeenAction;
 import org.elasticsearch.xpack.application.connector.action.UpdateConnectorLastSyncStatsAction;
@@ -262,6 +268,11 @@ public class EnterpriseSearch extends Plugin implements ActionPlugin, SystemInde
                     new ActionHandler<>(UpdateConnectorConfigurationAction.INSTANCE, TransportUpdateConnectorConfigurationAction.class),
                     new ActionHandler<>(UpdateConnectorErrorAction.INSTANCE, TransportUpdateConnectorErrorAction.class),
                     new ActionHandler<>(UpdateConnectorFilteringAction.INSTANCE, TransportUpdateConnectorFilteringAction.class),
+                    new ActionHandler<>(UpdateConnectorActiveFilteringAction.INSTANCE, TransportUpdateConnectorActiveFilteringAction.class),
+                    new ActionHandler<>(
+                        UpdateConnectorFilteringValidationAction.INSTANCE,
+                        TransportUpdateConnectorFilteringValidationAction.class
+                    ),
                     new ActionHandler<>(UpdateConnectorIndexNameAction.INSTANCE, TransportUpdateConnectorIndexNameAction.class),
                     new ActionHandler<>(UpdateConnectorLastSeenAction.INSTANCE, TransportUpdateConnectorLastSeenAction.class),
                     new ActionHandler<>(UpdateConnectorLastSyncStatsAction.INSTANCE, TransportUpdateConnectorLastSyncStatsAction.class),
@@ -356,6 +367,8 @@ public class EnterpriseSearch extends Plugin implements ActionPlugin, SystemInde
                     new RestUpdateConnectorApiKeyIdAction(),
                     new RestUpdateConnectorConfigurationAction(),
                     new RestUpdateConnectorErrorAction(),
+                    new RestUpdateConnectorActiveFilteringAction(),
+                    new RestUpdateConnectorFilteringValidationAction(),
                     new RestUpdateConnectorFilteringAction(),
                     new RestUpdateConnectorIndexNameAction(),
                     new RestUpdateConnectorLastSeenAction(),

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

@@ -58,6 +58,7 @@ import org.elasticsearch.xpack.application.connector.filtering.FilteringAdvanced
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRule;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRules;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo;
+import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationState;
 import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJob;
 import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJobIndexService;
 
@@ -644,6 +645,111 @@ public class ConnectorIndexService {
         }
     }
 
+    /**
+     * Updates the {@link FilteringValidationInfo} of the draft {@link ConnectorFiltering} property of a {@link Connector}.
+     *
+     * @param connectorId  Request for updating {@link ConnectorFiltering}.
+     * @param listener     Listener to respond to a successful response or an error.
+     */
+    public void updateConnectorDraftFilteringValidation(
+        String connectorId,
+        FilteringValidationInfo validation,
+        ActionListener<UpdateResponse> listener
+    ) {
+        getConnector(connectorId, listener.delegateFailure((l, connector) -> {
+            try {
+                List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering(
+                    connector.getSourceRef(),
+                    XContentType.JSON
+                );
+                // Connectors represent their filtering configuration as a singleton list
+                ConnectorFiltering connectorFilteringSingleton = connectorFilteringList.get(0);
+
+                ConnectorFiltering activatedConnectorFiltering = connectorFilteringSingleton.setDraft(
+                    new FilteringRules.Builder().setRules(connectorFilteringSingleton.getDraft().getRules())
+                        .setAdvancedSnippet(connectorFilteringSingleton.getDraft().getAdvancedSnippet())
+                        .setFilteringValidationInfo(validation)
+                        .build()
+                );
+
+                final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
+                    new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
+                        .id(connectorId)
+                        .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
+                        .source(Map.of(Connector.FILTERING_FIELD.getPreferredName(), List.of(activatedConnectorFiltering)))
+                );
+
+                client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, l, (ll, updateResponse) -> {
+                    if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
+                        ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
+                        return;
+                    }
+                    ll.onResponse(updateResponse);
+                }));
+            } catch (Exception e) {
+                l.onFailure(e);
+            }
+        }));
+
+    }
+
+    /**
+     * Activates the draft {@link ConnectorFiltering} property of a {@link Connector}.
+     *
+     * @param connectorId  Request for updating {@link ConnectorFiltering} property.
+     * @param listener     Listener to respond to a successful response or an error.
+     */
+    public void activateConnectorDraftFiltering(String connectorId, ActionListener<UpdateResponse> listener) {
+        getConnector(connectorId, listener.delegateFailure((l, connector) -> {
+            try {
+                List<ConnectorFiltering> connectorFilteringList = fromXContentBytesConnectorFiltering(
+                    connector.getSourceRef(),
+                    XContentType.JSON
+                );
+                // Connectors represent their filtering configuration as a singleton list
+                ConnectorFiltering connectorFilteringSingleton = connectorFilteringList.get(0);
+
+                FilteringValidationState currentValidationState = connectorFilteringSingleton.getDraft()
+                    .getFilteringValidationInfo()
+                    .getValidationState();
+
+                if (currentValidationState != FilteringValidationState.VALID) {
+                    throw new ElasticsearchStatusException(
+                        "Filtering draft needs to be validated by the connector service before activation. "
+                            + "Current filtering draft validation state ["
+                            + currentValidationState.toString()
+                            + "] is not equal to ["
+                            + FilteringValidationState.VALID
+                            + "].",
+                        RestStatus.BAD_REQUEST
+                    );
+                }
+
+                ConnectorFiltering activatedConnectorFiltering = connectorFilteringSingleton.setActive(
+                    connectorFilteringSingleton.getDraft()
+                );
+
+                final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
+                    new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
+                        .id(connectorId)
+                        .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
+                        .source(Map.of(Connector.FILTERING_FIELD.getPreferredName(), List.of(activatedConnectorFiltering)))
+                );
+
+                client.update(updateRequest, new DelegatingIndexNotFoundActionListener<>(connectorId, l, (ll, updateResponse) -> {
+                    if (updateResponse.getResult() == UpdateResponse.Result.NOT_FOUND) {
+                        ll.onFailure(new ResourceNotFoundException(connectorNotFoundErrorMsg(connectorId)));
+                        return;
+                    }
+                    ll.onResponse(updateResponse);
+                }));
+            } catch (Exception e) {
+                l.onFailure(e);
+            }
+        }));
+
+    }
+
     /**
      * Updates the lastSeen property of a {@link Connector}.
      *

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

@@ -0,0 +1,46 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.client.internal.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.Scope;
+import org.elasticsearch.rest.ServerlessScope;
+import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.xpack.application.EnterpriseSearch;
+
+import java.util.List;
+
+import static org.elasticsearch.rest.RestRequest.Method.PUT;
+
+@ServerlessScope(Scope.PUBLIC)
+public class RestUpdateConnectorActiveFilteringAction extends BaseRestHandler {
+
+    @Override
+    public String getName() {
+        return "connector_update_active_filtering_action";
+    }
+
+    @Override
+    public List<Route> routes() {
+        return List.of(new Route(PUT, "/" + EnterpriseSearch.CONNECTOR_API_ENDPOINT + "/{connector_id}/_filtering/_activate"));
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
+        UpdateConnectorActiveFilteringAction.Request request = new UpdateConnectorActiveFilteringAction.Request(
+            restRequest.param("connector_id")
+        );
+        return channel -> client.execute(
+            UpdateConnectorActiveFilteringAction.INSTANCE,
+            request,
+            new RestToXContentListener<>(channel, ConnectorUpdateActionResponse::status)
+        );
+    }
+}

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

@@ -0,0 +1,48 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.client.internal.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.Scope;
+import org.elasticsearch.rest.ServerlessScope;
+import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.xpack.application.EnterpriseSearch;
+
+import java.util.List;
+
+import static org.elasticsearch.rest.RestRequest.Method.PUT;
+
+@ServerlessScope(Scope.PUBLIC)
+public class RestUpdateConnectorFilteringValidationAction extends BaseRestHandler {
+
+    @Override
+    public String getName() {
+        return "connector_update_filtering_validation_action";
+    }
+
+    @Override
+    public List<Route> routes() {
+        return List.of(new Route(PUT, "/" + EnterpriseSearch.CONNECTOR_API_ENDPOINT + "/{connector_id}/_filtering/_validation"));
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
+        UpdateConnectorFilteringValidationAction.Request request = UpdateConnectorFilteringValidationAction.Request.fromXContentBytes(
+            restRequest.param("connector_id"),
+            restRequest.content(),
+            restRequest.getXContentType()
+        );
+        return channel -> client.execute(
+            UpdateConnectorFilteringValidationAction.INSTANCE,
+            request,
+            new RestToXContentListener<>(channel, ConnectorUpdateActionResponse::status)
+        );
+    }
+}

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

@@ -0,0 +1,55 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.HandledTransportAction;
+import org.elasticsearch.client.internal.Client;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.util.concurrent.EsExecutors;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.application.connector.ConnectorIndexService;
+
+public class TransportUpdateConnectorActiveFilteringAction extends HandledTransportAction<
+    UpdateConnectorActiveFilteringAction.Request,
+    ConnectorUpdateActionResponse> {
+
+    protected final ConnectorIndexService connectorIndexService;
+
+    @Inject
+    public TransportUpdateConnectorActiveFilteringAction(
+        TransportService transportService,
+        ClusterService clusterService,
+        ActionFilters actionFilters,
+        Client client
+    ) {
+        super(
+            UpdateConnectorActiveFilteringAction.NAME,
+            transportService,
+            actionFilters,
+            UpdateConnectorActiveFilteringAction.Request::new,
+            EsExecutors.DIRECT_EXECUTOR_SERVICE
+        );
+        this.connectorIndexService = new ConnectorIndexService(client);
+    }
+
+    @Override
+    protected void doExecute(
+        Task task,
+        UpdateConnectorActiveFilteringAction.Request request,
+        ActionListener<ConnectorUpdateActionResponse> listener
+    ) {
+        connectorIndexService.activateConnectorDraftFiltering(
+            request.getConnectorId(),
+            listener.map(r -> new ConnectorUpdateActionResponse(r.getResult()))
+        );
+    }
+}

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

@@ -0,0 +1,56 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.HandledTransportAction;
+import org.elasticsearch.client.internal.Client;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.util.concurrent.EsExecutors;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.application.connector.ConnectorIndexService;
+
+public class TransportUpdateConnectorFilteringValidationAction extends HandledTransportAction<
+    UpdateConnectorFilteringValidationAction.Request,
+    ConnectorUpdateActionResponse> {
+
+    protected final ConnectorIndexService connectorIndexService;
+
+    @Inject
+    public TransportUpdateConnectorFilteringValidationAction(
+        TransportService transportService,
+        ClusterService clusterService,
+        ActionFilters actionFilters,
+        Client client
+    ) {
+        super(
+            UpdateConnectorFilteringValidationAction.NAME,
+            transportService,
+            actionFilters,
+            UpdateConnectorFilteringValidationAction.Request::new,
+            EsExecutors.DIRECT_EXECUTOR_SERVICE
+        );
+        this.connectorIndexService = new ConnectorIndexService(client);
+    }
+
+    @Override
+    protected void doExecute(
+        Task task,
+        UpdateConnectorFilteringValidationAction.Request request,
+        ActionListener<ConnectorUpdateActionResponse> listener
+    ) {
+        connectorIndexService.updateConnectorDraftFilteringValidation(
+            request.getConnectorId(),
+            request.getValidation(),
+            listener.map(r -> new ConnectorUpdateActionResponse(r.getResult()))
+        );
+    }
+}

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

@@ -0,0 +1,85 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.xcontent.ToXContentObject;
+import org.elasticsearch.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.action.ValidateActions.addValidationError;
+
+public class UpdateConnectorActiveFilteringAction {
+
+    public static final String NAME = "indices:data/write/xpack/connector/update_filtering/activate";
+    public static final ActionType<ConnectorUpdateActionResponse> INSTANCE = new ActionType<>(NAME);
+
+    private UpdateConnectorActiveFilteringAction() {/* no instances */}
+
+    public static class Request extends ConnectorActionRequest implements ToXContentObject {
+
+        private final String connectorId;
+
+        public Request(String connectorId) {
+            this.connectorId = connectorId;
+        }
+
+        public Request(StreamInput in) throws IOException {
+            super(in);
+            this.connectorId = in.readString();
+
+        }
+
+        public String getConnectorId() {
+            return connectorId;
+        }
+
+        @Override
+        public ActionRequestValidationException validate() {
+            ActionRequestValidationException validationException = null;
+
+            if (Strings.isNullOrEmpty(connectorId)) {
+                validationException = addValidationError("[connector_id] cannot be [null] or [\"\"].", validationException);
+            }
+
+            return validationException;
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            super.writeTo(out);
+            out.writeString(connectorId);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Request request = (Request) o;
+            return Objects.equals(connectorId, request.connectorId);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(connectorId);
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            builder.endObject();
+            return builder;
+        }
+    }
+}

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

@@ -0,0 +1,141 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.xcontent.ConstructingObjectParser;
+import org.elasticsearch.xcontent.ToXContentObject;
+import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xcontent.XContentParser;
+import org.elasticsearch.xcontent.XContentParserConfiguration;
+import org.elasticsearch.xcontent.XContentType;
+import org.elasticsearch.xpack.application.connector.filtering.FilteringRules;
+import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.action.ValidateActions.addValidationError;
+import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
+
+public class UpdateConnectorFilteringValidationAction {
+
+    public static final String NAME = "indices:data/write/xpack/connector/update_filtering/draft_validation";
+    public static final ActionType<ConnectorUpdateActionResponse> INSTANCE = new ActionType<>(NAME);
+
+    private UpdateConnectorFilteringValidationAction() {/* no instances */}
+
+    public static class Request extends ConnectorActionRequest implements ToXContentObject {
+
+        private final String connectorId;
+        private final FilteringValidationInfo validation;
+
+        public Request(String connectorId, FilteringValidationInfo validation) {
+            this.connectorId = connectorId;
+            this.validation = validation;
+        }
+
+        public Request(StreamInput in) throws IOException {
+            super(in);
+            this.connectorId = in.readString();
+            this.validation = new FilteringValidationInfo(in);
+        }
+
+        public String getConnectorId() {
+            return connectorId;
+        }
+
+        public FilteringValidationInfo getValidation() {
+            return validation;
+        }
+
+        @Override
+        public ActionRequestValidationException validate() {
+            ActionRequestValidationException validationException = null;
+
+            if (Strings.isNullOrEmpty(connectorId)) {
+                validationException = addValidationError("[connector_id] cannot be [null] or [\"\"].", validationException);
+            }
+
+            if (validation == null) {
+                validationException = addValidationError("[validation] cannot be [null].", validationException);
+            }
+
+            return validationException;
+        }
+
+        @SuppressWarnings("unchecked")
+        private static final ConstructingObjectParser<UpdateConnectorFilteringValidationAction.Request, String> PARSER =
+            new ConstructingObjectParser<>(
+                "connector_update_filtering_validation",
+                false,
+                ((args, connectorId) -> new UpdateConnectorFilteringValidationAction.Request(
+                    connectorId,
+                    (FilteringValidationInfo) args[0]
+                ))
+            );
+
+        static {
+            PARSER.declareObject(constructorArg(), (p, c) -> FilteringValidationInfo.fromXContent(p), FilteringRules.VALIDATION_FIELD);
+        }
+
+        public static UpdateConnectorFilteringValidationAction.Request fromXContentBytes(
+            String connectorId,
+            BytesReference source,
+            XContentType xContentType
+        ) {
+            try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, source, xContentType)) {
+                return UpdateConnectorFilteringValidationAction.Request.fromXContent(parser, connectorId);
+            } catch (IOException e) {
+                throw new ElasticsearchParseException("Failed to parse: " + source.utf8ToString(), e);
+            }
+        }
+
+        public static UpdateConnectorFilteringValidationAction.Request fromXContent(XContentParser parser, String connectorId)
+            throws IOException {
+            return PARSER.parse(parser, connectorId);
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            {
+                builder.field(FilteringRules.VALIDATION_FIELD.getPreferredName(), validation);
+            }
+            builder.endObject();
+            return builder;
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            super.writeTo(out);
+            out.writeString(connectorId);
+            validation.writeTo(out);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Request request = (Request) o;
+            return Objects.equals(connectorId, request.connectorId) && Objects.equals(validation, request.validation);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(connectorId, validation);
+        }
+    }
+}

+ 1 - 0
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/filtering/FilteringRules.java

@@ -71,6 +71,7 @@ public class FilteringRules implements Writeable, ToXContentObject {
 
     public static final ParseField ADVANCED_SNIPPET_FIELD = new ParseField("advanced_snippet");
     public static final ParseField RULES_FIELD = new ParseField("rules");
+
     public static final ParseField VALIDATION_FIELD = new ParseField("validation");
 
     @SuppressWarnings("unchecked")

+ 4 - 0
x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/filtering/FilteringValidationInfo.java

@@ -53,6 +53,10 @@ public class FilteringValidationInfo implements Writeable, ToXContentObject {
     private static final ParseField ERRORS_FIELD = new ParseField("errors");
     private static final ParseField STATE_FIELD = new ParseField("state");
 
+    public FilteringValidationState getValidationState() {
+        return validationState;
+    }
+
     @SuppressWarnings("unchecked")
     private static final ConstructingObjectParser<FilteringValidationInfo, Void> PARSER = new ConstructingObjectParser<>(
         "filtering_validation_info",

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

@@ -40,6 +40,7 @@ import org.elasticsearch.xpack.application.connector.action.UpdateConnectorStatu
 import org.elasticsearch.xpack.application.connector.filtering.FilteringAdvancedSnippet;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRule;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo;
+import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationState;
 import org.junit.Before;
 
 import java.util.ArrayList;
@@ -290,6 +291,71 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
         );
     }
 
+    public void testUpdateConnectorFilteringValidation() throws Exception {
+        Connector connector = ConnectorTestUtils.getRandomConnector();
+        String connectorId = randomUUID();
+
+        DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
+        assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+
+        FilteringValidationInfo validationInfo = ConnectorTestUtils.getRandomFilteringValidationInfo();
+
+        DocWriteResponse validationInfoUpdateResponse = awaitUpdateConnectorDraftFilteringValidation(connectorId, validationInfo);
+        assertThat(validationInfoUpdateResponse.status(), equalTo(RestStatus.OK));
+
+        Connector indexedConnector = awaitGetConnector(connectorId);
+
+        assertThat(validationInfo, equalTo(indexedConnector.getFiltering().get(0).getDraft().getFilteringValidationInfo()));
+    }
+
+    public void testActivateConnectorDraftFiltering_draftValid_shouldActivate() throws Exception {
+        Connector connector = ConnectorTestUtils.getRandomConnector();
+        String connectorId = randomUUID();
+
+        DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
+        assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+
+        // Populate draft filtering
+        FilteringAdvancedSnippet advancedSnippet = ConnectorTestUtils.getRandomConnectorFiltering().getDraft().getAdvancedSnippet();
+        List<FilteringRule> rules = ConnectorTestUtils.getRandomConnectorFiltering().getDraft().getRules();
+
+        DocWriteResponse updateResponse = awaitUpdateConnectorFilteringDraft(connectorId, advancedSnippet, rules);
+        assertThat(updateResponse.status(), equalTo(RestStatus.OK));
+
+        FilteringValidationInfo validationSuccess = new FilteringValidationInfo.Builder().setValidationState(FilteringValidationState.VALID)
+            .setValidationErrors(Collections.emptyList())
+            .build();
+
+        DocWriteResponse validationInfoUpdateResponse = awaitUpdateConnectorDraftFilteringValidation(connectorId, validationSuccess);
+        assertThat(validationInfoUpdateResponse.status(), equalTo(RestStatus.OK));
+
+        DocWriteResponse activateFilteringResponse = awaitActivateConnectorDraftFiltering(connectorId);
+        assertThat(activateFilteringResponse.status(), equalTo(RestStatus.OK));
+
+        Connector indexedConnector = awaitGetConnector(connectorId);
+
+        // Assert that draft is activated
+        assertThat(advancedSnippet, equalTo(indexedConnector.getFiltering().get(0).getActive().getAdvancedSnippet()));
+        assertThat(rules, equalTo(indexedConnector.getFiltering().get(0).getActive().getRules()));
+    }
+
+    public void testActivateConnectorDraftFiltering_draftNotValid_expectFailure() throws Exception {
+        Connector connector = ConnectorTestUtils.getRandomConnector();
+        String connectorId = randomUUID();
+
+        DocWriteResponse resp = buildRequestAndAwaitPutConnector(connectorId, connector);
+        assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+
+        FilteringValidationInfo validationFailure = new FilteringValidationInfo.Builder().setValidationState(
+            FilteringValidationState.INVALID
+        ).setValidationErrors(Collections.emptyList()).build();
+
+        DocWriteResponse validationInfoUpdateResponse = awaitUpdateConnectorDraftFilteringValidation(connectorId, validationFailure);
+        assertThat(validationInfoUpdateResponse.status(), equalTo(RestStatus.OK));
+
+        expectThrows(ElasticsearchStatusException.class, () -> awaitActivateConnectorDraftFiltering(connectorId));
+    }
+
     public void testUpdateConnectorLastSeen() throws Exception {
         Connector connector = ConnectorTestUtils.getRandomConnector();
         String connectorId = randomUUID();
@@ -775,6 +841,59 @@ public class ConnectorIndexServiceTests extends ESSingleNodeTestCase {
         return resp.get();
     }
 
+    private UpdateResponse awaitUpdateConnectorDraftFilteringValidation(String connectorId, FilteringValidationInfo validationInfo)
+        throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<UpdateResponse> resp = new AtomicReference<>(null);
+        final AtomicReference<Exception> exc = new AtomicReference<>(null);
+        connectorIndexService.updateConnectorDraftFilteringValidation(connectorId, validationInfo, new ActionListener<>() {
+            @Override
+            public void onResponse(UpdateResponse indexResponse) {
+                resp.set(indexResponse);
+                latch.countDown();
+            }
+
+            @Override
+            public void onFailure(Exception e) {
+                exc.set(e);
+                latch.countDown();
+            }
+        });
+
+        assertTrue("Timeout waiting for update filtering validation request", latch.await(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        if (exc.get() != null) {
+            throw exc.get();
+        }
+        assertNotNull("Received null response from update filtering validation request", resp.get());
+        return resp.get();
+    }
+
+    private UpdateResponse awaitActivateConnectorDraftFiltering(String connectorId) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<UpdateResponse> resp = new AtomicReference<>(null);
+        final AtomicReference<Exception> exc = new AtomicReference<>(null);
+        connectorIndexService.activateConnectorDraftFiltering(connectorId, new ActionListener<>() {
+            @Override
+            public void onResponse(UpdateResponse indexResponse) {
+                resp.set(indexResponse);
+                latch.countDown();
+            }
+
+            @Override
+            public void onFailure(Exception e) {
+                exc.set(e);
+                latch.countDown();
+            }
+        });
+
+        assertTrue("Timeout waiting for activate draft filtering request", latch.await(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        if (exc.get() != null) {
+            throw exc.get();
+        }
+        assertNotNull("Received null response from activate draft filtering request", resp.get());
+        return resp.get();
+    }
+
     private UpdateResponse awaitUpdateConnectorFilteringDraft(
         String connectorId,
         FilteringAdvancedSnippet advancedSnippet,

+ 19 - 10
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorTestUtils.java

@@ -24,6 +24,7 @@ import org.elasticsearch.xpack.application.connector.filtering.FilteringPolicy;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRule;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRuleCondition;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringRules;
+import org.elasticsearch.xpack.application.connector.filtering.FilteringValidation;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationInfo;
 import org.elasticsearch.xpack.application.connector.filtering.FilteringValidationState;
 import org.elasticsearch.xpack.application.connector.syncjob.ConnectorSyncJob;
@@ -179,6 +180,22 @@ public final class ConnectorTestUtils {
             .build();
     }
 
+    public static FilteringValidationInfo getRandomFilteringValidationInfo() {
+        return new FilteringValidationInfo.Builder().setValidationErrors(getRandomFilteringValidationErrors())
+            .setValidationState(getRandomFilteringValidationState())
+            .build();
+    }
+
+    private static List<FilteringValidation> getRandomFilteringValidationErrors() {
+        return List.of(getRandomFilteringValidationError(), getRandomFilteringValidationError(), getRandomFilteringValidationError());
+    }
+
+    private static FilteringValidation getRandomFilteringValidationError() {
+        return new FilteringValidation.Builder().setIds(List.of(randomAlphaOfLength(5), randomAlphaOfLength(5)))
+            .setMessages(List.of(randomAlphaOfLengthBetween(10, 20), randomAlphaOfLengthBetween(15, 25)))
+            .build();
+    }
+
     public static ConnectorFiltering getRandomConnectorFiltering() {
 
         Instant currentTimestamp = Instant.now();
@@ -203,11 +220,7 @@ public final class ConnectorTestUtils {
                             .build()
                     )
                 )
-                .setFilteringValidationInfo(
-                    new FilteringValidationInfo.Builder().setValidationErrors(Collections.emptyList())
-                        .setValidationState(getRandomFilteringValidationState())
-                        .build()
-                )
+                .setFilteringValidationInfo(getRandomFilteringValidationInfo())
                 .build()
         )
             .setDraft(
@@ -230,11 +243,7 @@ public final class ConnectorTestUtils {
                                 .build()
                         )
                     )
-                    .setFilteringValidationInfo(
-                        new FilteringValidationInfo.Builder().setValidationErrors(Collections.emptyList())
-                            .setValidationState(getRandomFilteringValidationState())
-                            .build()
-                    )
+                    .setFilteringValidationInfo(getRandomFilteringValidationInfo())
                     .build()
             )
             .build();

+ 51 - 0
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorActiveFilteringActionRequestBWCSerializingTests.java

@@ -0,0 +1,51 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.xcontent.XContentParser;
+import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase;
+
+import java.io.IOException;
+
+public class UpdateConnectorActiveFilteringActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase<
+    UpdateConnectorActiveFilteringAction.Request> {
+
+    private String connectorId;
+
+    @Override
+    protected Writeable.Reader<UpdateConnectorActiveFilteringAction.Request> instanceReader() {
+        return UpdateConnectorActiveFilteringAction.Request::new;
+    }
+
+    @Override
+    protected UpdateConnectorActiveFilteringAction.Request createTestInstance() {
+        this.connectorId = randomUUID();
+        return new UpdateConnectorActiveFilteringAction.Request(connectorId);
+    }
+
+    @Override
+    protected UpdateConnectorActiveFilteringAction.Request mutateInstance(UpdateConnectorActiveFilteringAction.Request instance)
+        throws IOException {
+        return randomValueOtherThan(instance, this::createTestInstance);
+    }
+
+    @Override
+    protected UpdateConnectorActiveFilteringAction.Request doParseInstance(XContentParser parser) throws IOException {
+        return new UpdateConnectorActiveFilteringAction.Request(this.connectorId);
+    }
+
+    @Override
+    protected UpdateConnectorActiveFilteringAction.Request mutateInstanceForVersion(
+        UpdateConnectorActiveFilteringAction.Request instance,
+        TransportVersion version
+    ) {
+        return instance;
+    }
+}

+ 52 - 0
x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/action/UpdateConnectorFilteringValidationActionRequestBWCSerializingTests.java

@@ -0,0 +1,52 @@
+/*
+ * 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.application.connector.action;
+
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.xcontent.XContentParser;
+import org.elasticsearch.xpack.application.connector.ConnectorTestUtils;
+import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase;
+
+import java.io.IOException;
+
+public class UpdateConnectorFilteringValidationActionRequestBWCSerializingTests extends AbstractBWCSerializationTestCase<
+    UpdateConnectorFilteringValidationAction.Request> {
+
+    private String connectorId;
+
+    @Override
+    protected Writeable.Reader<UpdateConnectorFilteringValidationAction.Request> instanceReader() {
+        return UpdateConnectorFilteringValidationAction.Request::new;
+    }
+
+    @Override
+    protected UpdateConnectorFilteringValidationAction.Request createTestInstance() {
+        this.connectorId = randomUUID();
+        return new UpdateConnectorFilteringValidationAction.Request(connectorId, ConnectorTestUtils.getRandomFilteringValidationInfo());
+    }
+
+    @Override
+    protected UpdateConnectorFilteringValidationAction.Request mutateInstance(UpdateConnectorFilteringValidationAction.Request instance)
+        throws IOException {
+        return randomValueOtherThan(instance, this::createTestInstance);
+    }
+
+    @Override
+    protected UpdateConnectorFilteringValidationAction.Request doParseInstance(XContentParser parser) throws IOException {
+        return UpdateConnectorFilteringValidationAction.Request.fromXContent(parser, this.connectorId);
+    }
+
+    @Override
+    protected UpdateConnectorFilteringValidationAction.Request mutateInstanceForVersion(
+        UpdateConnectorFilteringValidationAction.Request instance,
+        TransportVersion version
+    ) {
+        return instance;
+    }
+}

+ 2 - 0
x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java

@@ -134,6 +134,8 @@ public class Constants {
         "indices:data/write/xpack/connector/update_configuration",
         "indices:data/write/xpack/connector/update_error",
         "indices:data/write/xpack/connector/update_filtering",
+        "indices:data/write/xpack/connector/update_filtering/activate",
+        "indices:data/write/xpack/connector/update_filtering/draft_validation",
         "indices:data/write/xpack/connector/update_index_name",
         "indices:data/write/xpack/connector/update_last_seen",
         "indices:data/write/xpack/connector/update_last_sync_stats",