From 37d5488ebff76f2b4d961d0cd4c7498d5d9f30e8 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 26 Aug 2026 09:23:18 -0400 Subject: [PATCH 1/7] Flesh out what JS and Jackson both expect in attribute names. --- .../solr/client/api/endpoint/SchemaDesignerApi.java | 8 +++++++- .../client/api/model/SchemaDesignerAddRequestBody.java | 6 +----- .../client/api/model/SchemaDesignerUpdateRequestBody.java | 3 +++ .../solr/handler/designer/TestSchemaDesignerSolrJ.java | 4 +++- solr/solrj/src/resources/java-template/api.mustache | 6 +++--- 5 files changed, 17 insertions(+), 10 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 572891d6ec55..d319471857dc 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 @@ SchemaDesignerAddResponse addSchemaObject( 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 821cabc4246a..2bf9bd374ca3 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,29 +17,25 @@ 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; /** * Request body for the Schema Designer add endpoint. Exactly one of the four fields should be * populated; the populated field's name is the action and its value carries the schema-object * attributes (e.g. for {@code addField}: {@code name}, {@code type}, {@code stored}, etc.). + * */ public class SchemaDesignerAddRequestBody { - @Schema(name = "addField") @JsonProperty("add-field") public Map addField; - @Schema(name = "addDynamicField") @JsonProperty("add-dynamic-field") public Map addDynamicField; - @Schema(name = "addCopyField") @JsonProperty("add-copy-field") public Map addCopyField; - @Schema(name = "addFieldType") @JsonProperty("add-field-type") public Map 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 54b9bb9e56cb..e88114462e59 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 @@ 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 @@ * 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 additionalProperties = new HashMap<>(); + @Schema(hidden = true) @JsonAnyGetter public Map getAdditionalProperties() { return additionalProperties; 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 d755b6964d99..9d3a4fe13841 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 void testTypedBodyRoundTrip() throws Exception { 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 81cdff1b3a87..ad184adec91b 100644 --- a/solr/solrj/src/resources/java-template/api.mustache +++ b/solr/solrj/src/resources/java-template/api.mustache @@ -181,11 +181,11 @@ public class {{classname}} { // TODO find a way to add required parameters in the request body to the class constructor {{#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}} From 5d481311bff21952022c1e4d211ad33170762c1b Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 26 Aug 2026 10:31:12 -0400 Subject: [PATCH 2/7] I goofed editing javadocs --- .../solr/client/api/model/SchemaDesignerAddRequestBody.java | 1 - 1 file changed, 1 deletion(-) 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 2bf9bd374ca3..25e0da72a79a 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 @@ -23,7 +23,6 @@ * Request body for the Schema Designer add endpoint. Exactly one of the four fields should be * populated; the populated field's name is the action and its value carries the schema-object * attributes (e.g. for {@code addField}: {@code name}, {@code type}, {@code stored}, etc.). - * */ public class SchemaDesignerAddRequestBody { From 62f40f18472d7f6b5bdbdf88c80b4c9c48068cfc Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 26 Aug 2026 10:31:29 -0400 Subject: [PATCH 3/7] Same modeling needed for upsert. --- .../solr/client/api/model/UpsertDynamicFieldOperation.java | 3 +++ .../org/apache/solr/client/api/model/UpsertFieldOperation.java | 3 +++ .../apache/solr/client/api/model/UpsertFieldTypeOperation.java | 3 +++ 3 files changed, 9 insertions(+) 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 db010fbacca4..2a0376fafc39 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 @@ 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 additionalProperties = new HashMap<>(); + @Schema(hidden = true) @JsonAnyGetter public Map 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 c6a7d1c3c322..6946f3536c8a 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 @@ 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 additionalProperties = new HashMap<>(); + @Schema(hidden = true) @JsonAnyGetter public Map 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 6a517830b2ed..d0967b2a0556 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,9 +19,11 @@ 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; @@ -31,6 +33,7 @@ public class UpsertFieldTypeOperation extends SchemaChange { // Used for setting analyzers, index and stored settings, etc. private Map additionalProperties = new HashMap<>(); + @Schema(hidden = true) @JsonAnyGetter public Map getAdditionalProperties() { return additionalProperties; From 1dfdd5ab47857d8a449630d3ac253b6b7faaad67 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 4 Sep 2026 08:29:30 -0400 Subject: [PATCH 4/7] Use SchemaDesigner generated client in the Solr Admin. All except two endpoints that are not strong typed. --- .../js/angular/controllers/schema-designer.js | 448 ++++++++++-------- solr/webapp/web/js/angular/services.js | 17 +- 2 files changed, 274 insertions(+), 191 deletions(-) diff --git a/solr/webapp/web/js/angular/controllers/schema-designer.js b/solr/webapp/web/js/angular/controllers/schema-designer.js index 7ec4282bb053..6e2415f6176c 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 = []; @@ -68,20 +68,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 +167,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 +261,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 +330,15 @@ 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); + SchemaDesignerV2.prepNewSchema($scope.newSchema, {copyFrom: $scope.copyFrom}, function (error, data, response) { + $timeout(function () { + if (error) { + $scope.errorHandlerV2(response); + return; + } + $scope.initDesignerSettingsFromResponse(data); + }); + }); }; $scope.cancelAddSchema = function () { @@ -656,31 +689,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 +760,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 +834,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 +885,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 +938,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 +1449,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 +1557,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 f75a2f9d3c6c..1272fceff94f 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} From 4d82c0b52105ed4a7a889e3fcc57fd7840e9a4b2 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 4 Sep 2026 09:09:19 -0400 Subject: [PATCH 5/7] Enable the selenum admin ui test for Schema Designer, bringing in some fixes. --- .../webapp/AdminUiSchemaDesignerTest.java | 38 +++---------------- .../js/angular/controllers/schema-designer.js | 12 ++++-- solr/webapp/web/partials/schema-designer.html | 4 +- 3 files changed, 17 insertions(+), 37 deletions(-) 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 85a76aec800c..670a9df12b37 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 @@ * Happy-path test of the Schema Designer screen: create a new schema, paste a sample document and * let the designer analyze it. * - *

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. + *

The Analyze action remains disabled until creation of the mutable schema has completed, so a + * fast user cannot race the prep and analyze requests. */ -@LuceneTestCase.AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-18347") public class AdminUiSchemaDesignerTest extends AdminUiTestBase { @Test @@ -47,34 +44,9 @@ public void testDesignSchemaFromSampleDocument() throws Exception { 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(); } } diff --git a/solr/webapp/web/js/angular/controllers/schema-designer.js b/solr/webapp/web/js/angular/controllers/schema-designer.js index 6e2415f6176c..a8d4c963245d 100644 --- a/solr/webapp/web/js/angular/controllers/schema-designer.js +++ b/solr/webapp/web/js/angular/controllers/schema-designer.js @@ -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) { @@ -330,12 +332,14 @@ 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."; + $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); }); }); @@ -484,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; diff --git a/solr/webapp/web/partials/schema-designer.html b/solr/webapp/web/partials/schema-designer.html index 4d7fbd5b4b38..c5c0e1b6e7e0 100644 --- a/solr/webapp/web/partials/schema-designer.html +++ b/solr/webapp/web/partials/schema-designer.html @@ -487,7 +487,9 @@

Sample Documents

{{sampleMessage}}

- +
From 052cefd231336cfcdae15f4c102f7d94ce7b8bd0 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 4 Sep 2026 10:56:56 -0400 Subject: [PATCH 6/7] Not loving this change, but apparently how we refer to a class wasn't the best way to do it. --- .../solr/client/api/model/UpsertFieldTypeOperation.java | 7 ++++++- .../api/model/SchemaChangeOperationSerializationTest.java | 4 ++-- .../org/apache/solr/handler/admin/api/UpdateSchema.java | 2 +- .../src/java/org/apache/solr/schema/SchemaManager.java | 4 ++-- .../handler/admin/api/V2UpdateSchemaErrorCaseTests.java | 2 +- solr/solrj/src/resources/java-template/api.mustache | 5 +++++ 6 files changed, 17 insertions(+), 7 deletions(-) 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 d0967b2a0556..60ee25122c81 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 @@ -27,8 +27,13 @@ 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 additionalProperties = new HashMap<>(); 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 48afd83e694d..a0606bd6d3d7 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 void testAddFieldType() throws Exception { 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 void testReplaceFieldType() throws Exception { 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 1bb3f51c4c32..6bb00ea00f60 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 SolrJerseyResponse addFieldType(String fieldTypeName, UpsertFieldTypeOper 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 a65eb18df6aa..05efe512c21d 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 enum OpType { 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 boolean perform(SchemaChange op, SchemaManager mgr) throws SchemaOperatio 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 54d063dc31e9..d981f572a0b1 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 void testDeleteDynamicFieldOperationRequiresFieldName() { @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/solrj/src/resources/java-template/api.mustache b/solr/solrj/src/resources/java-template/api.mustache index ad184adec91b..4aae48285610 100644 --- a/solr/solrj/src/resources/java-template/api.mustache +++ b/solr/solrj/src/resources/java-template/api.mustache @@ -179,6 +179,11 @@ 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. 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 {{name}} {{description}} From 7eec2f7effd20639e3d7c61bd612f8f2adc955fd Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 4 Sep 2026 12:25:28 -0400 Subject: [PATCH 7/7] Lets add one more test from a UI perspective. --- .../org/apache/solr/webapp/AdminUiSchemaDesignerTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 670a9df12b37..739c6312e6eb 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java @@ -48,5 +48,13 @@ public void testDesignSchemaFromSampleDocument() throws Exception { 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(); } }