From b9c899bab74697333e56f9bc4d46f0b7b83f8922 Mon Sep 17 00:00:00 2001 From: rayshrey <121871912+rayshrey@users.noreply.github.com> Date: Wed, 26 Aug 2026 05:44:26 -0700 Subject: [PATCH 1/3] SOLR-18347: Guard null core selection in Admin UI --- .../src/test/org/apache/solr/webapp/AdminUiTestBase.java | 6 ------ solr/webapp/web/js/angular/app.js | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java index 5d0ea91fe9f4..ac471487420c 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java @@ -537,12 +537,6 @@ protected static void assertNoSevereConsoleErrors(String... allowedSubstrings) { // the ui-grid icon font referenced from ui-grid.min.css is not shipped with // the webapp at all, so it 404s in production too .filter(entry -> !entry.getMessage().contains("fonts/ui-grid")) - // benign race in the shared menu code: showCore() fires with a null core - // while the per-collection menu resolves after navigation - .filter( - entry -> - !(entry.getMessage().contains("reading 'name'") - && entry.getMessage().contains("showCore"))) .toList(); assertTrue("Severe browser console errors: " + severe, severe.isEmpty()); } diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js index 1ff0cb824f2d..9e929407a336 100644 --- a/solr/webapp/web/js/angular/app.js +++ b/solr/webapp/web/js/angular/app.js @@ -628,7 +628,9 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $ } $scope.showCore = function(core) { - $location.url("/" + core.name + "/core-overview"); + if (core) { + $location.url("/" + core.name + "/core-overview"); + } } $scope.showCollection = function(collection) { From e7e66a51b5232bd71ef756584c060a3f417d5ebb Mon Sep 17 00:00:00 2001 From: rayshrey <121871912+rayshrey@users.noreply.github.com> Date: Wed, 26 Aug 2026 06:36:07 -0700 Subject: [PATCH 2/3] SOLR-18347: Fix remaining Admin UI defects --- .../solr-18347-admin-ui-defects.yml | 9 ++++ dev-docs/admin-ui-tests.md | 19 ------- .../solr/handler/PingRequestHandler.java | 5 +- .../solr/handler/PingRequestHandlerTest.java | 4 ++ solr/webapp/build.gradle | 31 ------------ .../webapp/AdminUiCollectionScreensTest.java | 4 +- .../webapp/AdminUiCollectionsScreenTest.java | 10 +++- .../AdminUiCoreAdminStandaloneTest.java | 4 +- .../webapp/AdminUiSchemaDesignerTest.java | 38 ++------------ .../apache/solr/webapp/AdminUiSmokeTest.java | 4 +- .../apache/solr/webapp/AdminUiTestBase.java | 50 +------------------ solr/webapp/web/index.html | 1 - .../web/js/angular/controllers/collections.js | 23 +++++---- .../js/angular/controllers/core-overview.js | 10 ++-- .../js/angular/controllers/schema-designer.js | 12 +++-- solr/webapp/web/js/angular/services.js | 6 --- solr/webapp/web/partials/collections.html | 3 +- solr/webapp/web/partials/schema-designer.html | 4 +- 18 files changed, 66 insertions(+), 171 deletions(-) create mode 100644 changelog/unreleased/solr-18347-admin-ui-defects.yml diff --git a/changelog/unreleased/solr-18347-admin-ui-defects.yml b/changelog/unreleased/solr-18347-admin-ui-defects.yml new file mode 100644 index 000000000000..80f18ea21e8a --- /dev/null +++ b/changelog/unreleased/solr-18347-admin-ui-defects.yml @@ -0,0 +1,9 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Fix five Admin UI defects found by Selenium testing +type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other +authors: + - name: Shrey Narayan + nick: NextbrickInc +links: + - name: SOLR-18347 + url: https://issues.apache.org/jira/browse/SOLR-18347 diff --git a/dev-docs/admin-ui-tests.md b/dev-docs/admin-ui-tests.md index 73ad2a9f65b7..c88bf05786bd 100644 --- a/dev-docs/admin-ui-tests.md +++ b/dev-docs/admin-ui-tests.md @@ -44,28 +44,9 @@ deliberately does not do. ## Known limitations -- The generated js-client bundle (`libs/solr/index.js`) only exists inside the - built WAR, not in the source tree tests serve from, so the build hands its - location to the test JVM in `tests.ui.jsclient.bundle`. The bundle (and its - node/npm toolchain) is only built when `-Ptests.selenium=true` enables the - tests, keeping node off the default test build chain. With the js-client - build turned off (`-PdisableJsClient=true`) the bundle cannot be built, so - the build disables these tests with a warning — and fails with an error if - `-Ptests.selenium=true` was passed as well. - Every test cluster in the JVM registers a log-watcher appender under the same name in the shared log4j config, so a later cluster's watcher can be blind; the events-viewer test detects this via the API and skips itself. -- The shared menu code logs a benign - `TypeError: Cannot read properties of null (reading 'name')` from - `$scope.showCore` while the per-collection menu resolves (filtered in the - console-error assertion; tracked in - [SOLR-18347](https://issues.apache.org/jira/browse/SOLR-18347)). -- The core overview ping widget answers 503 when the configset has no - healthcheck file (allowed in the affected tests; tracked in - [SOLR-18347](https://issues.apache.org/jira/browse/SOLR-18347)). -- The Schema Designer's backend transiently fails its own prep/analyze calls - with "version mismatch, retry" and recovers via its retry dialog; its API - errors are excluded from the console-error assertion. - ASF Jenkins jobs do not pass `-Ptests.selenium=true`, so these tests do not run there (a nightly job could opt in if its build nodes have a browser). In CI they run via the GitHub Actions workflow diff --git a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java index f7eaa97db78b..ba7ebd2d6889 100644 --- a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java @@ -213,10 +213,7 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw break; case STATUS: if (healthcheck == null) { - SolrException e = - new SolrException( - SolrException.ErrorCode.SERVICE_UNAVAILABLE, "healthcheck not configured"); - rsp.setException(e); + rsp.add("status", "not_configured"); } else { rsp.add("status", isPingDisabled() ? "disabled" : "enabled"); } diff --git a/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java index 3f151e51efca..4180be913579 100644 --- a/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java @@ -85,6 +85,10 @@ public void testPingWithNoHealthCheck() throws Exception { rsp = makeRequest(handler, req("action", "ping")); assertEquals("OK", rsp.getValues().get("status")); + + rsp = makeRequest(handler, req("action", "status")); + assertEquals("not_configured", rsp.getValues().get("status")); + assertNull(rsp.getException()); } public void testEnablingServer() throws Exception { diff --git a/solr/webapp/build.gradle b/solr/webapp/build.gradle index 184391590c56..4f6831bf659b 100644 --- a/solr/webapp/build.gradle +++ b/solr/webapp/build.gradle @@ -37,7 +37,6 @@ configurations { war {} serverLib solrCore - generatedJSClientBundle generatedUIBundle } @@ -47,10 +46,6 @@ dependencies { solrCore project(":solr:core") implementation(configurations.solrCore - configurations.serverLib) - if (gradle.ext.withJsClient) { - generatedJSClientBundle project(path: ":solr:webapp:js-client", configuration: "jsClientBundle") - } - if (gradle.ext.withUiModule) { generatedUIBundle project(path: ":solr:ui", configuration: "wasmJsUIBundle") } @@ -88,37 +83,11 @@ tasks.withType(Test).configureEach { systemProperty 'tests.selenium.chrome.binary', chromeBinary } - // the Admin UI tests exercise the generated js-client bundle; a stub would only grow - // stale as the UI adopts more of the v2 API, so without the bundle the tests cannot - // run at all. The bundle (and thereby its node/npm toolchain) is only wired in when - // the tests are explicitly enabled, keeping node off the default test build chain. - def seleniumEnabled = Boolean.parseBoolean(Objects.toString(propertyOrDefault('tests.selenium', 'false'))) - if (seleniumEnabled && gradle.ext.withJsClient) { - def jsClientBundle = configurations.generatedJSClientBundle - inputs.files(jsClientBundle).withPropertyName('jsClientBundle') - doFirst { - systemProperty 'tests.ui.jsclient.bundle', new File(jsClientBundle.singleFile, 'index.js') - } - } else if (seleniumEnabled) { - doFirst { - throw new GradleException('Cannot run the Admin UI tests (-Ptests.selenium=true): ' + - '-PdisableJsClient=true excludes the js-client bundle they need. Drop one of the two flags.') - } - } else if (!gradle.ext.withJsClient) { - enabled = false - logger.warn('NOTE: :solr:webapp tests are disabled because -PdisableJsClient=true ' + - 'excludes the js-client bundle the Admin UI tests need.') - } } war { from("web") - // note: nonetheless may be disabled, copying nothing - from(configurations.generatedJSClientBundle, { - into "libs/solr" - }) - // note: nonetheless may be disabled, copying nothing from(configurations.generatedUIBundle, { into "ui" diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java index f758d46e11b4..b4e82fb54578 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java @@ -101,7 +101,7 @@ public void testCoreOverviewShowsStats() { openPage(coreName + "/core-overview", By.id("dashboard")); waitForPageContains("Num Docs"); waitForPageContains(Integer.toString(NUM_DOCS)); - // the ping widget answers 503 when the configset has no healthcheck file - assertNoSevereConsoleErrors("/admin/ping"); + waitForPageContains("Ping request handler is not configured with a healthcheck file."); + assertNoSevereConsoleErrors(); } } diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java index 8dad9b0be8a1..e8e44ad6ebd7 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java @@ -142,8 +142,8 @@ public void testAddAndDeleteReplicaViaUi() throws Exception { @Test public void testReloadCollectionViaUi() throws Exception { - // reloading resets the core's start time; that proves the action end-to-end, - // unlike the UI success indicator which only flashes for a second + // Reloading resets the core's start time, and the success state remains visible long enough + // for a user to notice instead of disappearing after one second. String coreName = coreNameOnNode0(COLLECTION); Object startTimeBefore = coreStartTime(coreName); @@ -152,6 +152,12 @@ public void testReloadCollectionViaUi() throws Exception { waitUntil( "core start time should change after reload", () -> !startTimeBefore.equals(coreStartTime(coreName))); + WebElement reload = waitFor(By.cssSelector("#reload.success")); + assertEquals("Reloaded", reload.getText()); + Thread.sleep(1500); + assertTrue( + "reload success should remain visible", reload.getAttribute("class").contains("success")); + assertEquals("Reloaded", reload.getText()); assertNoSevereConsoleErrors(); } diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCoreAdminStandaloneTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCoreAdminStandaloneTest.java index 0508fd636e79..ea801c7d8243 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCoreAdminStandaloneTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCoreAdminStandaloneTest.java @@ -63,8 +63,8 @@ public void testStandaloneMenus() { openPage("swapa/core-overview", By.id("dashboard")); waitFor(By.cssSelector("#core-menu .query")); waitFor(By.cssSelector("#core-menu .replication")); - // the ping widget answers 503 when the configset has no healthcheck file - assertNoSevereConsoleErrors("/admin/ping"); + waitForPageContains("Ping request handler is not configured with a healthcheck file."); + assertNoSevereConsoleErrors(); } @Test 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/src/test/org/apache/solr/webapp/AdminUiSmokeTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSmokeTest.java index 5dfc5b2a4517..a20059160181 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSmokeTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSmokeTest.java @@ -91,9 +91,7 @@ public void testCoreScreens() { coreName + "/plugins", By.id("plugins"), coreName + "/segments", By.id("segments")); screens.forEach(this::smoke); - // the ping widget on the overview answers 503 when no healthcheck file is configured, - // as is the case for the _default configset - smoke(coreName + "/core-overview", By.id("dashboard"), "/admin/ping"); + smoke(coreName + "/core-overview", By.id("dashboard")); } private void smoke(String route, By anchor) { diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java index ac471487420c..89718660c5f8 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java @@ -19,9 +19,6 @@ import com.carrotsearch.randomizedtesting.ThreadFilter; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -51,7 +48,6 @@ import org.apache.solr.embedded.JettySolrRunner; import org.apache.solr.util.ExternalPaths; import org.apache.solr.util.SeleniumTest; -import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; @@ -128,39 +124,6 @@ public abstract class AdminUiTestBase extends SolrCloudTestCase { /** The standalone node backing {@link #adminApi} when {@link #standaloneMode} is set. */ protected static JettySolrRunner standaloneJetty; - /** - * Serves the generated js-client bundle the AngularJS UI expects at {@code libs/solr/index.js}: - * its {@code CollectionsV2} service fails to instantiate without the {@code solrApi} global, - * taking the whole Collections screen down with it. The bundle is built by {@code - * :solr:webapp:js-client} and its location handed to the test JVM in {@code - * tests.ui.jsclient.bundle}; it only exists inside the built webapp, not in the source tree tests - * serve from. - */ - public static class JsClientServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { - resp.setContentType("text/javascript"); - Files.copy(jsClientBundlePath(), resp.getOutputStream()); - } - } - - /** - * The generated js-client bundle: handed to us by the build in {@code tests.ui.jsclient.bundle}, - * with the js-client build's output location as a fallback so tests can also run from an IDE - * (after a Gradle build has produced the bundle). Null when unavailable. - */ - private static Path jsClientBundlePath() { - String path = EnvUtils.getProperty("tests.ui.jsclient.bundle"); - if (path == null && ExternalPaths.SOURCE_HOME == null) { - return null; - } - Path bundle = - path != null - ? Path.of(path) - : ExternalPaths.SOURCE_HOME.resolve("webapp/js-client/build/jsClientBundle/index.js"); - return Files.isReadable(bundle) ? bundle : null; - } - /** Ignores threads spawned by Selenium and the JDK http client it uses. */ public static class WebDriverThreadsFilter implements ThreadFilter { @Override @@ -189,12 +152,6 @@ public static void startClusterAndBrowser() throws Exception { "Selenium tests are enabled (-Ptests.selenium=true) but no Chrome/Chromium binary was" + " found; install one or point -Dtests.selenium.chrome.binary at it"); } - if (jsClientBundlePath() == null) { - fail( - "No generated js-client bundle available; the Gradle build wires it via" - + " tests.ui.jsclient.bundle (is -PdisableJsClient=true set?)"); - } - // metrics are off by default in test clusters, but UI screens (e.g. Plugins) need them; // restored after the class by SolrTestCase's SystemPropertiesRestoreRule System.setProperty("metricsEnabled", "true"); @@ -241,12 +198,9 @@ protected static void ensureCloudCluster() { } } - /** Configures a Jetty node to serve the Admin UI plus the js-client bundle. */ + /** Configures a Jetty node to serve the Admin UI. */ protected static void configureJettyForUi(JettyConfig.Builder jetty) { - jetty - .enableAdminUi(true) - // exact-path mapping takes precedence over the static /libs/* servlet - .withServlet(new ServletHolder(new JsClientServlet()), "/libs/solr/index.js"); + jetty.enableAdminUi(true); } @AfterClass diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html index 46535d6b7427..ca7703b86909 100644 --- a/solr/webapp/web/index.html +++ b/solr/webapp/web/index.html @@ -68,7 +68,6 @@ - diff --git a/solr/webapp/web/js/angular/controllers/collections.js b/solr/webapp/web/js/angular/controllers/collections.js index 830290a18069..906251b43556 100644 --- a/solr/webapp/web/js/angular/controllers/collections.js +++ b/solr/webapp/web/js/angular/controllers/collections.js @@ -16,7 +16,7 @@ */ solrAdminApp.controller('CollectionsController', - function($scope, $routeParams, $location, $timeout, Collections, CollectionsV2, Zookeeper, Constants, ConfigSets){ + function($scope, $routeParams, $location, $timeout, Collections, Zookeeper, Constants, ConfigSets){ $scope.resetMenu("collections", Constants.IS_ROOT_PAGE); $scope.refresh = function() { @@ -215,16 +215,17 @@ solrAdminApp.controller('CollectionsController', alert("No collection selected."); return; } - CollectionsV2.reloadCollection($scope.collection.name, function(error, data,response) { - if (error) { - $scope.reloadFailure = true; - $timeout(function() {$scope.reloadFailure=false}, 1000); - $location.path("/~collections"); - } else { - $scope.reloadSuccess = true; - $timeout(function() {$scope.reloadSuccess=false}, 1000); - } - }); + $scope.reloadSuccess = false; + $scope.reloadFailure = false; + Collections.reload( + {name: $scope.collection.name}, + function() { + $scope.reloadSuccess = true; + }, + function() { + $scope.reloadFailure = true; + } + ); }; $scope.toggleAddReplica = function(shard) { diff --git a/solr/webapp/web/js/angular/controllers/core-overview.js b/solr/webapp/web/js/angular/controllers/core-overview.js index 4c97e6d12b07..75b8fdb06e9b 100644 --- a/solr/webapp/web/js/angular/controllers/core-overview.js +++ b/solr/webapp/web/js/angular/controllers/core-overview.js @@ -56,14 +56,16 @@ function($scope, $rootScope, $routeParams, Luke, CoreInfo, Update, Replication, $scope.refreshPing = function() { Ping.status({core: $routeParams.core}, function(data) { - if (data.error) { + if (data.status == "not_configured") { $scope.healthcheckStatus = false; - if (data.error.code == 503) { - $scope.healthcheckMessage = 'Ping request handler is not configured with a healthcheck file.'; - } + $scope.healthcheckMessage = 'Ping request handler is not configured with a healthcheck file.'; } else { + delete $scope.healthcheckMessage; $scope.healthcheckStatus = data.status == "enabled"; } + }, function(error) { + $scope.healthcheckStatus = false; + $scope.healthcheckMessage = error.data && error.data.error ? error.data.error.msg : 'Unable to read ping status.'; }); }; diff --git a/solr/webapp/web/js/angular/controllers/schema-designer.js b/solr/webapp/web/js/angular/controllers/schema-designer.js index 7ec4282bb053..a2d08aa79d73 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) { @@ -303,7 +305,9 @@ 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; SchemaDesigner.post({path: "prep", configSet: $scope.newSchema, copyFrom: $scope.copyFrom}, null, function (data) { + $scope.preparingSchema = false; $scope.initDesignerSettingsFromResponse(data); }, $scope.errorHandler); }; @@ -451,9 +455,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) { + // Route Luke 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/js/angular/services.js b/solr/webapp/web/js/angular/services.js index 47c5ad2fa6ee..f249c64c69d3 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -44,12 +44,6 @@ solrAdminServices.factory('System', } }); }]) -.factory('CollectionsV2', - function() { - solrApi.ApiClient.instance.basePath = '/api'; - delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; - return new solrApi.CollectionsApi(); - }) .factory('Collections', ['$resource', function($resource) { return $resource('admin/collections', diff --git a/solr/webapp/web/partials/collections.html b/solr/webapp/web/partials/collections.html index a05ebe7198f3..8973d31562c6 100644 --- a/solr/webapp/web/partials/collections.html +++ b/solr/webapp/web/partials/collections.html @@ -128,7 +128,8 @@ + ng-class="{success: reloadSuccess, warn: reloadFailure, disabled:!isPermitted(permissions.COLL_EDIT_PERM)}" + aria-live="polite">{{reloadSuccess ? 'Reloaded' : (reloadFailure ? 'Reload failed' : 'Reload')}}

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 9b5545ddb4aec0ac4f743af976944e660a0019e5 Mon Sep 17 00:00:00 2001 From: Shrey Narayan Date: Thu, 27 Aug 2026 03:11:52 -0700 Subject: [PATCH 3/3] SOLR-18347: Handle every ping status explicitly in Core Overview "enabled" and "disabled" both mean a healthcheckFile is configured, so toggleHealthcheck() works and the widget should show its lit / unlit control. "not_configured" means there is no file at all, and enable/disable answer 503, so it needs the message branch that hides the toggle instead of offering a control that cannot work. Those three cases already behaved correctly. Any other value, though, fell into the else and rendered the unlit "enable ping" control, which is the one outcome an unknown status should not produce. Name the three known states and send everything else to the message branch. Co-Authored-By: Claude Opus 5 --- .../web/js/angular/controllers/core-overview.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/solr/webapp/web/js/angular/controllers/core-overview.js b/solr/webapp/web/js/angular/controllers/core-overview.js index 75b8fdb06e9b..7ea076b432a4 100644 --- a/solr/webapp/web/js/angular/controllers/core-overview.js +++ b/solr/webapp/web/js/angular/controllers/core-overview.js @@ -56,12 +56,19 @@ function($scope, $rootScope, $routeParams, Luke, CoreInfo, Update, Replication, $scope.refreshPing = function() { Ping.status({core: $routeParams.core}, function(data) { - if (data.status == "not_configured") { - $scope.healthcheckStatus = false; - $scope.healthcheckMessage = 'Ping request handler is not configured with a healthcheck file.'; - } else { - delete $scope.healthcheckMessage; + // Three states, and they are not interchangeable. "enabled" and "disabled" both mean a + // healthcheck file is configured, so toggleHealthcheck() works and the widget shows the + // lit / unlit control. "not_configured" means there is no healthcheck file at all, and + // enable/disable would answer 503 - so set a message, which hides the toggle rather than + // offering a control that cannot work. + delete $scope.healthcheckMessage; + if (data.status == "enabled" || data.status == "disabled") { $scope.healthcheckStatus = data.status == "enabled"; + } else { + $scope.healthcheckStatus = false; + $scope.healthcheckMessage = data.status == "not_configured" + ? 'Ping request handler is not configured with a healthcheck file.' + : 'Unexpected ping status: ' + data.status; } }, function(error) { $scope.healthcheckStatus = false;