This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit ae0c9c40a1338f7b8f87f1d6c83f4098a4ef25e5
Author: Eric Pugh <[email protected]>
AuthorDate: Fri Sep 4 12:46:43 2026 -0400

    SOLR-18152: improve body format OpenAPI annotations to fully communicate 
SchemaDesigner API (#4819)
    
    (cherry picked from commit 0b1d3f75d5c1bdacd966584299b47366537395f8)
---
 .../client/api/endpoint/SchemaDesignerApi.java     |   8 +-
 .../api/model/SchemaDesignerAddRequestBody.java    |   5 -
 .../api/model/SchemaDesignerUpdateRequestBody.java |   3 +
 .../api/model/UpsertDynamicFieldOperation.java     |   3 +
 .../client/api/model/UpsertFieldOperation.java     |   3 +
 .../client/api/model/UpsertFieldTypeOperation.java |  10 +-
 .../SchemaChangeOperationSerializationTest.java    |   4 +-
 .../solr/handler/admin/api/UpdateSchema.java       |   2 +-
 .../java/org/apache/solr/schema/SchemaManager.java |   4 +-
 .../admin/api/V2UpdateSchemaErrorCaseTests.java    |   2 +-
 .../handler/designer/TestSchemaDesignerSolrJ.java  |   4 +-
 .../solrj/src/resources/java-template/api.mustache |  11 +-
 .../solr/webapp/AdminUiSchemaDesignerTest.java     |  46 +--
 .../web/js/angular/controllers/schema-designer.js  | 460 ++++++++++++---------
 solr/webapp/web/js/angular/services.js             |  17 +-
 solr/webapp/web/partials/schema-designer.html      |   4 +-
 16 files changed, 341 insertions(+), 245 deletions(-)

diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
index 572891d6ec5..d319471857d 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.client.api.endpoint;
 
+import static org.apache.solr.client.api.util.Constants.ADDTL_FIELDS_PROPERTY;
 import static 
org.apache.solr.client.api.util.Constants.GENERIC_ENTITY_PROPERTY;
 
 import io.swagger.v3.oas.annotations.Operation;
@@ -147,7 +148,12 @@ public interface SchemaDesignerApi {
   SchemaDesignerUpdateResponse updateSchemaObject(
       @PathParam("configSet") String configSet,
       @QueryParam("schemaVersion") Integer schemaVersion,
-      SchemaDesignerUpdateRequestBody requestBody)
+      @RequestBody(
+              extensions = {
+                @Extension(
+                    properties = {@ExtensionProperty(name = 
ADDTL_FIELDS_PROPERTY, value = "true")})
+              })
+          SchemaDesignerUpdateRequestBody requestBody)
       throws Exception;
 
   @PUT
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
index 821cabc4246..25e0da72a79 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
@@ -17,7 +17,6 @@
 package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.Map;
 
 /**
@@ -27,19 +26,15 @@ import java.util.Map;
  */
 public class SchemaDesignerAddRequestBody {
 
-  @Schema(name = "addField")
   @JsonProperty("add-field")
   public Map<String, Object> addField;
 
-  @Schema(name = "addDynamicField")
   @JsonProperty("add-dynamic-field")
   public Map<String, Object> addDynamicField;
 
-  @Schema(name = "addCopyField")
   @JsonProperty("add-copy-field")
   public Map<String, Object> addCopyField;
 
-  @Schema(name = "addFieldType")
   @JsonProperty("add-field-type")
   public Map<String, Object> addFieldType;
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
index 54b9bb9e56c..e88114462e5 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
@@ -19,6 +19,7 @@ package org.apache.solr.client.api.model;
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -28,6 +29,7 @@ import java.util.Map;
  * indexed}, {@code stored}, {@code analyzer}, {@code copyDest}) are captured 
via the dynamic {@code
  * additionalProperties} map and forwarded to the Schema API.
  */
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
 public class SchemaDesignerUpdateRequestBody {
 
   @JsonProperty public String name;
@@ -36,6 +38,7 @@ public class SchemaDesignerUpdateRequestBody {
   // Accessed via @JsonAnyGetter / @JsonAnySetter for JSON (de)serialization.
   public Map<String, Object> additionalProperties = new HashMap<>();
 
+  @Schema(hidden = true)
   @JsonAnyGetter
   public Map<String, Object> getAdditionalProperties() {
     return additionalProperties;
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
index db010fbacca..2a0376fafc3 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
@@ -19,9 +19,11 @@ package org.apache.solr.client.api.model;
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.HashMap;
 import java.util.Map;
 
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
 public class UpsertDynamicFieldOperation extends SchemaChange {
   @JsonProperty public String name;
   @JsonProperty public String type;
@@ -29,6 +31,7 @@ public class UpsertDynamicFieldOperation extends SchemaChange 
{
   // Used for setting index and stored settings, etc.
   private Map<String, Object> additionalProperties = new HashMap<>();
 
+  @Schema(hidden = true)
   @JsonAnyGetter
   public Map<String, Object> getAdditionalProperties() {
     return additionalProperties;
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
index c6a7d1c3c32..6946f3536c8 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
@@ -19,9 +19,11 @@ package org.apache.solr.client.api.model;
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.HashMap;
 import java.util.Map;
 
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
 public class UpsertFieldOperation extends SchemaChange {
   @JsonProperty public String name;
 
@@ -30,6 +32,7 @@ public class UpsertFieldOperation extends SchemaChange {
   // Used for setting index and stored settings, etc.
   private Map<String, Object> additionalProperties = new HashMap<>();
 
+  @Schema(hidden = true)
   @JsonAnyGetter
   public Map<String, Object> getAdditionalProperties() {
     return additionalProperties;
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
index 6a517830b2e..60ee25122c8 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
@@ -19,18 +19,26 @@ package org.apache.solr.client.api.model;
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.HashMap;
 import java.util.Map;
 
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
 public class UpsertFieldTypeOperation extends SchemaChange {
   @JsonProperty public String name;
 
+  // Field named to match what the OpenAPI-generated SolrJ client derives as a 
Java-safe
+  // identifier for the reserved word "class" (see api.mustache's {{name}} 
usage) -- the
+  // SolrJ codegen assigns into this field by that exact name, so a 
hand-picked name here
+  // (e.g. "className") would compile-fail the generated client the moment 
this class stops
+  // being shielded from per-field setter generation (see SchemaChange's oneOf 
discriminator).
   @JsonProperty("class")
-  public String className;
+  public String propertyClass;
 
   // Used for setting analyzers, index and stored settings, etc.
   private Map<String, Object> additionalProperties = new HashMap<>();
 
+  @Schema(hidden = true)
   @JsonAnyGetter
   public Map<String, Object> getAdditionalProperties() {
     return additionalProperties;
diff --git 
a/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
 
b/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
index 48afd83e694..a0606bd6d3d 100644
--- 
a/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
+++ 
b/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
@@ -58,7 +58,7 @@ public class SchemaChangeOperationSerializationTest extends 
SolrTestCase {
     assertThat(parsedGeneric, instanceOf(UpsertFieldTypeOperation.class));
     final var parsedSpecific = (UpsertFieldTypeOperation) parsedGeneric;
     assertEquals("my-new-field-type", parsedSpecific.name);
-    assertEquals("org.apache.my.ClassName", parsedSpecific.className);
+    assertEquals("org.apache.my.ClassName", parsedSpecific.propertyClass);
     // Arbitrary properties are put in a map, and can contain nesting
     assertEquals(100, 
parsedSpecific.getAdditionalProperties().get("positionIncrementGap"));
     assertThat(parsedSpecific.getAdditionalProperties().get("analyzer"), 
instanceOf(Map.class));
@@ -272,7 +272,7 @@ public class SchemaChangeOperationSerializationTest extends 
SolrTestCase {
     assertThat(parsedGeneric, instanceOf(UpsertFieldTypeOperation.class));
     final var parsedSpecific = (UpsertFieldTypeOperation) parsedGeneric;
     assertEquals("my-new-field-type", parsedSpecific.name);
-    assertEquals("org.apache.my.ClassName", parsedSpecific.className);
+    assertEquals("org.apache.my.ClassName", parsedSpecific.propertyClass);
     // Arbitrary properties are put in a map, and can contain nesting
     assertEquals(100, 
parsedSpecific.getAdditionalProperties().get("positionIncrementGap"));
     assertThat(parsedSpecific.getAdditionalProperties().get("analyzer"), 
instanceOf(Map.class));
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
index 1bb3f51c4c3..6bb00ea00f6 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
@@ -122,7 +122,7 @@ public class UpdateSchema extends JerseyResource implements 
UpdateSchemaApi {
     ensureSchemaMutable();
     ensureRequiredRequestBodyProvided(requestBody);
     ensureRequiredParameterProvided("fieldTypeName", fieldTypeName);
-    ensureRequiredParameterProvided("class", requestBody.className);
+    ensureRequiredParameterProvided("class", requestBody.propertyClass);
     requestBody.operationType = "add-field-type";
 
     runWithSchemaManager(List.of(requestBody), response);
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java 
b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
index a65eb18df6a..05efe512c21 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
@@ -240,7 +240,7 @@ public class SchemaManager {
       public boolean perform(SchemaChange op, SchemaManager mgr) throws 
SchemaOperationException {
         final var addFieldTypeOp = (UpsertFieldTypeOperation) op;
         String name = ensureNotNull("name", addFieldTypeOp.name);
-        String className = ensureNotNull("class", addFieldTypeOp.className);
+        String className = ensureNotNull("class", 
addFieldTypeOp.propertyClass);
         try {
           FieldType fieldType =
               mgr.managedIndexSchema.newFieldType(name, className, 
convertToMap(addFieldTypeOp));
@@ -420,7 +420,7 @@ public class SchemaManager {
       public boolean perform(SchemaChange op, SchemaManager mgr) throws 
SchemaOperationException {
         final var replaceFieldTypeOp = (UpsertFieldTypeOperation) op;
         String name = ensureNotNull("name", replaceFieldTypeOp.name);
-        String className = ensureNotNull("class", 
replaceFieldTypeOp.className);
+        String className = ensureNotNull("class", 
replaceFieldTypeOp.propertyClass);
         try {
           mgr.managedIndexSchema =
               mgr.managedIndexSchema.replaceFieldType(
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
index 54d063dc31e..d981f572a0b 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
@@ -123,7 +123,7 @@ public class V2UpdateSchemaErrorCaseTests extends 
SolrTestCase {
   @Test
   public void testAddFieldTypeOperationRequiresTypeNameAndClass() {
     final var noTypeOp = new UpsertFieldTypeOperation();
-    noTypeOp.className = "solr.TextField";
+    noTypeOp.propertyClass = "solr.TextField";
     var thrown =
         expectThrows(
             SolrException.class,
diff --git 
a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
 
b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
index d755b6964d9..9d3a4fe1384 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
@@ -123,7 +123,9 @@ public class TestSchemaDesignerSolrJ extends 
SolrCloudTestCase {
     var update = new SchemaDesignerApi.UpdateSchemaObject(configSet);
     update.setSchemaVersion(schemaVersion);
     update.setName("keywords");
-    update.setAdditionalProperties(Map.of("type", "string", "stored", true, 
"multiValued", true));
+    update.setAdditionalProperty("type", "string");
+    update.setAdditionalProperty("stored", true);
+    update.setAdditionalProperty("multiValued", true);
     SchemaDesignerUpdateResponse updateResp = 
update.process(cluster.getSolrClient());
     assertNotNull(updateResp.field);
     assertEquals("field", updateResp.updateType);
diff --git a/solr/solrj/src/resources/java-template/api.mustache 
b/solr/solrj/src/resources/java-template/api.mustache
index 81cdff1b3a8..4aae4828561 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -179,13 +179,18 @@ public class {{classname}} {
             {{#bodyParam}}
             {{#vars}}
             // TODO find a way to add required parameters in the request body 
to the class constructor
+            // The setter parameter (and the requestBody.<field> access below) 
both use the
+            // "name" var: openapi-generator's Java-safe sanitization of this 
property's wire
+            // name (e.g. the reserved word "class" becomes "propertyClass"). 
The hand-written
+            // requestBody model class's field MUST be named to match exactly, 
or the field
+            // access below fails to compile.
             {{#description}}
             /**
-             * @param {{baseName}} {{description}}
+             * @param {{name}} {{description}}
              */
              {{/description}}
-             public void {{setter}}({{{dataType}}} {{baseName}}) {
-               this.requestBody.{{baseName}} = {{baseName}};
+             public void {{setter}}({{{dataType}}} {{name}}) {
+               this.requestBody.{{name}} = {{name}};
              }
             {{/vars}}
 
diff --git 
a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
index 85a76aec800..739c6312e6e 100644
--- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
+++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.webapp;
 
-import org.apache.lucene.tests.util.LuceneTestCase;
 import org.junit.Test;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
@@ -25,11 +24,9 @@ import org.openqa.selenium.WebElement;
  * Happy-path test of the Schema Designer screen: create a new schema, paste a 
sample document and
  * let the designer analyze it.
  *
- * <p>AwaitsFix: the designer backend transiently fails its own prep/analyze 
calls ("version
- * mismatch, retry", "Error loading solr config") when driven at automation 
speed, making this test
- * flaky even with retries.
+ * <p>The Analyze action remains disabled until creation of the mutable schema 
has completed, so a
+ * fast user cannot race the prep and analyze requests.
  */
[email protected](bugUrl = 
"https://issues.apache.org/jira/browse/SOLR-18347";)
 public class AdminUiSchemaDesignerTest extends AdminUiTestBase {
 
   @Test
@@ -47,34 +44,17 @@ public class AdminUiSchemaDesignerTest extends 
AdminUiTestBase {
     WebElement sampleDocs = waitFor(By.cssSelector("#sample-docs 
textarea#document"));
     sampleDocs.clear();
     sampleDocs.sendKeys("[{\"id\":\"1\",\"designer_title\":\"Hello 
Designer\"}]");
-    click(By.id("analyze"));
+    click(By.cssSelector("#analyze:not([disabled])"));
 
-    // the analyzed schema lists the field derived from the sample doc. The 
designer
-    // backend transiently fails its own calls ("version mismatch, retry", 
"Error
-    // loading solr config") and surfaces an error dialog - dismiss it and 
analyze
-    // again, with a generous budget since each round trips several requests
-    long deadlineNanos = System.nanoTime() + 
WAIT_TIMEOUT.multipliedBy(3).toNanos();
-    boolean analyzed = false;
-    while (!analyzed && System.nanoTime() < deadlineNanos) {
-      analyzed = driver.getPageSource().contains("designer_title");
-      if (!analyzed) {
-        for (String dismissButton : new String[] {"Reload Schema", "OK"}) {
-          driver.findElements(By.xpath("//button[contains(., '" + 
dismissButton + "')]")).stream()
-              .filter(WebElement::isDisplayed)
-              .findFirst()
-              .ifPresent(WebElement::click);
-        }
-        driver.findElements(By.id("analyze")).stream()
-            .filter(WebElement::isDisplayed)
-            .findFirst()
-            .ifPresent(WebElement::click);
-        Thread.sleep(500);
-      }
-    }
-    assertTrue("Analyzed schema should list the sample doc field", analyzed);
-    // the designer's own API calls (prep/analyze/luke against its temp core) 
error
-    // transiently while it persists and reloads the schema - it recovers via 
its retry
-    // dialog, so only unrelated console errors fail the test
-    assertNoSevereConsoleErrors("schema-designer/", "._designer_");
+    waitForPageContains("designer_title");
+    assertNoSevereConsoleErrors();
+
+    // add a field through the UI
+    click(By.cssSelector("#addField"));
+    setText(By.id("add_name"), "extra_test_field");
+    click(By.xpath("//button[@ng-click='addField()']"));
+
+    waitForPageContains("extra_test_field");
+    assertNoSevereConsoleErrors();
   }
 }
diff --git a/solr/webapp/web/js/angular/controllers/schema-designer.js 
b/solr/webapp/web/js/angular/controllers/schema-designer.js
index 7ec4282bb05..a8d4c963245 100644
--- a/solr/webapp/web/js/angular/controllers/schema-designer.js
+++ b/solr/webapp/web/js/angular/controllers/schema-designer.js
@@ -15,7 +15,7 @@
  limitations under the License.
 */
 
-solrAdminApp.controller('SchemaDesignerController', function ($scope, 
$timeout, $cookies, $window, Constants, SchemaDesigner, ConfigSetFiles, Luke) {
+solrAdminApp.controller('SchemaDesignerController', function ($scope, 
$timeout, $cookies, $window, Constants, SchemaDesigner, SchemaDesignerV2, 
ConfigSetFiles, Luke) {
   $scope.resetMenu("schema-designer", Constants.IS_ROOT_PAGE);
 
   $scope.schemas = [];
@@ -24,6 +24,7 @@ solrAdminApp.controller('SchemaDesignerController', function 
($scope, $timeout,
   $scope.sortableFields = [];
   $scope.hlFields = [];
   $scope.types = [];
+  $scope.preparingSchema = false;
 
   $scope.onWarning = function (warnMsg, warnDetails) {
     $scope.updateWorking = false;
@@ -34,6 +35,7 @@ solrAdminApp.controller('SchemaDesignerController', function 
($scope, $timeout,
   
   $scope.onError = function (errorMsg, errorCode, errorDetails) {
     $scope.updateWorking = false;
+    $scope.preparingSchema = false;
     delete $scope.updateStatusMessage;
     $scope.designerAPIError = errorMsg;
     if (errorDetails) {
@@ -68,20 +70,38 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     } // else 500 errors get the top-level error message
   };
 
-  $scope.errorHandler = function (e) {
-    var error = e.data && e.data.error ? e.data.error : null;
+  // Shared by $scope.errorHandler (v1 $http) and $scope.errorHandlerV2 (v2 
generated-client) below:
+  // given a structured API error (or null, for a network-level failure like a 
timeout), drives the
+  // same local error dialog via $scope.onError.
+  function reportApiFailure(error, errorDetails, fallbackPath, fallbackCode, 
extraFailureHint) {
     if (error) {
-      $scope.onError(error.msg, error.code, e.data.errorDetails);
+      $scope.onError(error.msg, error.code, errorDetails);
     } else {
-      // when a timeout occurs, the error details are sparse so just give the 
user a hint that something was off
-      var path = e.config && e.config.url ? e.config.url : 
"/api/schema-designer";
-      var reloadMsg = "";
-      if (path.includes("/analyze")) {
-        reloadMsg = " Re-try analyzing your sample docs by clicking on 
'Analyze Documents' again."
-      }
-      $scope.onError("Request to "+path+" failed!", 408,
-          {"error":"Most likely the request timed out; check server log for 
more details."+reloadMsg});
+      $scope.onError("Request to "+fallbackPath+" failed!", fallbackCode,
+          {"error":"Most likely the request timed out; check server log for 
more details."+(extraFailureHint || "")});
     }
+  }
+
+  $scope.errorHandler = function (e) {
+    var error = e.data && e.data.error ? e.data.error : null;
+    // when a timeout occurs, the error details are sparse so just give the 
user a hint that something was off
+    var path = e.config && e.config.url ? e.config.url : 
"/api/schema-designer";
+    var reloadMsg = path.includes("/analyze")
+        ? " Re-try analyzing your sample docs by clicking on 'Analyze 
Documents' again."
+        : "";
+    reportApiFailure(error, e.data && e.data.errorDetails, path, 408, 
reloadMsg);
+  };
+
+  // Error handler for SchemaDesignerV2 (generated OpenAPI client) callbacks: 
response is the raw
+  // superagent response (may be undefined for a network-level failure like a 
timeout). This deliberately
+  // does NOT go through the shared ApiErrorHandler service -- app.js's 
httpInterceptor already carves
+  // out /api/schema-designer/ from the global 401/403 handling so a failure 
here degrades this one
+  // screen instead of forcing a full login redirect mid-design-session; this 
mirrors that same intent
+  // for v2 calls by driving the same local error dialog as 
$scope.errorHandler.
+  $scope.errorHandlerV2 = function (response) {
+    var data = (response && response.body) || {};
+    var path = (response && response.req && response.req.url) || 
"/api/schema-designer";
+    reportApiFailure(data.error, data.errorDetails, path, (response && 
response.status) || 408);
   };
 
   $scope.closeWarnDialog = function () {
@@ -149,36 +169,40 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     // query form
     $scope.query = {q: '*:*', sortBy: 'score', sortDir: 'desc'};
 
-    SchemaDesigner.get({path: "configs"}, function (data) {
+    SchemaDesignerV2.listDesignerConfigs(function (error, data, response) {
+      $timeout(function () {
+        if (error) {
+          if (response && (response.status === 401 || response.status === 
403)) {
+            $scope.isSchemaDesignerEnabled = false;
+            $scope.hideAll();
+          }
+          return;
+        }
 
-      $scope.schemas = [];
-      $scope.publishedSchemas = ["_default"];
+        $scope.schemas = [];
+        $scope.publishedSchemas = ["_default"];
 
-      for (var s in data.configSets) {
-        // 1 means published but not editable
-        if (data.configSets[s] !== 1) {
-          $scope.schemas.push(s);
-        }
+        for (var s in data.configSets) {
+          // 1 means published but not editable
+          if (data.configSets[s] !== 1) {
+            $scope.schemas.push(s);
+          }
 
-        // 0 means not published yet (so can't copy from it yet)
-        if (data.configSets[s] > 0) {
-          $scope.publishedSchemas.push(s);
+          // 0 means not published yet (so can't copy from it yet)
+          if (data.configSets[s] > 0) {
+            $scope.publishedSchemas.push(s);
+          }
         }
-      }
 
-      $scope.schemas.sort();
-      $scope.publishedSchemas.sort();
+        $scope.schemas.sort();
+        $scope.publishedSchemas.sort();
 
-      // if no schemas available to select, open the pop-up immediately
-      if ($scope.schemas.length === 0) {
-        $scope.firstSchemaMessage = true;
-        $scope.showNewSchemaDialog();
-      }
-    }, function(e) {
-      if (e.status === 401 || e.status === 403) {
-        $scope.isSchemaDesignerEnabled = false;
-        $scope.hideAll();
-      }
+        // if no schemas available to select, open the pop-up immediately
+        if ($scope.schemas.length === 0) {
+          $scope.firstSchemaMessage = true;
+          $scope.showNewSchemaDialog();
+        }
+      });
     });
   };
 
@@ -239,26 +263,31 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     }
 
     $scope.resetSchema();
-    var params = {configSet: $scope.currentSchema};
-    SchemaDesigner.get(params, function (data) {
-      $scope.currentSchema = data.configSet;
-      $("#select-schema").trigger("chosen:updated");
-
-      $scope.confirmSchema = data.configSet;
-      $scope.collectionsForConfig = data.collections;
-      $scope.hasDocsOnServer = data.numDocs > 0;
-      $scope.published = data.published;
-      $scope.initDesignerSettingsFromResponse(data);
-      if ($scope.collectionsForConfig && $scope.collectionsForConfig.length > 
0) {
-        $scope.showConfirmEditSchema = true;
-      } else {
-        if ($scope.hasDocsOnServer || $scope.published) {
-          $scope.doAnalyze();
+    SchemaDesignerV2.getInfo($scope.currentSchema, function (error, data, 
response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
+        $scope.currentSchema = data.configSet;
+        $("#select-schema").trigger("chosen:updated");
+
+        $scope.confirmSchema = data.configSet;
+        $scope.collectionsForConfig = data.collections;
+        $scope.hasDocsOnServer = data.numDocs > 0;
+        $scope.published = data.published;
+        $scope.initDesignerSettingsFromResponse(data);
+        if ($scope.collectionsForConfig && $scope.collectionsForConfig.length 
> 0) {
+          $scope.showConfirmEditSchema = true;
         } else {
-          $scope.sampleMessage = "Please upload or paste some sample documents 
to build the '" + $scope.currentSchema + "' schema.";
+          if ($scope.hasDocsOnServer || $scope.published) {
+            $scope.doAnalyze();
+          } else {
+            $scope.sampleMessage = "Please upload or paste some sample 
documents to build the '" + $scope.currentSchema + "' schema.";
+          }
         }
-      }
-    }, $scope.errorHandler);
+      });
+    });
   };
 
   $scope.showNewSchemaDialog = function () {
@@ -303,9 +332,17 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     $scope.currentSchema = $scope.newSchema;
     $scope.sampleMessage = "Please upload or paste some sample documents to 
analyze for building the '" + $scope.currentSchema + "' schema.";
 
-    SchemaDesigner.post({path: "prep", configSet: $scope.newSchema, copyFrom: 
$scope.copyFrom}, null, function (data) {
-      $scope.initDesignerSettingsFromResponse(data);
-    }, $scope.errorHandler);
+    $scope.preparingSchema = true;
+    SchemaDesignerV2.prepNewSchema($scope.newSchema, {copyFrom: 
$scope.copyFrom}, function (error, data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
+        $scope.preparingSchema = false;
+        $scope.initDesignerSettingsFromResponse(data);
+      });
+    });
   };
 
   $scope.cancelAddSchema = function () {
@@ -451,9 +488,11 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     // re-apply the filters on the updated schema
     $scope.onTreeFilterOptionChanged();
 
-    // Load the Luke schema
-    Luke.schema({core: data.core}, function (schema) {
-      Luke.raw({core: data.core}, function (index) {
+    // Load the Luke schema. Route through the temporary collection so the 
request reaches its
+    // active replica even when the Admin UI is connected to a different node.
+    var lukeTarget = data.tempCollection || data.core;
+    Luke.schema({core: lukeTarget}, function (schema) {
+      Luke.raw({core: lukeTarget}, function (index) {
         $scope.luke = mergeIndexAndSchemaData(index, schema.schema);
         $scope.types = Object.keys(schema.schema.types);
         $scope.showSchemaActions = true;
@@ -656,31 +695,37 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     }
     delete $scope.addErrors; // no errors!
 
-    SchemaDesigner.post({
-      configSet: $scope.currentSchema,
-      schemaVersion: $scope.schemaVersion
-    }, addData, function (data) {
-      if (data.errors) {
-        $scope.addErrors = data.errors[0].errorMessages;
-        if (typeof $scope.addErrors === "string") {
-          $scope.addErrors = [$scope.addErrors];
+    SchemaDesignerV2.addSchemaObject($scope.currentSchema, {
+      schemaVersion: $scope.schemaVersion,
+      schemaDesignerAddRequestBody: addData
+    }, function (error, data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
         }
-      } else {
-        delete $scope.textAnalysisJson;
-        $scope.added = true;
-        $timeout(function () {
-          $scope.showAddField = false;
-          $scope.added = false;
-          var nodeId = "/";
-          if ("field" === $scope.adding) {
-            nodeId = "field/" + ("add-dynamic-field" === command ? 
data.dynamicField : data.field);
-          } else if ("type" === $scope.adding) {
-            nodeId = "type/" + data.fieldType;
+        if (data.errors) {
+          $scope.addErrors = data.errors[0].errorMessages;
+          if (typeof $scope.addErrors === "string") {
+            $scope.addErrors = [$scope.addErrors];
           }
-          $scope.onSchemaUpdated(data.configSet, data, nodeId);
-        }, 500);
-      }
-    }, $scope.errorHandler);
+        } else {
+          delete $scope.textAnalysisJson;
+          $scope.added = true;
+          $timeout(function () {
+            $scope.showAddField = false;
+            $scope.added = false;
+            var nodeId = "/";
+            if ("field" === $scope.adding) {
+              nodeId = "field/" + ("add-dynamic-field" === command ? 
data.dynamicField : data.field);
+            } else if ("type" === $scope.adding) {
+              nodeId = "type/" + data.fieldType;
+            }
+            $scope.onSchemaUpdated(data.configSet, data, nodeId);
+          }, 500);
+        }
+      });
+    });
   }
 
   function toSortedNameAndTypeList(fields, typeAttr) {
@@ -721,38 +766,44 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
       $('#show-diff-dialog').css({left: leftPos});
     }
 
-    SchemaDesigner.get({ path: "diff", configSet: $scope.currentSchema }, 
function (data) {
-      var diff = data.diff;
+    SchemaDesignerV2.getSchemaDiff($scope.currentSchema, function (error, 
data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
+        var diff = data.diff;
 
-      var dynamicFields = diff.dynamicFields;
-      var enableDynamicFields = data.enableDynamicFields !== null ? 
data.enableDynamicFields : true;
-      if (!enableDynamicFields) {
-        dynamicFields = null;
-      }
+        var dynamicFields = diff.dynamicFields;
+        var enableDynamicFields = data.enableDynamicFields !== null ? 
data.enableDynamicFields : true;
+        if (!enableDynamicFields) {
+          dynamicFields = null;
+        }
 
-      $scope.diffSource = data["diff-source"];
-      $scope.schemaDiff = {
-        "fieldsDiff": diff.fields,
-        "addedFields": [],
-        "removedFields": [],
-        "fieldTypesDiff": diff.fieldTypes,
-        "removedTypes": [],
-        "dynamicFieldsDiff": dynamicFields,
-        "copyFieldsDiff": diff.copyFields
-      }
-      if (diff.fields && diff.fields.added) {
-        $scope.schemaDiff.addedFields = toSortedFieldList(diff.fields.added);
-      }
-      if (diff.fields && diff.fields.removed) {
-        $scope.schemaDiff.removedFields = 
toSortedNameAndTypeList(diff.fields.removed, "type");
-      }
-      if (diff.fieldTypes && diff.fieldTypes.removed) {
-        $scope.schemaDiff.removedTypes = 
toSortedNameAndTypeList(diff.fieldTypes.removed, "class");
-      }
+        $scope.diffSource = data["diff-source"];
+        $scope.schemaDiff = {
+          "fieldsDiff": diff.fields,
+          "addedFields": [],
+          "removedFields": [],
+          "fieldTypesDiff": diff.fieldTypes,
+          "removedTypes": [],
+          "dynamicFieldsDiff": dynamicFields,
+          "copyFieldsDiff": diff.copyFields
+        }
+        if (diff.fields && diff.fields.added) {
+          $scope.schemaDiff.addedFields = toSortedFieldList(diff.fields.added);
+        }
+        if (diff.fields && diff.fields.removed) {
+          $scope.schemaDiff.removedFields = 
toSortedNameAndTypeList(diff.fields.removed, "type");
+        }
+        if (diff.fieldTypes && diff.fieldTypes.removed) {
+          $scope.schemaDiff.removedTypes = 
toSortedNameAndTypeList(diff.fieldTypes.removed, "class");
+        }
 
-      $scope.schemaDiffExists = !(diff.fields == null && diff.fieldTypes == 
null && dynamicFields == null && diff.copyFields == null);
-      $scope.showDiff = true;
-    }, $scope.errorHandler);
+        $scope.schemaDiffExists = !(diff.fields == null && diff.fieldTypes == 
null && dynamicFields == null && diff.copyFields == null);
+        $scope.showDiff = true;
+      });
+    });
   }
 
   $scope.togglePublish = function (event) {
@@ -789,22 +840,28 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
   }
   $scope.addCopyField = function () {
     delete $scope.addCopyFieldErrors;
-    var data = {"add-copy-field": $scope.copyField};
-    SchemaDesigner.post({
-      configSet: $scope.currentSchema,
-      schemaVersion: $scope.schemaVersion
-    }, data, function (data) {
-      if (data.errors) {
-        $scope.addCopyFieldErrors = data.errors[0].errorMessages;
-        if (typeof $scope.addCopyFieldErrors === "string") {
-          $scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
+    var copyFieldData = {"add-copy-field": $scope.copyField};
+    SchemaDesignerV2.addSchemaObject($scope.currentSchema, {
+      schemaVersion: $scope.schemaVersion,
+      schemaDesignerAddRequestBody: copyFieldData
+    }, function (error, data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
         }
-      } else {
-        $scope.showAddCopyField = false;
-        // TODO:
-        //$timeout($scope.refresh, 1500);
-      }
-    }, $scope.errorHandler);
+        if (data.errors) {
+          $scope.addCopyFieldErrors = data.errors[0].errorMessages;
+          if (typeof $scope.addCopyFieldErrors === "string") {
+            $scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
+          }
+        } else {
+          $scope.showAddCopyField = false;
+          // TODO:
+          //$timeout($scope.refresh, 1500);
+        }
+      });
+    });
   }
 
   $scope.toggleAnalyzer = function (analyzer) {
@@ -834,22 +891,28 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     }
 
     var field = $scope.selectedNode.name;
-    var params = {path: "sample"};
-    params.configSet = $scope.currentSchema;
-    params.uniqueKeyField = $scope.uniqueKeyField;
-    params.field = field;
+    var opts = {uniqueKeyField: $scope.uniqueKeyField, field: field};
 
     if ($scope.sampleDocId) {
-      params.docId = $scope.sampleDocId;
+      opts.docId = $scope.sampleDocId;
     } // else the server will pick the first doc with a non-empty text value 
for the desired field
 
-    SchemaDesigner.get(params, function (data) {
-      $scope.sampleDocId = data[$scope.uniqueKeyField];
-      $scope.indexText = data[field];
-      if (data.analysis && data.analysis["field_names"]) {
-        $scope.result = 
processFieldAnalysisData(data.analysis["field_names"][field]);
-      }
-    }, $scope.errorHandler);
+    SchemaDesignerV2.getSampleValue($scope.currentSchema, opts, function 
(error, data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
+        // FlexibleSolrJerseyResponse only declares responseHeader/error since 
the sample value and
+        // analysis are dynamic per-field data; read the raw parsed body 
instead of the typed `data`.
+        var raw = (response && response.body) || {};
+        $scope.sampleDocId = raw[$scope.uniqueKeyField];
+        $scope.indexText = raw[field];
+        if (raw.analysis && raw.analysis["field_names"]) {
+          $scope.result = 
processFieldAnalysisData(raw.analysis["field_names"][field]);
+        }
+      });
+    });
   };
 
   $scope.changeLanguages = function () {
@@ -881,23 +944,28 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
 
   $scope.updateFile = function () {
     var nodeId = "files/" + $scope.selectedFile;
-    var params = {path: "file", file: $scope.selectedFile, configSet: 
$scope.currentSchema};
 
     $scope.updateWorking = true;
     $scope.updateStatusMessage = "Updating file ...";
 
-    SchemaDesigner.put(params, $scope.fileNodeText, function (data) {
-      if (data.updateFileError) {
-        if (data.fileContent) {
-          $scope.fileNodeText = data.fileContent;
+    SchemaDesignerV2.updateFileContents($scope.currentSchema, 
$scope.fileNodeText, {file: $scope.selectedFile}, function (error, data, 
response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
         }
-        $scope.updateFileError = data.updateFileError;
-      } else {
-        delete $scope.updateFileError;
-        $scope.updateStatusMessage = "File '"+$scope.selectedFile+"' updated.";
-        $scope.onSchemaUpdated(data.configSet, data, nodeId);
-      }
-    }, $scope.errorHandler);
+        if (data.updateFileError) {
+          if (data.fileContent) {
+            $scope.fileNodeText = data.fileContent;
+          }
+          $scope.updateFileError = data.updateFileError;
+        } else {
+          delete $scope.updateFileError;
+          $scope.updateStatusMessage = "File '"+$scope.selectedFile+"' 
updated.";
+          $scope.onSchemaUpdated(data.configSet, data, nodeId);
+        }
+      });
+    });
   };
 
   $scope.onSelectFileNode = function (id, doSelectOnTree) {
@@ -1387,31 +1455,37 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
     $scope.updateWorking = true;
     $scope.updateStatusMessage = "Updating " + $scope.selectedType + " ...";
 
-    SchemaDesigner.put({
-      configSet: $scope.currentSchema,
-      schemaVersion: $scope.schemaVersion
-    }, putData, function (data) {
+    SchemaDesignerV2.updateSchemaObject($scope.currentSchema, {
+      schemaVersion: $scope.schemaVersion,
+      schemaDesignerUpdateRequestBody: putData
+    }, function (error, data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
 
-      var nodeType = data.updateType;
-      $scope.schemaVersion = data.schemaVersion;
-      $scope.currentSchema = data.configSet;
-      $scope.core = data.core;
+        var nodeType = data.updateType;
+        $scope.schemaVersion = data.schemaVersion;
+        $scope.currentSchema = data.configSet;
+        $scope.core = data.core;
 
-      $scope.selectedNode = data[nodeType];
-      $scope.selectedNode.href = href;
-      $scope.selectedNode.id = id;
+        $scope.selectedNode = data[nodeType];
+        $scope.selectedNode.href = href;
+        $scope.selectedNode.id = id;
 
-      var name = nodeType === "field" ? $scope.selectedNode.type : 
$scope.selectedNode.name;
-      $scope.initTypeAnalysisInfo(name, "type");
-      $scope.showFieldDetails = true;
+        var name = nodeType === "field" ? $scope.selectedNode.type : 
$scope.selectedNode.name;
+        $scope.initTypeAnalysisInfo(name, "type");
+        $scope.showFieldDetails = true;
 
-      if (nodeType === "field" && $scope.selectedNode.tokenized) {
-        $scope.showAnalysis = true;
-        $scope.updateSampleDocId();
-      }
+        if (nodeType === "field" && $scope.selectedNode.tokenized) {
+          $scope.showAnalysis = true;
+          $scope.updateSampleDocId();
+        }
 
-      $scope.onSchemaUpdated($scope.currentSchema, data, href);
-    }, $scope.errorHandler);
+        $scope.onSchemaUpdated($scope.currentSchema, data, href);
+      });
+    });
   };
 
   // TODO: These are copied from analysis.js, so move to a shared location for 
both vs. duplicating
@@ -1489,34 +1563,38 @@ solrAdminApp.controller('SchemaDesignerController', 
function ($scope, $timeout,
   };
 
   $scope.doPublish = function () {
-    var params = {
-      path: "publish",
-      configSet: $scope.currentSchema,
+    var opts = {
       schemaVersion: $scope.schemaVersion,
       reloadCollections: $scope.reloadOnPublish,
       cleanupTemp: true,
       disableDesigner: $scope.disableDesigner
     };
     if ($scope.newCollection && $scope.newCollection.name) {
-      params.newCollection = $scope.newCollection.name;
-      params.numShards = $scope.newCollection.numShards;
-      params.replicationFactor = $scope.newCollection.replicationFactor;
-      params.indexToCollection = $scope.newCollection.indexToCollection;
-    }
-    SchemaDesigner.put(params, null, function (data) {
-      $scope.schemaVersion = data.schemaVersion;
-      $scope.currentSchema = data.configSet;
+      opts.newCollection = $scope.newCollection.name;
+      opts.numShards = $scope.newCollection.numShards;
+      opts.replicationFactor = $scope.newCollection.replicationFactor;
+      opts.indexToCollection = $scope.newCollection.indexToCollection;
+    }
+    SchemaDesignerV2.publish($scope.currentSchema, opts, function (error, 
data, response) {
+      $timeout(function () {
+        if (error) {
+          $scope.errorHandlerV2(response);
+          return;
+        }
+        $scope.schemaVersion = data.schemaVersion;
+        $scope.currentSchema = data.configSet;
 
-      delete $scope.selectedNode;
-      $scope.currentSchema = "";
-      delete $scope.newSchema;
-      $scope.showPublish = false;
-      $scope.refresh();
+        delete $scope.selectedNode;
+        $scope.currentSchema = "";
+        delete $scope.newSchema;
+        $scope.showPublish = false;
+        $scope.refresh();
 
-      if (data.newCollection) {
-        $window.location.href = "#/" + data.newCollection + 
"/collection-overview";
-      }
-    }, $scope.errorHandler);
+        if (data.newCollection) {
+          $window.location.href = "#/" + data.newCollection + 
"/collection-overview";
+        }
+      });
+    });
   };
 
   $scope.downloadConfig = function () {
diff --git a/solr/webapp/web/js/angular/services.js 
b/solr/webapp/web/js/angular/services.js
index f75a2f9d3c6..1272fceff94 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -148,6 +148,12 @@ solrAdminServices.factory('Metrics',
       delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
       return new solrApi.SegmentsApi();
     })
+.factory('SchemaDesignerV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.SchemaDesignerApi();
+    })
 .factory('Collections',
   ['$resource', function ($resource) {
     // v2 ClusterAPI (/api/cluster) delegates straight through to the same v1 
CollectionsHandler
@@ -208,7 +214,7 @@ solrAdminServices.factory('Metrics',
     // v2 NodeThreadsAPI (/api/node/threads) still just delegates straight 
through to the same v1
     // ThreadDumpHandler, so the response shape is byte-identical -- no 
generated solrApi client
     // class exists for it (it predates the OpenAPI-based v2 API framework), 
so this stays a plain
-    // $resource, like SchemaDesigner/Security.
+    // $resource, like Security and (partially) SchemaDesigner.
     return $resource('/api/node/threads', {'wt':'json', '_':Date.now()});
   }])
 .factory('Replication',
@@ -364,11 +370,16 @@ solrAdminServices.factory('Metrics',
 }])
 .factory('SchemaDesigner',
    ['$resource', function($resource) {
+     // Schema Designer's analyze (sample-doc upload/paste, dynamic 
content-type) and query
+     // (arbitrary forwarded Solr query params) endpoints read their request 
bodies/params in ways
+     // the OpenAPI-generated SchemaDesignerApi client can't express: 
analyze() always sends a null
+     // body (the server deliberately reads the raw content stream, dispatched 
by Content-Type,
+     // rather than a formal parameter) and query() takes no query params at 
all (the server
+     // forwards arbitrary SolrParams straight through). Both stay on this 
plain $resource, like
+     // Threads/Collections/ParamSet. Every other Schema Designer endpoint 
uses SchemaDesignerV2.
      return $resource('/api/schema-designer/:configSet/:path', {wt: 'json', 
path: '@path', configSet: '@configSet', filePath: '@filePath', _:Date.now()}, {
        get: {method: "GET"},
        post: {method: "POST", timeout: 90000},
-       put: {method: "PUT"},
-       delete: {method: "DELETE"},
        postXml: {headers: {'Content-type': 'text/xml'}, method: "POST", 
timeout: 90000},
        postCsv: {headers: {'Content-type': 'application/csv'}, method: "POST", 
timeout: 90000},
        upload: {method: "POST", transformRequest: angular.identity, headers: 
{'Content-Type': undefined}, timeout: 90000}
diff --git a/solr/webapp/web/partials/schema-designer.html 
b/solr/webapp/web/partials/schema-designer.html
index 4d7fbd5b4b3..c5c0e1b6e7e 100644
--- a/solr/webapp/web/partials/schema-designer.html
+++ b/solr/webapp/web/partials/schema-designer.html
@@ -487,7 +487,9 @@ limitations under the License.
             </div>
             <div id="analyze-buttons">
               <p class="clearfix note" ng-show="sampleMessage && !fileUpload 
&& !sampleDocuments"><span>{{sampleMessage}}</span></p>
-              <button type="submit" ng-click="doAnalyze()" id="analyze" 
ng-show="currentSchema && (hasDocsOnServer || sampleDocuments || 
fileUpload)"><span>Analyze Documents</span></button>
+              <button type="submit" ng-click="doAnalyze()" id="analyze"
+                      ng-show="currentSchema && (hasDocsOnServer || 
sampleDocuments || fileUpload)"
+                      ng-disabled="preparingSchema || 
updateWorking"><span>Analyze Documents</span></button>
             </div>
           </form>
         </div>

Reply via email to