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 983ccfb9aa63 [SPARK-55771][UI] Modernize progress bars using Bootstrap 
5 Progress component
983ccfb9aa63 is described below

commit 983ccfb9aa632bf8ff6a025a4d80e1941159d1ed
Author: Kent Yao <[email protected]>
AuthorDate: Mon Mar 2 22:51:42 2026 +0800

    [SPARK-55771][UI] Modernize progress bars using Bootstrap 5 Progress 
component
    
    ### What changes were proposed in this pull request?
    
    Replace the custom progress bar implementation with Bootstrap 5's 
`progress-stacked` component for proper accessibility and modern CSS.
    
    **Changes:**
    - **CSS** (`webui.css`): Remove 21 lines of IE9 vendor-prefixed gradients 
(`-moz-`, `-webkit-`, `-o-`, `filter:progid`), legacy `box-shadow` border, and 
`background-repeat`. Replace with clean `background: linear-gradient()` rules 
targeting `.progress-stacked` selector.
    - **Scala** (`UIUtils.scala`): Use BS5 `progress-stacked` wrapper with 
proper ARIA attributes (`role="progressbar"`, `aria-valuenow/min/max`). Replace 
inline styles with BS5 utility classes (`position-absolute`, `d-flex`, 
`align-items-center`, etc.). Extract label text to a `progressLabel` variable 
for clarity.
    - **Tests**: Update `UIUtilsSuite` assertions for new BS5 markup structure. 
Update `UISeleniumSuite` CSS selector from `.progress` to `.progress-stacked`.
    
    **Before → After (HTML output):**
    ```html
    <div class="progress">
      <span style="display:flex;align-items:center;...">3/4</span>
      <div class="progress-bar progress-completed" style="width:75%"></div>
      <div class="progress-bar progress-started" style="width:25%"></div>
    </div>
    
    <div class="progress-stacked" title="3/4 (2 running)">
      <span class="position-absolute w-100 h-100 d-flex align-items-center 
justify-content-center">3/4</span>
      <div class="progress" role="progressbar" aria-label="Completed" 
aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="width:75%">
        <div class="progress-bar progress-completed"></div>
      </div>
      <div class="progress" role="progressbar" aria-label="Running" 
aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="width:25%">
        <div class="progress-bar progress-started"></div>
      </div>
    </div>
    ```
    
    ### Why are the changes needed?
    
    1. **Accessibility**: Progress bars lacked ARIA attributes, making them 
invisible to screen readers
    2. **Dead code**: IE9 vendor prefixes (`-moz-`, `-webkit-`, `-o-`, 
`filter:progid`) are no longer needed
    3. **BS5 alignment**: Use the standard BS5 Progress component API instead 
of custom markup
    4. **Maintainability**: Inline styles replaced with BS5 utility classes
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes — progress bars now have proper ARIA attributes for accessibility. 
Visual appearance is unchanged.
    
    ### How was this patch tested?
    
    - `UIUtilsSuite` — progress bar markup assertions updated and passing
    - `UISeleniumSuite` — CSS selector updated for new structure
    - Scalastyle checks passing
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Yes, GitHub Copilot CLI was used.
    
    Closes #54564 from yaooqinn/SPARK-55771.
    
    Authored-by: Kent Yao <[email protected]>
    Signed-off-by: Kent Yao <[email protected]>
---
 .../resources/org/apache/spark/ui/static/webui.css | 25 +++++-----------------
 .../main/scala/org/apache/spark/ui/UIUtils.scala   | 24 +++++++++++++--------
 .../org/apache/spark/ui/UISeleniumSuite.scala      |  2 +-
 .../scala/org/apache/spark/ui/UIUtilsSuite.scala   | 11 ++++++++--
 4 files changed, 30 insertions(+), 32 deletions(-)

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 3f7ed3be2a51..fd00c2fd2920 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
@@ -108,34 +108,19 @@ table.sortable td {
   max-width: 600px;
 }
 
-.progress {
+.progress-stacked {
   font-size: 1rem;
   height: 1.42rem;
   margin-bottom: 0px;
   position: relative;
-  box-shadow: inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);
 }
 
-.progress .progress-bar.progress-started {
-  background-color: #A0DFFF;
-  background-image: -moz-linear-gradient(top, #A4EDFF, #94DDFF);
-  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#A4EDFF), 
to(#94DDFF));
-  background-image: -webkit-linear-gradient(top, #A4EDFF, #94DDFF);
-  background-image: -o-linear-gradient(top, #A4EDFF, #94DDFF);
-  background-image: linear-gradient(to bottom, #A4EDFF, #94DDFF);
-  background-repeat: repeat-x;
-  filter: 
progid:dximagetransform.microsoft.gradient(startColorstr='#FFA4EDFF', 
endColorstr='#FF94DDFF', GradientType=0);
+.progress-stacked .progress-bar.progress-started {
+  background: linear-gradient(to bottom, #A4EDFF, #94DDFF);
 }
 
-.progress .progress-bar.progress-completed {
-  background-color: #3EC0FF;
-  background-image: -moz-linear-gradient(top, #44CBFF, #34B0EE);
-  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#44CBFF), 
to(#34B0EE));
-  background-image: -webkit-linear-gradient(top, #44CBFF, #34B0EE);
-  background-image: -o-linear-gradient(top, #44CBFF, #34B0EE);
-  background-image: linear-gradient(to bottom, #64CBFF, #54B0EE);
-  background-repeat: repeat-x;
-  filter: 
progid:dximagetransform.microsoft.gradient(startColorstr='#FF44CBFF', 
endColorstr='#FF34B0EE', GradientType=0);
+.progress-stacked .progress-bar.progress-completed {
+  background: linear-gradient(to bottom, #64CBFF, #54B0EE);
 }
 
 a.kill-link {
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 4cb206d80755..74cd4791399e 100644
--- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
+++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
@@ -472,17 +472,23 @@ private[spark] object UIUtils extends Logging {
       if (skipped > 0) s" ($skipped skipped)" else ""
     } + killTaskReasonText
 
+    val progressLabel = s"$completed/$total" +
+      (if (failed == 0 && skipped == 0 && started > 0) s" ($started running)" 
else "") +
+      (if (failed > 0) s" ($failed failed)" else "") +
+      (if (skipped > 0) s" ($skipped skipped)" else "") +
+      killTaskReasonText
+
     // scalastyle:off line.size.limit
-    <div class="progress">
-      <span style="display: flex; align-items: center; justify-content: 
center; position:absolute; width:100%; height:100%; text-align:center;" 
title={progressTitle}>
-        { s"$completed/$total" +
-            (if (failed == 0 && skipped == 0 && started > 0) s" ($started 
running)" else "") +
-            (if (failed > 0) s" ($failed failed)" else "") +
-            (if (skipped > 0) s" ($skipped skipped)" else "") +
-            killTaskReasonText }
+    <div class="progress-stacked" title={progressTitle}>
+      <span class="position-absolute w-100 h-100 d-flex align-items-center 
justify-content-center">
+        {progressLabel}
       </span>
-      <div class="progress-bar progress-completed" style={completeWidth}></div>
-      <div class="progress-bar progress-started" style={startWidth}></div>
+      <div class="progress" role="progressbar" aria-label="Completed" 
aria-valuenow={ratio.toInt.toString} aria-valuemin="0" aria-valuemax="100" 
style={completeWidth}>
+        <div class="progress-bar progress-completed"></div>
+      </div>
+      <div class="progress" role="progressbar" aria-label="Running" 
aria-valuenow={startRatio.toInt.toString} aria-valuemin="0" aria-valuemax="100" 
style={startWidth}>
+        <div class="progress-bar progress-started"></div>
+      </div>
     </div>
     // scalastyle:on line.size.limit
   }
diff --git a/core/src/test/scala/org/apache/spark/ui/UISeleniumSuite.scala 
b/core/src/test/scala/org/apache/spark/ui/UISeleniumSuite.scala
index 5d02dd753845..9096076f8212 100644
--- a/core/src/test/scala/org/apache/spark/ui/UISeleniumSuite.scala
+++ b/core/src/test/scala/org/apache/spark/ui/UISeleniumSuite.scala
@@ -362,7 +362,7 @@ class UISeleniumSuite extends SparkFunSuite with WebBrowser 
with Matchers {
       eventually(timeout(5.seconds), interval(50.milliseconds)) {
         goToUi(sc, "/jobs")
         find(cssSelector(".stage-progress-cell")).get.text should be ("2/2 (1 
failed)")
-        find(cssSelector(".progress-cell .progress")).get.text should be ("2/2 
(1 failed)")
+        find(cssSelector(".progress-cell .progress-stacked")).get.text should 
be ("2/2 (1 failed)")
       }
       val jobJson = getJson(sc.ui.get, "jobs")
       (jobJson \\ "numTasks").extract[Int]should be (2)
diff --git a/core/src/test/scala/org/apache/spark/ui/UIUtilsSuite.scala 
b/core/src/test/scala/org/apache/spark/ui/UIUtilsSuite.scala
index c0753a2af8be..ebb22de7c538 100644
--- a/core/src/test/scala/org/apache/spark/ui/UIUtilsSuite.scala
+++ b/core/src/test/scala/org/apache/spark/ui/UIUtilsSuite.scala
@@ -126,10 +126,17 @@ class UIUtilsSuite extends SparkFunSuite {
 
   test("SPARK-11906: Progress bar should not overflow because of speculative 
tasks") {
     val generated = makeProgressBar(2, 3, 0, 0, Map.empty, 
4).head.child.filter(_.label == "div")
+    // BS5 progress-stacked: each segment is a .progress wrapper with a 
.progress-bar
+    // scalastyle:off line.size.limit
     val expected = Seq(
-      <div class="progress-bar progress-completed" style="width: 75.0%"></div>,
-      <div class="progress-bar progress-started" style="width: 25.0%"></div>
+      <div class="progress" role="progressbar" aria-label="Completed" 
aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="width: 75.0%">
+        <div class="progress-bar progress-completed"></div>
+      </div>,
+      <div class="progress" role="progressbar" aria-label="Running" 
aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="width: 25.0%">
+        <div class="progress-bar progress-started"></div>
+      </div>
     )
+    // scalastyle:on line.size.limit
     assert(generated.sameElements(expected),
       s"\nRunning progress bar should round 
down\n\nExpected:\n$expected\nGenerated:\n$generated")
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to