From 88864c061594f3a627886bd12c29416d95a0a5f1 Mon Sep 17 00:00:00 2001 From: Kousuke Saruta Date: Tue, 24 Mar 2020 13:00:46 -0500 Subject: [PATCH] [SPARK-31161][WEBUI] Refactor the on-click timeline action in streagming-page.js ### What changes were proposed in this pull request? Refactor `streaming-page.js` by making on-click timeline action customizable. ### Why are the changes needed? In the current implementation, `streaming-page.js` is used from Streaming page and Structured Streaming page but the implementation of the on-click timeline action is strongly dependent on Streamng page. Structured Streaming page doesn't define the on-click action for now but it's better to remove the dependncy for the future. Originally, I make this change to fix `SPARK-31128` but #27883 resolved it. So, now this is just for refactoring. ### Does this PR introduce any user-facing change? No. ### How was this patch tested? Manual tests with following code and confirmed there are no regression and no error in the debug console in Firefox. For Structured Streaming: ``` spark.readStream.format("socket").options(Map("host"->"localhost", "port"->"8765")).load.writeStream.format("console").start ``` And then, visited Structured Streaming page and there were no error in the debug console when I clicked a point in the timeline. For Spark Streaming: ``` import org.apache.spark.streaming._ val ssc = new StreamingContext(sc, Seconds(1)) ssc.socketTextStream("localhost", 8765) dstream.foreachRDD(rdd => rdd.foreach(println)) ssc.start ``` And then, visited Streaming page and confirmed scrolling down and hilighting work well and there were no error in the debug console when I clicked a point in the timeline. Closes #27921 from sarutak/followup-SPARK-29543-fix-oncick. Authored-by: Kousuke Saruta Signed-off-by: Sean Owen --- .../apache/spark/ui/static/streaming-page.js | 70 +++++++++++-------- .../spark/streaming/ui/StreamingPage.scala | 9 ++- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/core/src/main/resources/org/apache/spark/ui/static/streaming-page.js b/core/src/main/resources/org/apache/spark/ui/static/streaming-page.js index 6a944afe83..6d4c8d94b4 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/streaming-page.js +++ b/core/src/main/resources/org/apache/spark/ui/static/streaming-page.js @@ -33,6 +33,8 @@ var yValueFormat = d3.format(",.2f"); var unitLabelYOffset = -10; +var onClickTimeline = function() {}; + // Show a tooltip "text" for "node" function showBootstrapTooltip(node, text) { $(node).tooltip({title: text, trigger: "manual", container: "body"}); @@ -44,6 +46,45 @@ function hideBootstrapTooltip(node) { $(node).tooltip("dispose"); } +// Return the function to scroll to the corresponding +// row on clicking a point of batch in the timeline. +function getOnClickTimelineFunction() { + // If the user click one point in the graphs, jump to the batch row and highlight it. And + // recovery the batch row after 3 seconds if necessary. + // We need to remember the last clicked batch so that we can recovery it. + var lastClickedBatch = null; + var lastTimeout = null; + + return function(d) { + var batchSelector = $("#batch-" + d.x); + // If there is a corresponding batch row, scroll down to it and highlight it. + if (batchSelector.length > 0) { + if (lastTimeout != null) { + window.clearTimeout(lastTimeout); + } + if (lastClickedBatch != null) { + clearBatchRow(lastClickedBatch); + lastClickedBatch = null; + } + lastClickedBatch = d.x; + highlightBatchRow(lastClickedBatch); + lastTimeout = window.setTimeout(function () { + lastTimeout = null; + if (lastClickedBatch != null) { + clearBatchRow(lastClickedBatch); + lastClickedBatch = null; + } + }, 3000); // Clean up after 3 seconds + + var topOffset = batchSelector.offset().top - 15; + if (topOffset < 0) { + topOffset = 0; + } + $('html,body').animate({scrollTop: topOffset}, 200); + } + } +} + // Register a timeline graph. All timeline graphs should be register before calling any // "drawTimeline" so that we can determine the max margin left for all timeline graphs. function registerTimeline(minY, maxY) { @@ -189,34 +230,7 @@ function drawTimeline(id, data, minX, maxX, minY, maxY, unitY, batchInterval) { .attr("opacity", function(d) { return isFailedBatch(d.x) ? "1" : "0";}) .attr("r", function(d) { return isFailedBatch(d.x) ? "2" : "3";}); }) - .on("click", function(d) { - var batchSelector = $("#batch-" + d.x); - // If there is a corresponding batch row, scroll down to it and highlight it. - if (batchSelector.length > 0) { - if (lastTimeout != null) { - window.clearTimeout(lastTimeout); - } - if (lastClickedBatch != null) { - clearBatchRow(lastClickedBatch); - lastClickedBatch = null; - } - lastClickedBatch = d.x; - highlightBatchRow(lastClickedBatch); - lastTimeout = window.setTimeout(function () { - lastTimeout = null; - if (lastClickedBatch != null) { - clearBatchRow(lastClickedBatch); - lastClickedBatch = null; - } - }, 3000); // Clean up after 3 seconds - - var topOffset = batchSelector.offset().top - 15; - if (topOffset < 0) { - topOffset = 0; - } - $('html,body').animate({scrollTop: topOffset}, 200); - } - }); + .on("click", onClickTimeline); } /** diff --git a/streaming/src/main/scala/org/apache/spark/streaming/ui/StreamingPage.scala b/streaming/src/main/scala/org/apache/spark/streaming/ui/StreamingPage.scala index 32f295d528..3bdf009dbc 100644 --- a/streaming/src/main/scala/org/apache/spark/streaming/ui/StreamingPage.scala +++ b/streaming/src/main/scala/org/apache/spark/streaming/ui/StreamingPage.scala @@ -80,9 +80,10 @@ private[ui] class StreamingPage(parent: StreamingTab) /** Render the page */ def render(request: HttpServletRequest): Seq[Node] = { val resources = generateLoadResources(request) + val onClickTimelineFunc = generateOnClickTimelineFunction() val basicInfo = generateBasicInfo() val content = resources ++ - basicInfo ++ + onClickTimelineFunc ++ basicInfo ++ listener.synchronized { generateStatTable() ++ generateBatchListTables() @@ -101,6 +102,12 @@ private[ui] class StreamingPage(parent: StreamingTab) // scalastyle:on } + /** Generate html that will set onClickTimeline declared in streaming-page.js */ + private def generateOnClickTimelineFunction(): Seq[Node] = { + val js = "onClickTimeline = getOnClickTimelineFunction();" + + } + /** Generate basic information of the streaming program */ private def generateBasicInfo(): Seq[Node] = { val timeSinceStart = System.currentTimeMillis() - startTime