Skip to content

SOLR-15752: Migrate admin UI to v2 apis - #4659

Open
epugh wants to merge 21 commits into
apache:mainfrom
epugh:migrate_admin_ui_to_v2_apis
Open

SOLR-15752: Migrate admin UI to v2 apis#4659
epugh wants to merge 21 commits into
apache:mainfrom
epugh:migrate_admin_ui_to_v2_apis

Conversation

@epugh

@epugh epugh commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-15752

Description

Start using V2 apis in our solr admin. We need to use them "in anger" if we want to build confidence in them, and to help us move away from v1 equivalents.

Solution

Work with Claude to do the updates to our javascript from v1 to v2. We don't have tests, so this is all manually tested.

We use the real generated solrApi client classes CoresApi, CollectionsApi, AliasesApi, ShardsApi, ReplicasApi, ConfigsetsApi, ClusterApi, SystemApi, LoggingApi, SegmentsApi via the *V2 factories in services.js.

We bumped to using the older style v2 without the strongly typed client for Collections (specically CLUSTERSTATUS), Threads, ParamSet.

Tests

Sigh. No JS tests. And not ready to even begin that!

@epugh

epugh commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Created https://issues.apache.org/jira/browse/SOLR-18318 to track blocker to finishing this.

@epugh

epugh commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Migrated to V2

Controller What moved Notes
cores.js list/add/unload/rename/swap/reload → CoresV2 Full migration
segments.js get → plain $resource on /api/cores/:core/segments No generated client existed; response shape identical to v1
threads.js get → plain $resource on /api/node/threads No generated client (pre-dates OpenAPI v2 framework); same handler as v1 under the hood
index.js system info → SystemV2 Full migration
app.js (MainController) system info fetch in resetMenu(), core-list fetch → SystemV2, CoresV2 Drives nav/sidebar across the whole app
security.js system info → SystemV2 Ported the 401/403 detection logic to the new callback shape
logging.js events, levels → LoggingV2 setLevel stayed on v1 (see below)
collections.js aliases, create/delete alias, create/delete collection, delete shard, create/delete replica, configsets, live-nodes → AliasesV2/ShardsV2/ReplicasV2/ConfigSetsV2/ClusterV2; reloadCollection was already on CollectionsV2 before this work started Collections.status (CLUSTERSTATUS) stayed on v1 (see below)
cloud.js System.get({nodes:...}) (Nodes page) Now dealt with. Full mgiration.

Still on v1 — deliberately, with a documented reason

Where Why
logging.js setLevel v1's nodes=all broadcasts the log-level change to every node in the cluster; the v2 NodeLoggingApis endpoint is single-node only until SOLR-16738 wires up proxying for it
collections.js Collections.status (CLUSTERSTATUS) No v2 endpoint returns the full collections+shards+replicas topology in one call; only per-collection status exists, which would mean N+1 requests. Your call was to leave this on v1
plugins.js Was migrated to MetricsV2, then you asked me to back it out — fully reverted to v1 Metrics.raw
cloud.js (everything else) Metrics.get, Zookeeper.* (tree/simple/clusterState/detail) untouched — not in scope of any request so far

@epugh

epugh commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Merging #4661 unblocks the move to V2 for cloud.js.

@epugh
epugh marked this pull request as ready for review July 29, 2026 21:52
@epugh
epugh requested review from gerlowskija and janhoy and removed request for janhoy July 29, 2026 21:53
epugh and others added 2 commits July 29, 2026 18:54
The V2 client callback runs outside Angular's digest cycle, so setting
$scope.reloadSuccess/reloadFailure directly (with only the delayed
reset wrapped in $timeout) never triggered a re-render -- the ng-class
success/warn flash on the Reload button silently never appeared.
Wrap the whole callback body in $timeout instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same digest-cycle issue as cores.js's reloadCore: deleteShard,
deleteReplica, addReplica, and reloadCollection set their
success/failure/deleted flags outside $timeout, so the ng-class
indicators never rendered. Wrap each callback body in $timeout.

Also found and fixed a separate bug in reloadCollection while
verifying the above: CollectionsApi.reloadCollection requires
(collectionName, opts, callback), but the call only passed
(collectionName, callback) -- the callback function was landing in
the opts slot, so the generated client's `if (callback)` guard
silently no-op'd and neither branch ever ran. Confirmed via browser
testing that reload requests fired but had zero UI effect until this
was fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@epugh
epugh requested a review from janhoy July 30, 2026 13:31
@epugh epugh added this to the 10.x milestone Jul 30, 2026
@janhoy
janhoy requested a review from Copilot July 31, 2026 10:15

@janhoy janhoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive work - for a UI that is going away :)

I spot checked some files, and it is exactly what I expected. However, don't have time to go in detail on each conversion or to spin it up for a thorough test in the UI.

So I take the easy way out and ask our friend Copilot to do a thorough code review, perhaps they find some nasty bug or typo for us...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the Solr Admin UI’s AngularJS data-access layer from v1 admin endpoints toward v2 /api endpoints, primarily by introducing v2 OpenAPI client-backed factories and updating controllers to use them.

Changes:

  • Added v2 (*V2) service factories that wrap generated solrApi clients and updated multiple controllers to call v2 APIs.
  • Kept a small set of v1 $resource services where v2 equivalents aren’t available/complete yet (e.g., cluster status, threads, paramsets routing nuances, logging setLevel broadcast).
  • Updated ParamSet consumers to pass an explicit indexType (cores vs collections) required by the v2 config/params routing.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
solr/webapp/web/js/angular/services.js Introduces multiple *V2 factories and adjusts remaining v1 $resource services for endpoints that still require them.
solr/webapp/web/js/angular/controllers/segments.js Switches segments page to use SegmentsV2 client calls.
solr/webapp/web/js/angular/controllers/security.js Switches system info loading to SystemV2.getNodeSystemInfo.
solr/webapp/web/js/angular/controllers/query.js Adds indexType when calling ParamSet APIs.
solr/webapp/web/js/angular/controllers/paramsets.js Adds indexType routing support and basic client-side validation before submit.
solr/webapp/web/js/angular/controllers/logging.js Migrates log events/levels listing to LoggingV2 while keeping v1 for broadcast setLevel.
solr/webapp/web/js/angular/controllers/index.js Switches system info dashboard to SystemV2.getNodeSystemInfo.
solr/webapp/web/js/angular/controllers/cores.js Migrates core admin operations to CoresV2 client calls.
solr/webapp/web/js/angular/controllers/collections.js Migrates aliases/shards/replicas/configsets/node listing to v2 clients, retaining cluster status via $resource.
solr/webapp/web/js/angular/controllers/cloud.js Switches node system-info loading to SystemV2.getNodeSystemInfo for cloud nodes view.
solr/webapp/web/js/angular/app.js Updates main navigation/menu refresh logic to use v2 clients for cores/collections/aliases/system.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +49 to +50
segment.deletedDocSize = Math.floor((segment.delCount / segment.size) * segment.totalSize);
if (segment.delDocSize <= 0.001) delete segment.deletedDocSize;
Comment on lines +356 to +363
SystemV2.getNodeSystemInfo({"nodes": liveNodesToShow.join(',')}, function (error, data, response) {
if (error) {
console.error('Failed to fetch node system info:', error);
return;
}
$timeout(function() {
var systemResponse = response.body;
for (var node in systemResponse) {
Comment on lines 68 to 72
if (!$scope.newCore.name) {
$scope.addMessage = "Please provide a core name";
} else if (false) { //@todo detect whether core exists
$scope.AddMessage = "A core with that name already exists";
} else {
@epugh

epugh commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Learning more about our OpenAPI JS client.. it uses "superagent" for http, and that doesn't wire into AngularJS existing $http object.. So we lost error handling. However, claude and I have a fix that restores:

image

@epugh

epugh commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Okay, had claude via playwright give this a good testing, and I did a bunch of manual testing. I think it's ready for final review and merging in a few days...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (12)

solr/webapp/web/js/angular/controllers/cores.js:129

  • On v2 API errors, this handler sets a local message but skips ApiErrorHandler, so 401/403 won’t trigger the expected login redirect/authz banner. Call ApiErrorHandler.handle(response) when error is present.
              if (error) {
                $scope.renameMessage = "Error renaming core: " + error;
                return;
              }

solr/webapp/web/js/angular/controllers/cores.js:34

  • This loop uses an implicit global variable (firstCore). Declare it with var to avoid leaking globals and accidental cross-scope reuse.
              for (firstCore in cores) break;

solr/webapp/web/js/angular/app.js:576

  • This loop uses an implicit global variable (key). Declare it with var to avoid leaking globals and accidental cross-scope reuse.
                  for (key in cdata.collections) {

solr/webapp/web/js/angular/controllers/cores.js:159

  • On v2 API errors, this handler sets a local message but skips ApiErrorHandler, so 401/403 won’t trigger the expected login redirect/authz banner. Call ApiErrorHandler.handle(response) when error is present.
              if (error) {
                $scope.swapMessage = "Error swapping cores: " + error;
                return;
              }

solr/webapp/web/js/angular/controllers/cores.js:89

  • On v2 API errors, this handler sets a local message but skips ApiErrorHandler, so 401/403 won’t trigger the expected login redirect/authz banner. Call ApiErrorHandler.handle(response) when error is present.

This issue also appears in the following locations of the same file:

  • line 126
  • line 156
              if (error) {
                $scope.addMessage = "Error creating core: " + error;
                return;
              }

solr/webapp/web/js/angular/controllers/cores.js:179

  • Other CoresV2 methods are called with (core, opts, callback). reloadCore is missing the options argument, so the callback may be treated as opts and never run. Pass an explicit empty options object.
        CoresV2.reloadCore($scope.selectedCore,
          function(error, data, response) {

solr/webapp/web/js/angular/app.js:545

  • $scope.isSecurityEnabled is derived from $scope.authenticationPlugin, but that variable is never set in this controller; this will keep isSecurityEnabled false even when security is enabled. Derive it from the SystemV2 response instead.
        $scope.isSecurityEnabled = $scope.authenticationPlugin != null;

solr/webapp/web/js/angular/app.js:525

  • This loop uses an implicit global variable (key). Declare it with var to avoid leaking globals and accidental cross-scope reuse.

This issue also appears on line 576 of the same file.

        for (key in data.status) {

solr/webapp/web/js/angular/controllers/cores.js:71

  • This sets $scope.AddMessage, but the template shows addMessage; the user won’t see this validation error. Use a consistent casing.
          $scope.AddMessage = "A core with that name already exists";

solr/webapp/web/js/angular/controllers/cores.js:28

  • This loop uses an implicit global variable (_obj). Declare it with var to avoid leaking globals and accidental cross-scope reuse.

This issue also appears on line 34 of the same file.

            for (_obj in cores) coreCount++;

solr/webapp/web/js/angular/controllers/logging.js:135

  • This loop uses an implicit global variable (level). Declare it with var to avoid leaking globals and accidental cross-scope reuse.
          for (level in data.levels) {

solr/webapp/web/js/angular/controllers/segments.js:53

  • The check uses segment.delDocSize (undefined) instead of segment.deletedDocSize, so the delete branch never triggers. Also, deleting deletedDocSize before computing aliveDocSize can yield NaN. Use the correct property and compute aliveDocSize before deleting the display-only field.
                  segment.deletedDocSize = Math.floor((segment.delCount / segment.size) * segment.totalSize);
                  if (segment.delDocSize <= 0.001) delete segment.deletedDocSize;

                  segment.aliveDocSize = segment.totalSize - segment.deletedDocSize;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants