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

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


The following commit(s) were added to refs/heads/branch_10_1 by this push:
     new b31e3cc79e5 SOLR-18347: Fix four Admin UI defects found by Selenium 
(#4818)
b31e3cc79e5 is described below

commit b31e3cc79e5bab32d628cb8b75c7859d6053a066
Author: NextBrickINC.com <[email protected]>
AuthorDate: Sun Sep 27 10:28:22 2026 -0700

    SOLR-18347: Fix four Admin UI defects found by Selenium (#4818)
    
    Co-authored-by: rayshrey <[email protected]>
    Co-authored-by: Shrey Narayan <[email protected]>
    Co-authored-by: Claude Opus 5 <[email protected]>
    Co-authored-by: Eric Pugh <[email protected]>
    (cherry picked from commit c526efab696bd097088f277a03b0d69b8c92398f)
---
 .../unreleased/solr-18347-admin-ui-defects.yml      |  9 +++++++++
 dev-docs/admin-ui-tests.md                          | 11 -----------
 .../org/apache/solr/handler/PingRequestHandler.java | 15 +++++++--------
 .../apache/solr/handler/PingRequestHandlerTest.java |  4 ++++
 .../solr/webapp/AdminUiCollectionScreensTest.java   |  4 ++--
 .../solr/webapp/AdminUiCollectionsScreenTest.java   | 10 ++++++++--
 .../solr/webapp/AdminUiCoreAdminStandaloneTest.java |  4 ++--
 .../org/apache/solr/webapp/AdminUiSmokeTest.java    |  4 +---
 .../org/apache/solr/webapp/AdminUiTestBase.java     |  6 ------
 solr/webapp/web/js/angular/app.js                   |  4 +++-
 .../web/js/angular/controllers/collections.js       |  6 +++---
 .../web/js/angular/controllers/core-overview.js     | 21 +++++++++++++++------
 solr/webapp/web/partials/collections.html           |  3 ++-
 13 files changed, 56 insertions(+), 45 deletions(-)

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 00000000000..00801618637
--- /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 four Admin UI defects found by Selenium testing - a null core 
selection in the shared menu, the ping status of a core with no healthcheck 
file, silent failures on collection reload, and an enable-ping control offered 
when it cannot work.
+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 73ad2a9f65b..3a5d070ea5d 100644
--- a/dev-docs/admin-ui-tests.md
+++ b/dev-docs/admin-ui-tests.md
@@ -55,17 +55,6 @@ deliberately does not do.
 - 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 8333ab3c0f5..d26c298fc50 100644
--- a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
@@ -89,8 +89,9 @@ import org.slf4j.LoggerFactory;
  * <ul>
  *   <li>If the health check file exists, the handler will execute the 
delegated query and return
  *       status as described above.
- *   <li>If the health check file does not exist, the handler will return an 
HTTP error even if the
- *       server is working fine and the delegated query would have succeeded
+ *   <li>For a ping request, if a configured health check file does not exist, 
the handler will
+ *       return an HTTP error even if the server is working fine and the 
delegated query would have
+ *       succeeded. The <code>action=status</code> request instead reports the 
file state.
  * </ul>
  *
  * <p>This health check file feature can be used as a way to indicate to some 
Load Balancers that
@@ -104,8 +105,9 @@ import org.slf4j.LoggerFactory;
  *   <li><code>http://.../ping?action=enable</code> - creates the health check 
file if it does not
  *       already exist
  *   <li><code>http://.../ping?action=disable</code> - deletes the health 
check file if it exists
- *   <li><code>http://.../ping?action=status</code> - returns a status code 
indicating if the
- *       healthcheck file exists ("<code>enabled</code>") or not 
("<code>disabled</code>")
+ *   <li><code>http://.../ping?action=status</code> - returns the healthcheck 
state as <code>enabled
+ *       </code> (configured file exists), <code>disabled</code> (configured 
file does not exist),
+ *       or <code>not_configured</code> (no healthcheck file is configured)
  * </ul>
  *
  * @deprecated This handler is deprecated and will be removed in a future 
release. For load balancer
@@ -220,10 +222,7 @@ public class PingRequestHandler extends RequestHandlerBase 
implements SolrCoreAw
         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 d676c007cad..a6a2f972172 100644
--- a/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/PingRequestHandlerTest.java
@@ -86,6 +86,10 @@ public class PingRequestHandlerTest extends SolrTestCaseJ4 {
 
     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/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionScreensTest.java
index f758d46e11b..b4e82fb5457 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 class AdminUiCollectionScreensTest extends 
AdminUiTestBase {
     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 8dad9b0be8a..e8e44ad6ebd 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 class AdminUiCollectionsScreenTest extends 
AdminUiTestBase {
 
   @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 class AdminUiCollectionsScreenTest extends 
AdminUiTestBase {
     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 1707bc9f2a3..fb5d8f05b66 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 class AdminUiCoreAdminStandaloneTest extends 
AdminUiStandaloneTestBase {
     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/AdminUiSmokeTest.java 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSmokeTest.java
index 5dfc5b2a451..a2005916018 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 class AdminUiSmokeTest extends AdminUiTestBase {
             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 a0f6469aa17..77674cadc4d 100644
--- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java
+++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiTestBase.java
@@ -533,12 +533,6 @@ public abstract class AdminUiTestBase extends 
SolrCloudTestCase {
                     Arrays.stream(allowedSubstrings)
                         .noneMatch(allowed -> 
entry.getMessage().contains(allowed)))
             .filter(entry -> !entry.getMessage().contains("favicon.ico"))
-            // 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 1ff037daedb..f61b4e8d51c 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -660,7 +660,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) {
diff --git a/solr/webapp/web/js/angular/controllers/collections.js 
b/solr/webapp/web/js/angular/controllers/collections.js
index d7a14b3dcf3..1c93b088b5e 100644
--- a/solr/webapp/web/js/angular/controllers/collections.js
+++ b/solr/webapp/web/js/angular/controllers/collections.js
@@ -230,15 +230,15 @@ solrAdminApp.controller('CollectionsController',
             alert("No collection selected.");
             return;
         }
+        $scope.reloadSuccess = false;
+        $scope.reloadFailure = false;
         CollectionsV2.reloadCollection($scope.collection.name, {}, 
function(error, data,response) {
            $timeout(function() {
              if (error) {
                  $scope.reloadFailure = true;
-                 $timeout(function() {$scope.reloadFailure=false}, 1000);
-                 $location.path("/~collections");
+                 ApiErrorHandler.handle(response);
              } else {
                  $scope.reloadSuccess = true;
-                 $timeout(function() {$scope.reloadSuccess=false}, 1000);
              }
            });
         });
diff --git a/solr/webapp/web/js/angular/controllers/core-overview.js 
b/solr/webapp/web/js/angular/controllers/core-overview.js
index 4c97e6d12b0..7ea076b432a 100644
--- a/solr/webapp/web/js/angular/controllers/core-overview.js
+++ b/solr/webapp/web/js/angular/controllers/core-overview.js
@@ -56,14 +56,23 @@ function($scope, $rootScope, $routeParams, Luke, CoreInfo, 
Update, Replication,
 
   $scope.refreshPing = function() {
     Ping.status({core: $routeParams.core}, function(data) {
-      if (data.error) {
-        $scope.healthcheckStatus = false;
-        if (data.error.code == 503) {
-          $scope.healthcheckMessage = 'Ping request handler is not configured 
with a healthcheck file.';
-        }
-      } else {
+      // 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;
+      $scope.healthcheckMessage = error.data && error.data.error ? 
error.data.error.msg : 'Unable to read ping status.';
     });
   };
 
diff --git a/solr/webapp/web/partials/collections.html 
b/solr/webapp/web/partials/collections.html
index a05ebe7198f..8973d31562c 100644
--- a/solr/webapp/web/partials/collections.html
+++ b/solr/webapp/web/partials/collections.html
@@ -128,7 +128,8 @@ limitations under the License.
       <button id="delete-alias" 
ng-class="{action,disabled:!isPermitted(permissions.COLL_EDIT_PERM)}" 
ng-disabled="!isPermitted(permissions.COLL_EDIT_PERM)" 
ng-click="toggleDeleteAlias()" ng-show="collection && collection.type === 
'alias'"><span>Delete alias</span></button>
       <button id="reload" ng-click="reloadCollection()" ng-show="collection && 
collection.type === 'collection'"
               ng-disabled="!isPermitted(permissions.COLL_EDIT_PERM)"
-              ng-class="{success: reloadSuccess, warn: reloadFailure, 
disabled:!isPermitted(permissions.COLL_EDIT_PERM)}"><span>Reload</span></button>
+              ng-class="{success: reloadSuccess, warn: reloadFailure, 
disabled:!isPermitted(permissions.COLL_EDIT_PERM)}"
+              aria-live="polite"><span>{{reloadSuccess ? 'Reloaded' : 
(reloadFailure ? 'Reload failed' : 'Reload')}}</span></button>
 
       <div class="action delete" ng-show="showDelete">
 

Reply via email to