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

sarutak 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 1788a3477493 [SPARK-56354][WEBUI] Lazy-load vis-timeline resources 
only on pages that use timeline views
1788a3477493 is described below

commit 1788a34774935549fab0e8c71535077bb4b37ed4
Author: Kousuke Saruta <[email protected]>
AuthorDate: Sat Apr 11 15:21:55 2026 +0900

    [SPARK-56354][WEBUI] Lazy-load vis-timeline resources only on pages that 
use timeline views
    
    ### What changes were proposed in this pull request?
    This PR extracts vis-timeline resources from `commonHeaderNodes()` (loaded 
on every page) into a new `timelineHeaderNodes()` method, following the same 
pattern as `vizHeaderNodes()` and `dataTablesHeaderNodes()`.
    
    Specifically:
    - Moved `vis-timeline-graph2d.min.js` (569KB), 
`vis-timeline-graph2d.min.css`, `timeline-view.css`, and `timeline-view.js` 
from `commonHeaderNodes()` to a new `timelineHeaderNodes()` method
    - Added `useTimeline: Boolean = false` parameter to `headerSparkPage()` and 
`basicSparkPage()`
    - Set `useTimeline = true` on the 4 pages that actually use vis-timeline: 
`AllJobsPage`, `JobPage`, `StagePage`, and `ExecutionPage`
    
    ### Why are the changes needed?
    `vis-timeline-graph2d.min.js` (569KB) and its associated CSS are currently 
loaded on **every** Spark Web UI page, even though they are only used on pages 
that render event timelines (Jobs overview, Job detail, Stage detail, and SQL 
execution detail).
    
    Pages such as Environment, Storage, Executors, SQL listing, and Structured 
Streaming load these resources unnecessarily. This change eliminates ~570KB of 
JavaScript from those pages, reducing page load time and memory usage.
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    Existing tests.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Kiro CLI / Opus 4.6
    
    Closes #55193 from sarutak/viz-timeline-lazy-load.
    
    Authored-by: Kousuke Saruta <[email protected]>
    Signed-off-by: Kousuke Saruta <[email protected]>
---
 .../main/scala/org/apache/spark/ui/UIUtils.scala   | 23 ++++++++++++++--------
 .../org/apache/spark/ui/jobs/AllJobsPage.scala     |  2 +-
 .../scala/org/apache/spark/ui/jobs/JobPage.scala   |  3 ++-
 .../scala/org/apache/spark/ui/jobs/StagePage.scala |  2 +-
 .../spark/sql/execution/ui/ExecutionPage.scala     |  2 +-
 5 files changed, 20 insertions(+), 12 deletions(-)

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 f663b1ea19c0..eedec979f715 100644
--- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
+++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
@@ -218,24 +218,27 @@ private[spark] object UIUtils extends Logging {
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <link rel="stylesheet"
           href={prependBaseUri(request, "/static/bootstrap.min.css")} 
type="text/css"/>
-    <link rel="stylesheet"
-          href={prependBaseUri(request, 
"/static/vis-timeline-graph2d.min.css")} type="text/css"/>
     <link rel="stylesheet" href={prependBaseUri(request, "/static/webui.css")} 
type="text/css"/>
-    <link rel="stylesheet"
-          href={prependBaseUri(request, "/static/timeline-view.css")} 
type="text/css"/>
     <script src={prependBaseUri(request, "/static/sorttable.js")} ></script>
     <script src={prependBaseUri(request, "/static/jquery.min.js")}></script>
-    <script src={prependBaseUri(request, 
"/static/vis-timeline-graph2d.min.js")}></script>
     <script src={prependBaseUri(request, 
"/static/bootstrap.bundle.min.js")}></script>
     <script src={prependBaseUri(request, 
"/static/initialize-tooltips.js")}></script>
     <script src={prependBaseUri(request, "/static/table.js")}></script>
-    <script src={prependBaseUri(request, "/static/timeline-view.js")}></script>
     <script src={prependBaseUri(request, "/static/log-view.js")}></script>
     <script src={prependBaseUri(request, "/static/webui.js")}></script>
     <script src={prependBaseUri(request, "/static/scroll-button.js")} 
type="module"></script>
     <script 
nonce={CspNonce.get}>setUIRoot('{UIUtils.uiRoot(request)}')</script>
   }
 
+  def timelineHeaderNodes(request: HttpServletRequest): Seq[Node] = {
+    <link rel="stylesheet"
+          href={prependBaseUri(request, 
"/static/vis-timeline-graph2d.min.css")} type="text/css"/>
+    <link rel="stylesheet"
+          href={prependBaseUri(request, "/static/timeline-view.css")} 
type="text/css"/>
+    <script src={prependBaseUri(request, 
"/static/vis-timeline-graph2d.min.js")}></script>
+    <script src={prependBaseUri(request, "/static/timeline-view.js")}></script>
+  }
+
   def vizHeaderNodes(request: HttpServletRequest): Seq[Node] = {
     <link rel="stylesheet"
           href={prependBaseUri(request, "/static/spark-dag-viz.css")} 
type="text/css" />
@@ -266,7 +269,8 @@ private[spark] object UIUtils extends Logging {
       activeTab: SparkUITab,
       helpText: Option[String] = None,
       showVisualization: Boolean = false,
-      useDataTables: Boolean = false): Seq[Node] = {
+      useDataTables: Boolean = false,
+      useTimeline: Boolean = false): Seq[Node] = {
 
     val appName = activeTab.appName
     val shortAppName = if (appName.length < 36) appName else appName.take(32) 
+ "..."
@@ -288,6 +292,7 @@ private[spark] object UIUtils extends Logging {
         <script 
nonce={CspNonce.get}>setAppBasePath('{activeTab.basePath}')</script>
         {if (showVisualization) vizHeaderNodes(request) else Seq.empty}
         {if (useDataTables) dataTablesHeaderNodes(request) else Seq.empty}
+        {if (useTimeline) timelineHeaderNodes(request) else Seq.empty}
         <link rel="shortcut icon"
               href={prependBaseUri(request, "/static/spark-logo.svg")}></link>
         <title>{appName} - {title}</title>
@@ -352,7 +357,8 @@ private[spark] object UIUtils extends Logging {
       request: HttpServletRequest,
       content: => Seq[Node],
       title: String,
-      useDataTables: Boolean = false): Seq[Node] = {
+      useDataTables: Boolean = false,
+      useTimeline: Boolean = false): Seq[Node] = {
     <html data-bs-theme="light">
       <head>
         {commonHeaderNodes(request)}
@@ -361,6 +367,7 @@ private[spark] object UIUtils extends Logging {
           "localStorage.getItem('spark-theme')||" +
           
"(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'))")}</script>
         {if (useDataTables) dataTablesHeaderNodes(request) else Seq.empty}
+        {if (useTimeline) timelineHeaderNodes(request) else Seq.empty}
         <link rel="shortcut icon"
               href={prependBaseUri(request, "/static/spark-logo.svg")}></link>
         <title>{title}</title>
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala 
b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
index 6898cbbd8288..75115ed4366e 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
@@ -406,7 +406,7 @@ private[ui] class AllJobsPage(parent: JobsTab, store: 
AppStatusStore) extends We
       " Click on a job to see information about the stages of tasks inside it."
 
     UIUtils.headerSparkPage(request, "Spark Jobs", content, parent,
-      helpText = Some(helpText))
+      helpText = Some(helpText), useTimeline = true)
   }
 
 }
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala 
b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
index 30e0f678de78..a5c8aaab9ec0 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
@@ -530,6 +530,7 @@ private[ui] class JobPage(parent: JobsTab, store: 
AppStatusStore) extends WebUIP
         </div>
     }
     UIUtils.headerSparkPage(
-      request, s"Details for Job $jobId", content, parent, showVisualization = 
true)
+      request, s"Details for Job $jobId", content, parent, showVisualization = 
true,
+      useTimeline = true)
   }
 }
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala 
b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
index 264a57efff0c..391345f74dff 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
@@ -243,7 +243,7 @@ private[ui] class StagePage(parent: StagesTab, store: 
AppStatusStore) extends We
           <script type="module" nonce={CspNonce.get}>{Unparsed(js)}</script>
         </div>
         UIUtils.headerSparkPage(request, stageHeader, content, parent, 
showVisualization = true,
-          useDataTables = true)
+          useDataTables = true, useTimeline = true)
 
   }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/ui/ExecutionPage.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/ui/ExecutionPage.scala
index b9f0b91dfde4..3c8f0c1bec9d 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/ui/ExecutionPage.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/ui/ExecutionPage.scala
@@ -128,7 +128,7 @@ class ExecutionPage(parent: SQLTab) extends 
WebUIPage("execution") with Logging
     }
 
     UIUtils.headerSparkPage(
-      request, s"Details for Query $executionId", content, parent)
+      request, s"Details for Query $executionId", content, parent, useTimeline 
= true)
   }
 
 


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

Reply via email to