Skip to content

Commit

Permalink
Implement tooltip using bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
sarutak committed Apr 24, 2015
1 parent b09d0c5 commit ef34a5b
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,15 @@ tr.corresponding-item-hover>td, tr.corresponding-item-hover>th {
span.expand-application-timeline, span.expand-job-timeline {
cursor: pointer;
}

.control-panel input+span {
cursor: pointer;
}

.vis.timeline .item.range .content {
position: unset;
}

.vis.timeline .item .tooltip-inner {
max-width: unset !important;
}
115 changes: 71 additions & 44 deletions core/src/main/resources/org/apache/spark/ui/static/timeline-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,34 @@ function drawApplicationTimeline(groupArray, eventObjArray, startTime) {
applicationTimeline.setItems(items);

setupZoomable("#application-timeline-zoom-lock", applicationTimeline);

$(".item.range.job.application-timeline-object").each(function() {
var getJobId = function(baseElem) {
var jobIdText = $($(baseElem).find(".application-timeline-content")[0]).text();
var jobId = jobIdText.match("\\(Job (\\d+)\\)")[1];
return jobId;
};

$(this).click(function() {
window.location.href = "job/?id=" + getJobId(this);
setupExecutorEventAction();

function setupJobEventAction() {
$(".item.range.job.application-timeline-object").each(function() {
var getJobId = function(baseElem) {
var jobIdText = $($(baseElem).find(".application-timeline-content")[0]).text();
var jobId = jobIdText.match("\\(Job (\\d+)\\)")[1];
return jobId;
};

$(this).click(function() {
window.location.href = "job/?id=" + getJobId(this);
});

$(this).hover(
function() {
$("#job-" + getJobId(this)).addClass("corresponding-item-hover");
$($(this).find("div.application-timeline-content")[0]).tooltip("show");
},
function() {
$("#job-" + getJobId(this)).removeClass("corresponding-item-hover");
$($(this).find("div.application-timeline-content")[0]).tooltip("hide");
}
);
});
}

$(this).hover(
function() {
$("#job-" + getJobId(this)).addClass("corresponding-item-hover");
},
function() {
$("#job-" + getJobId(this)).removeClass("corresponding-item-hover");
}
);
});
setupJobEventAction();

$("span.expand-application-timeline").click(function() {
$("#application-timeline").toggleClass('collapsed');
Expand Down Expand Up @@ -86,36 +93,43 @@ function drawJobTimeline(groupArray, eventObjArray, startTime) {
jobTimeline.setItems(items);

setupZoomable("#job-timeline-zoom-lock", jobTimeline);
setupExecutorEventAction();

$(".item.range.stage.job-timeline-object").each(function() {
var getStageIdAndAttempt = function(baseElem) {
var stageIdText = $($(baseElem).find(".job-timeline-content")[0]).text();
var stageIdAndAttempt = stageIdText.match("\\(Stage (\\d+\\.\\d+)\\)")[1].split(".");
return stageIdAndAttempt;
};

$(this).click(function() {
var idAndAttempt = getStageIdAndAttempt(this);
var id = idAndAttempt[0];
var attempt = idAndAttempt[1];
window.location.href = "../../stages/stage/?id=" + id + "&attempt=" + attempt;
});
function setupStageEventAction() {
$(".item.range.stage.job-timeline-object").each(function() {
var getStageIdAndAttempt = function(baseElem) {
var stageIdText = $($(baseElem).find(".job-timeline-content")[0]).text();
var stageIdAndAttempt = stageIdText.match("\\(Stage (\\d+\\.\\d+)\\)")[1].split(".");
return stageIdAndAttempt;
};

$(this).hover(
function() {
$(this).click(function() {
var idAndAttempt = getStageIdAndAttempt(this);
var id = idAndAttempt[0];
var attempt = idAndAttempt[1];
$("#stage-" + id + "-" + attempt).addClass("corresponding-item-hover");
},
function() {
var idAndAttempt = getStageIdAndAttempt(this);
var id = idAndAttempt[0];
var attempt = idAndAttempt[1];
$("#stage-" + id + "-" + attempt).removeClass("corresponding-item-hover");
}
);
});
window.location.href = "../../stages/stage/?id=" + id + "&attempt=" + attempt;
});

$(this).hover(
function() {
var idAndAttempt = getStageIdAndAttempt(this);
var id = idAndAttempt[0];
var attempt = idAndAttempt[1];
$("#stage-" + id + "-" + attempt).addClass("corresponding-item-hover");
$($(this).find("div.job-timeline-content")[0]).tooltip("show");
},
function() {
var idAndAttempt = getStageIdAndAttempt(this);
var id = idAndAttempt[0];
var attempt = idAndAttempt[1];
$("#stage-" + id + "-" + attempt).removeClass("corresponding-item-hover");
$($(this).find("div.job-timeline-content")[0]).tooltip("hide");
}
);
});
}

setupStageEventAction();

$("span.expand-job-timeline").click(function() {
$("#job-timeline").toggleClass('collapsed');
Expand All @@ -126,6 +140,19 @@ function drawJobTimeline(groupArray, eventObjArray, startTime) {
});
}

function setupExecutorEventAction() {
$(".item.box.executor").each(function () {
$(this).hover(
function() {
$($(this).find(".executor-event-content")[0]).tooltip("show");
},
function() {
$($(this).find(".executor-event-content")[0]).tooltip("hide");
}
);
});
}

function setupZoomable(id, timeline) {
$(id + '>input[type="checkbox"]').click(function() {
if (this.checked) {
Expand Down
69 changes: 38 additions & 31 deletions core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,18 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
| 'group': 'jobs',
| 'start': new Date(${submissionTime}),
| 'end': new Date(${completionTime}),
| 'content': '<div class="application-timeline-content">' +
| '${displayJobDescription} (Job ${jobId})</div>',
| 'title': '${displayJobDescription} (Job ${jobId})\\nStatus: ${status}\\n' +
| 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != JobExecutionStatus.RUNNING) {
s"""\\nCompletion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}'
| 'content': '<div class="application-timeline-content"' +
| 'data-html="true" data-placement="top" data-toggle="tooltip"' +
| 'data-title="${displayJobDescription} (Job ${jobId})<br>Status: ${status}<br>' +
| 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != JobExecutionStatus.RUNNING) {
s"""<br>Completion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}">' +
| '${displayJobDescription} (Job ${jobId})</div>'
|}
""".stripMargin
jobEventJsonAsStr
Expand All @@ -109,38 +110,44 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
val events = ListBuffer[String]()
executorUIDatas.foreach {
case (executorId, event) =>
val addedEvent =
s"""
|{
| 'className': 'executor added',
| 'group': 'executors',
| 'start': new Date(${event.startTime}),
| 'content': '<div>Executor ${executorId} added</div>',
| 'title': 'Added at ${UIUtils.formatDate(new Date(event.startTime))}'
|}
""".stripMargin
events += addedEvent

if (event.finishTime.isDefined) {
val removedEvent =
val addedEvent =
s"""
|{
| 'className': 'executor added',
| 'group': 'executors',
| 'start': new Date(${event.startTime}),
| 'content': '<div class="executor-event-content"' +
| 'data-toggle="tooltip" data-placement="bottom"' +
| 'data-title="Executor ${executorId}<br>' +
| 'Added at ${UIUtils.formatDate(new Date(event.startTime))}"' +
| 'data-html="true">Executor ${executorId} added</div>'
|}
""".stripMargin
events += addedEvent

if (event.finishTime.isDefined) {
val removedEvent =
s"""
|{
| 'className': 'executor removed',
| 'group': 'executors',
| 'start': new Date(${event.finishTime.get}),
| 'content': '<div>Executor ${executorId} removed</div>',
| 'title': 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' +
| 'content': '<div class="executor-event-content"' +
| 'data-toggle="tooltip" data-placement="bottom"' +
| 'data-title="Executor ${executorId}<br>' +
| 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' +
| '${
if (event.finishReason.isDefined) {
s"""\\nReason: ${event.finishReason.get}"""
s"""<br>Reason: ${event.finishReason.get}"""
} else {
""
}
}'
}"' +
| 'data-html="true">Executor ${executorId} removed</div>'
|}
""".stripMargin
events += removedEvent
}
events += removedEvent
}
}
events.toSeq
}
Expand Down
80 changes: 44 additions & 36 deletions core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
<div class="legend-area"><svg width="150px" height="55px">
<rect class="executor-added-legend"
x="5px" y="5px" width="20px" height="15px" rx="2px" ry="2px"></rect>
<text x="35px" y="17px">Executor Added</text>
<text x="35px" y="17px">Added</text>
<rect class="executor-removed-legend"
x="5px" y="30px" width="20px" height="15px" rx="2px" ry="2px"></rect>
<text x="35px" y="42px">Executor Removed</text>
<text x="35px" y="42px">Removed</text>
</svg></div>.toString.filter(_ != '\n')

private def makeStageEvent(stageInfos: Seq[StageInfo]): Seq[String] = {
Expand All @@ -69,17 +69,19 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
| 'group': 'stages',
| 'start': new Date(${submissionTime}),
| 'end': new Date(${completionTime}),
| 'content': '<div class="job-timeline-content">' +
| 'content': '<div class="job-timeline-content" data-toggle="tooltip"' +
| 'data-placement="top" data-html="true"' +
| 'data-title="${name} (Stage ${stageId}.${attemptId})<br>' +
| 'Status: ${status.toUpperCase}<br>' +
| 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != "running") {
s"""<br>Completion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}">' +
| '${name} (Stage ${stageId}.${attemptId})</div>',
| 'title': '${name} (Stage ${stageId}.${attemptId})\\nStatus: ${status.toUpperCase}\\n' +
| 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != "running") {
s"""\\nCompletion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}'
|}
""".stripMargin
}
Expand All @@ -89,46 +91,52 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
val events = ListBuffer[String]()
executorUIDatas.foreach {
case (executorId, event) =>
val addedEvent =
s"""
|{
| 'className': 'executor added',
| 'group': 'executors',
| 'start': new Date(${event.startTime}),
| 'content': '<div>Executor ${executorId} added</div>',
| 'title': 'Added at ${UIUtils.formatDate(new Date(event.startTime))}'
|}
""".stripMargin
events += addedEvent

if (event.finishTime.isDefined) {
val removedEvent =
val addedEvent =
s"""
|{
| 'className': 'executor added',
| 'group': 'executors',
| 'start': new Date(${event.startTime}),
| 'content': '<div class="executor-event-content"' +
| 'data-toggle="tooltip" data-placement="bottom"' +
| 'data-title="Executor ${executorId}<br>' +
| 'Added at ${UIUtils.formatDate(new Date(event.startTime))}"' +
| 'data-html="true">Executor ${executorId} added</div>'
|}
""".stripMargin
events += addedEvent

if (event.finishTime.isDefined) {
val removedEvent =
s"""
|{
| 'className': 'executor removed',
| 'group': 'executors',
| 'start': new Date(${event.finishTime.get}),
| 'content': '<div>Executor ${executorId} removed</div>',
| 'title': 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' +
| 'content': '<div class="executor-event-content"' +
| 'data-toggle="tooltip" data-placement="bottom"' +
| 'data-title="Executor ${executorId}<br>' +
| 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' +
| '${
if (event.finishReason.isDefined) {
s"""\\nReason: ${event.finishReason.get}"""
s"""<br>Reason: ${event.finishReason.get}"""
} else {
""
}
}'
}"' +
| 'data-html="true">Executor ${executorId} removed</div>'
|}
""".stripMargin
events += removedEvent
}
events += removedEvent
}
}
events.toSeq
}

private def makeTimeline(
stages: Seq[StageInfo],
executors: HashMap[String, ExecutorUIData],
jobStartTime: Long): Seq[Node] = {
appStartTime: Long): Seq[Node] = {

val stageEventJsonAsStrSeq = makeStageEvent(stages)
val executorsJsonAsStrSeq = makeExecutorEvent(executors)
Expand Down Expand Up @@ -163,7 +171,7 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
</div>
</div> ++
<script type="text/javascript">
{Unparsed(s"drawJobTimeline(${groupJsonArrayAsStr}, ${eventArrayAsStr}, ${jobStartTime});")}
{Unparsed(s"drawJobTimeline(${groupJsonArrayAsStr}, ${eventArrayAsStr}, ${appStartTime});")}
</script>
}

Expand Down Expand Up @@ -293,10 +301,10 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
</div>

var content = summary
val jobStartTime = jobData.submissionTime.get
val appStartTime = listener.startTime
val executorListener = parent.executorListener
content ++= makeTimeline(activeStages ++ completedStages ++ failedStages,
executorListener.executorIdToData, jobStartTime)
executorListener.executorIdToData, appStartTime)

if (shouldShowActiveStages) {
content ++= <h4 id="active">Active Stages ({activeStages.size})</h4> ++
Expand Down

0 comments on commit ef34a5b

Please sign in to comment.