This is an automated email from the ASF dual-hosted git repository.
yao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 81f21722875d [SPARK-55985][WEBUI] Remove `jquery.blockUI.min.js`
81f21722875d is described below
commit 81f21722875db1d593578dc5ce6355682d667688
Author: Kousuke Saruta <[email protected]>
AuthorDate: Sat Mar 14 20:52:48 2026 +0800
[SPARK-55985][WEBUI] Remove `jquery.blockUI.min.js`
### What changes were proposed in this pull request?
This PR aims to remove `jquery.blockUI.min.js` and replace the jQuery
BlockUI overlay with a Bootstrap 5 Spinner + CSS overlay.
This change includes:
- **Removed** `jquery.blockUI.min.js` and its entry in `dev/.rat-excludes`
- **Removed** the BlockUI `<script>` tag from `UIUtils.scala`
- **Added** a `<div id="loading-overlay">` with a Bootstrap 5 Spinner to
the `<body>` in `UIUtils.scala` (initially hidden via `d-none`)
- **Updated** `executorspage.js`, `historypage.js`, and `stagepage.js` to
toggle the overlay visibility using
`$("#loading-overlay").removeClass("d-none")` / `.addClass("d-none")` instead
of `$.blockUI()` / `$.unblockUI()`
- **Added** `#loading-overlay` styles to `webui.css` with dark mode support
- **Added** `ui-test/tests/loading-overlay.test.js` with tests for DOM
structure, show/hide behavior, and dark mode CSS
Instead of relying on the jQuery BlockUI plugin to dynamically generate and
remove overlay elements, the new implementation:
1. Embeds a static overlay `<div>` with a Bootstrap 5 Spinner in the page
HTML (hidden by default with `d-none`)
2. Shows/hides the overlay by toggling the `d-none` class via jQuery's
`addClass`/`removeClass`
3. Uses CSS (`position: fixed`, `z-index: 1050`, semi-transparent
background) for positioning and appearance
4. Supports dark mode via `[data-bs-theme="dark"] #loading-overlay` selector
Overlay will be changed like as follows.
* Before (light mode)
<img width="1200" height="863" alt="spark-ui-overlay-light-before"
src="https://github.com/user-attachments/assets/fb7934e4-a2a9-4128-99f4-187484b57459"
/>
* After (light mode)
<img width="1200" height="863" alt="spark-ui-overlay-light"
src="https://github.com/user-attachments/assets/ba1f53e9-9132-4d50-8715-242f2f6d5fd9"
/>
* Before (dark mode)
<img width="1200" height="863" alt="spark-ui-overlay-dark-before"
src="https://github.com/user-attachments/assets/bef5b69a-e015-409a-9257-1bf015ab4a57"
/>
* After (dark mode)
<img width="1200" height="863" alt="spark-ui-overlay-dark"
src="https://github.com/user-attachments/assets/504078ad-3eef-4394-b574-abfd365132be"
/>
### Why are the changes needed?
`jquery.blockUI.min.js` is a legacy jQuery plugin that is no longer
necessary. The same overlay functionality can be achieved with Bootstrap 5
utilities and plain CSS, which are already available in the Spark Web UI.
Removing this dependency reduces the JavaScript footprint and aligns with the
Web UI modernization goals.
### Does this PR introduce _any_ user-facing change?
Yes but it's just a slight change in appearance.
### How was this patch tested?
- New tests: `ui-test/tests/loading-overlay.test.js` (3 tests — DOM
structure, show/hide behavior, dark mode CSS)
- Manual verification with screenshots in both light and dark modes (before
and after)
### Was this patch authored or co-authored using generative AI tooling?
Kiro CLI / Opus 4.6
Closes #54784 from sarutak/remove-block-ui.
Authored-by: Kousuke Saruta <[email protected]>
Signed-off-by: Kent Yao <[email protected]>
---
.../org/apache/spark/ui/static/executorspage.js | 6 +-
.../org/apache/spark/ui/static/historypage.js | 8 ++-
.../apache/spark/ui/static/jquery.blockUI.min.js | 6 --
.../org/apache/spark/ui/static/stagepage.js | 4 +-
.../resources/org/apache/spark/ui/static/webui.css | 9 +++
.../main/scala/org/apache/spark/ui/UIUtils.scala | 21 +++++-
dev/.rat-excludes | 1 -
ui-test/tests/loading-overlay.test.js | 83 ++++++++++++++++++++++
8 files changed, 123 insertions(+), 15 deletions(-)
diff --git
a/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
b/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
index a10d7fe2a703..b1eae834c73a 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
@@ -269,9 +269,11 @@ $.extend($.fn.dataTableExt.oSort, {
}
});
-$(document).ajaxStop($.unblockUI);
+$(document).ajaxStop(function () {
+ $("#loading-overlay").addClass("d-none");
+});
$(document).ajaxStart(function () {
- $.blockUI({message: '<h3>Loading Executors Page...</h3>'});
+ $("#loading-overlay").removeClass("d-none");
});
function logsExist(execs) {
diff --git a/core/src/main/resources/org/apache/spark/ui/static/historypage.js
b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
index f5dd5a0ab763..764b3e97b1e6 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/historypage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
@@ -109,9 +109,11 @@ jQuery.extend( jQuery.fn.dataTableExt.ofnSearch, {
}
});
-$(document).ajaxStop($.unblockUI);
-$(document).ajaxStart(function(){
- $.blockUI({ message: '<h3>Loading history summary...</h3>'});
+$(document).ajaxStop(function () {
+ $("#loading-overlay").addClass("d-none");
+});
+$(document).ajaxStart(function () {
+ $("#loading-overlay").removeClass("d-none");
});
$(document).ready(function() {
diff --git
a/core/src/main/resources/org/apache/spark/ui/static/jquery.blockUI.min.js
b/core/src/main/resources/org/apache/spark/ui/static/jquery.blockUI.min.js
deleted file mode 100644
index 6f5256064bd2..000000000000
--- a/core/src/main/resources/org/apache/spark/ui/static/jquery.blockUI.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
-* jQuery BlockUI; v20141123
-* http://jquery.malsup.com/block/
-* Copyright (c) 2014 M. Alsup; Dual licensed: MIT/GPL
-*/
-(function(){"use strict";function e(e){function o(o,i){var
s,h,k=o==window,v=i&&void 0!==i.message?i.message:void
0;if(i=e.extend({},e.blockUI.defaults,i||{}),!i.ignoreIfBlocked||!e(o).data("blockUI.isBlocked")){if(i.overlayCSS=e.extend({},e.blockUI.defaults.overlayCSS,i.overlayCSS||{}),s=e.extend({},e.blockUI.defaults.css,i.css||{}),i.onOverlayClick&&(i.overlayCSS.cursor="pointer"),h=e.extend({},e.blockUI.defaults.themedCSS,i.themedCSS||{}),v=void
0===v?i.message:v,k&&b&&t(window,{fadeO [...]
\ No newline at end of file
diff --git a/core/src/main/resources/org/apache/spark/ui/static/stagepage.js
b/core/src/main/resources/org/apache/spark/ui/static/stagepage.js
index dcba4f544b50..f41a5d49f477 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/stagepage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/stagepage.js
@@ -42,14 +42,14 @@ function setTaskThreadDumpEnabled(enabled){
$(document).ajaxStop(function () {
if (shouldBlockUI) {
- $.unblockUI();
+ $("#loading-overlay").addClass("d-none");
shouldBlockUI = false;
}
});
$(document).ajaxStart(function () {
if (shouldBlockUI) {
- $.blockUI({message: '<h3>Loading Stage Page...</h3>'});
+ $("#loading-overlay").removeClass("d-none");
}
});
diff --git a/core/src/main/resources/org/apache/spark/ui/static/webui.css
b/core/src/main/resources/org/apache/spark/ui/static/webui.css
index 69ce54532274..c407c8cb22bd 100755
--- a/core/src/main/resources/org/apache/spark/ui/static/webui.css
+++ b/core/src/main/resources/org/apache/spark/ui/static/webui.css
@@ -514,3 +514,12 @@ a.downloadbutton {
.tab-content {
margin-top: 10px;
}
+
+#loading-overlay {
+ z-index: 1050;
+ background-color: rgba(255, 255, 255, 0.75);
+}
+
+[data-bs-theme="dark"] #loading-overlay {
+ background-color: rgba(0, 0, 0, 0.75);
+}
diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
index 51b96dd27497..7434cf131497 100644
--- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
+++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
@@ -256,7 +256,6 @@ private[spark] object UIUtils extends Logging {
href={prependBaseUri(request, "/static/webui-dataTables.css")}
type="text/css"/>
<script src={prependBaseUri(request,
"/static/jquery.dataTables.min.js")}></script>
<script src={prependBaseUri(request,
"/static/jquery.cookies.2.2.0.min.js")}></script>
- <script src={prependBaseUri(request,
"/static/jquery.blockUI.min.js")}></script>
<script src={prependBaseUri(request,
"/static/dataTables.bootstrap5.min.js")}></script>
}
@@ -295,6 +294,16 @@ private[spark] object UIUtils extends Logging {
<title>{appName} - {title}</title>
</head>
<body class="d-flex flex-column min-vh-100">
+ <div id="loading-overlay"
+ class={"position-fixed top-0 start-0 w-100 h-100" +
+ " d-flex justify-content-center align-items-center d-none"}>
+ <div class="text-center">
+ <div class="spinner-border text-primary" role="status">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ <h3 class="mt-2">Loading...</h3>
+ </div>
+ </div>
<nav class="navbar navbar-expand-md navbar-light bg-light mb-4">
<div class="navbar-header">
<div class="navbar-brand">
@@ -358,6 +367,16 @@ private[spark] object UIUtils extends Logging {
<title>{title}</title>
</head>
<body class="d-flex flex-column min-vh-100">
+ <div id="loading-overlay"
+ class={"position-fixed top-0 start-0 w-100 h-100" +
+ " d-flex justify-content-center align-items-center d-none"}>
+ <div class="text-center">
+ <div class="spinner-border text-primary" role="status">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ <h3 class="mt-2">Loading...</h3>
+ </div>
+ </div>
<div class="container-fluid flex-fill">
<div class="row">
<div class="col-12">
diff --git a/dev/.rat-excludes b/dev/.rat-excludes
index 224e09003752..390d27b2ecba 100644
--- a/dev/.rat-excludes
+++ b/dev/.rat-excludes
@@ -35,7 +35,6 @@ vis-timeline-graph2d.min.css
dataTables.bootstrap5.min.css
dataTables.bootstrap5.min.js
dataTables.rowsGroup.js
-jquery.blockUI.min.js
jquery.cookies.2.2.0.min.js
jquery.dataTables.min.css
jquery.dataTables.min.js
diff --git a/ui-test/tests/loading-overlay.test.js
b/ui-test/tests/loading-overlay.test.js
new file mode 100644
index 000000000000..9d4e0bf24a7f
--- /dev/null
+++ b/ui-test/tests/loading-overlay.test.js
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { readFileSync } from 'fs';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const staticDir = join(__dirname,
'../../core/src/main/resources/org/apache/spark/ui/static');
+
+function readStatic(filename) {
+ return readFileSync(join(staticDir, filename), 'utf-8');
+}
+
+describe("loading overlay", () => {
+ test("overlay has expected DOM structure", () => {
+ document.body.innerHTML = `
+ <div id="loading-overlay" class="position-fixed top-0 start-0 w-100
h-100 d-flex justify-content-center align-items-center d-none">
+ <div class="text-center">
+ <div class="spinner-border text-primary" role="status">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ <h3 class="mt-2">Loading...</h3>
+ </div>
+ </div>`;
+
+ const overlay = document.getElementById("loading-overlay");
+ expect(overlay).not.toBeNull();
+ expect(overlay.classList.contains("d-none")).toBe(true);
+ expect(overlay.classList.contains("position-fixed")).toBe(true);
+
+ const spinner = overlay.querySelector(".spinner-border");
+ expect(spinner).not.toBeNull();
+ expect(spinner.getAttribute("role")).toBe("status");
+
expect(spinner.querySelector(".visually-hidden").textContent).toBe("Loading...");
+ });
+
+ test("overlay can be shown and hidden via d-none class", () => {
+ document.body.innerHTML = `
+ <div id="loading-overlay" class="position-fixed top-0 start-0 w-100
h-100 d-flex justify-content-center align-items-center d-none">
+ <div class="text-center">
+ <div class="spinner-border text-primary" role="status">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ <h3 class="mt-2">Loading...</h3>
+ </div>
+ </div>`;
+
+ const overlay = document.getElementById("loading-overlay");
+
+ // Initially hidden
+ expect(overlay.classList.contains("d-none")).toBe(true);
+
+ // Show overlay
+ overlay.classList.remove("d-none");
+ expect(overlay.classList.contains("d-none")).toBe(false);
+
+ // Hide overlay
+ overlay.classList.add("d-none");
+ expect(overlay.classList.contains("d-none")).toBe(true);
+ });
+
+ test("CSS has dark mode support for overlay", () => {
+ const css = readStatic("webui.css");
+ expect(css).toContain("#loading-overlay");
+ expect(css).toContain('[data-bs-theme="dark"] #loading-overlay');
+ });
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]