[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 <sarutak@oss.nttdata.com> Signed-off-by: Sean Owen <srowen@gmail.com>
This commit is contained in:
parent
f6ff7d0cf8
commit
88864c0615
|
@ -33,6 +33,8 @@ var yValueFormat = d3.format(",.2f");
|
||||||
|
|
||||||
var unitLabelYOffset = -10;
|
var unitLabelYOffset = -10;
|
||||||
|
|
||||||
|
var onClickTimeline = function() {};
|
||||||
|
|
||||||
// Show a tooltip "text" for "node"
|
// Show a tooltip "text" for "node"
|
||||||
function showBootstrapTooltip(node, text) {
|
function showBootstrapTooltip(node, text) {
|
||||||
$(node).tooltip({title: text, trigger: "manual", container: "body"});
|
$(node).tooltip({title: text, trigger: "manual", container: "body"});
|
||||||
|
@ -44,6 +46,45 @@ function hideBootstrapTooltip(node) {
|
||||||
$(node).tooltip("dispose");
|
$(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
|
// 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.
|
// "drawTimeline" so that we can determine the max margin left for all timeline graphs.
|
||||||
function registerTimeline(minY, maxY) {
|
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("opacity", function(d) { return isFailedBatch(d.x) ? "1" : "0";})
|
||||||
.attr("r", function(d) { return isFailedBatch(d.x) ? "2" : "3";});
|
.attr("r", function(d) { return isFailedBatch(d.x) ? "2" : "3";});
|
||||||
})
|
})
|
||||||
.on("click", function(d) {
|
.on("click", onClickTimeline);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -80,9 +80,10 @@ private[ui] class StreamingPage(parent: StreamingTab)
|
||||||
/** Render the page */
|
/** Render the page */
|
||||||
def render(request: HttpServletRequest): Seq[Node] = {
|
def render(request: HttpServletRequest): Seq[Node] = {
|
||||||
val resources = generateLoadResources(request)
|
val resources = generateLoadResources(request)
|
||||||
|
val onClickTimelineFunc = generateOnClickTimelineFunction()
|
||||||
val basicInfo = generateBasicInfo()
|
val basicInfo = generateBasicInfo()
|
||||||
val content = resources ++
|
val content = resources ++
|
||||||
basicInfo ++
|
onClickTimelineFunc ++ basicInfo ++
|
||||||
listener.synchronized {
|
listener.synchronized {
|
||||||
generateStatTable() ++
|
generateStatTable() ++
|
||||||
generateBatchListTables()
|
generateBatchListTables()
|
||||||
|
@ -101,6 +102,12 @@ private[ui] class StreamingPage(parent: StreamingTab)
|
||||||
// scalastyle:on
|
// scalastyle:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generate html that will set onClickTimeline declared in streaming-page.js */
|
||||||
|
private def generateOnClickTimelineFunction(): Seq[Node] = {
|
||||||
|
val js = "onClickTimeline = getOnClickTimelineFunction();"
|
||||||
|
<script>{Unparsed(js)}</script>
|
||||||
|
}
|
||||||
|
|
||||||
/** Generate basic information of the streaming program */
|
/** Generate basic information of the streaming program */
|
||||||
private def generateBasicInfo(): Seq[Node] = {
|
private def generateBasicInfo(): Seq[Node] = {
|
||||||
val timeSinceStart = System.currentTimeMillis() - startTime
|
val timeSinceStart = System.currentTimeMillis() - startTime
|
||||||
|
|
Loading…
Reference in a new issue