-
Notifications
You must be signed in to change notification settings - Fork 28.6k
[SPARK-31534][WEBUI] Text for tooltip should be escaped. #28317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,7 +88,8 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We | |
// The timeline library treats contents as HTML, so we have to escape them. We need to add | ||
// extra layers of escaping in order to embed this in a Javascript string literal. | ||
val escapedDesc = Utility.escape(jobDescription) | ||
val jsEscapedDesc = StringEscapeUtils.escapeEcmaScript(escapedDesc) | ||
val jsEscapedDescForTooltip = StringEscapeUtils.escapeEcmaScript(Utility.escape(escapedDesc)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ur, this is triple escaping since we are using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we'd like to get a tooltip text There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. Thanks~ |
||
val jsEscapedDescForLabel = StringEscapeUtils.escapeEcmaScript(escapedDesc) | ||
val jobEventJsonAsStr = | ||
s""" | ||
|{ | ||
|
@@ -98,7 +99,7 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We | |
| 'end': new Date(${completionTime}), | ||
| 'content': '<div class="application-timeline-content"' + | ||
| 'data-html="true" data-placement="top" data-toggle="tooltip"' + | ||
| 'data-title="${jsEscapedDesc} (Job ${jobId})<br>' + | ||
| 'data-title="${jsEscapedDescForTooltip} (Job ${jobId})<br>' + | ||
| 'Status: ${status}<br>' + | ||
| 'Submitted: ${UIUtils.formatDate(new Date(submissionTime))}' + | ||
| '${ | ||
|
@@ -108,7 +109,7 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We | |
"" | ||
} | ||
}">' + | ||
| '${jsEscapedDesc} (Job ${jobId})</div>' | ||
| '${jsEscapedDescForLabel} (Job ${jobId})</div>' | ||
|} | ||
""".stripMargin | ||
jobEventJsonAsStr | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ import org.apache.spark.internal.config.Status._ | |
import org.apache.spark.internal.config.UI._ | ||
import org.apache.spark.shuffle.FetchFailedException | ||
import org.apache.spark.status.api.v1.{JacksonMessageWriter, RDDDataDistribution, StageStatus} | ||
import org.apache.spark.util.CallSite | ||
|
||
private[spark] class SparkUICssErrorHandler extends DefaultCssErrorHandler { | ||
|
||
|
@@ -687,29 +688,29 @@ class UISeleniumSuite extends SparkFunSuite with WebBrowser with Matchers with B | |
assert(stage0.contains("digraph G {\n subgraph clusterstage_0 {\n " + | ||
"label="Stage 0";\n subgraph ")) | ||
assert(stage0.contains("{\n label="parallelize";\n " + | ||
"0 [label="ParallelCollectionRDD [0]")) | ||
"0 [labelType="html" label="ParallelCollectionRDD [0]")) | ||
assert(stage0.contains("{\n label="map";\n " + | ||
"1 [label="MapPartitionsRDD [1]")) | ||
"1 [labelType="html" label="MapPartitionsRDD [1]")) | ||
assert(stage0.contains("{\n label="groupBy";\n " + | ||
"2 [label="MapPartitionsRDD [2]")) | ||
"2 [labelType="html" label="MapPartitionsRDD [2]")) | ||
|
||
val stage1 = Source.fromURL(sc.ui.get.webUrl + | ||
"/stages/stage/?id=1&attempt=0&expandDagViz=true").mkString | ||
assert(stage1.contains("digraph G {\n subgraph clusterstage_1 {\n " + | ||
"label="Stage 1";\n subgraph ")) | ||
assert(stage1.contains("{\n label="groupBy";\n " + | ||
"3 [label="ShuffledRDD [3]")) | ||
"3 [labelType="html" label="ShuffledRDD [3]")) | ||
assert(stage1.contains("{\n label="map";\n " + | ||
"4 [label="MapPartitionsRDD [4]")) | ||
"4 [labelType="html" label="MapPartitionsRDD [4]")) | ||
assert(stage1.contains("{\n label="groupBy";\n " + | ||
"5 [label="MapPartitionsRDD [5]")) | ||
"5 [labelType="html" label="MapPartitionsRDD [5]")) | ||
|
||
val stage2 = Source.fromURL(sc.ui.get.webUrl + | ||
"/stages/stage/?id=2&attempt=0&expandDagViz=true").mkString | ||
assert(stage2.contains("digraph G {\n subgraph clusterstage_2 {\n " + | ||
"label="Stage 2";\n subgraph ")) | ||
assert(stage2.contains("{\n label="groupBy";\n " + | ||
"6 [label="ShuffledRDD [6]")) | ||
"6 [labelType="html" label="ShuffledRDD [6]")) | ||
} | ||
} | ||
} | ||
|
@@ -772,6 +773,33 @@ class UISeleniumSuite extends SparkFunSuite with WebBrowser with Matchers with B | |
} | ||
} | ||
|
||
test("SPARK-31534: text for tooltip should be escaped") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for adding this. |
||
withSpark(newSparkContext()) { sc => | ||
sc.setLocalProperty(CallSite.LONG_FORM, "collect at <console>:25") | ||
sc.setLocalProperty(CallSite.SHORT_FORM, "collect at <console>:25") | ||
sc.parallelize(1 to 10).collect | ||
|
||
val driver = webDriver.asInstanceOf[HtmlUnitDriver] | ||
driver.setJavascriptEnabled(true) | ||
|
||
eventually(timeout(10.seconds), interval(50.milliseconds)) { | ||
goToUi(sc, "/jobs") | ||
val jobDesc = | ||
driver.findElement(By.cssSelector("div[class='application-timeline-content']")) | ||
jobDesc.getAttribute("data-title") should include ("collect at <console>:25") | ||
|
||
goToUi(sc, "/jobs/job/?id=0") | ||
val stageDesc = driver.findElement(By.cssSelector("div[class='job-timeline-content']")) | ||
stageDesc.getAttribute("data-title") should include ("collect at <console>:25") | ||
|
||
// Open DAG Viz. | ||
driver.findElement(By.id("job-dag-viz")).click() | ||
val nodeDesc = driver.findElement(By.cssSelector("g[class='node_0 node']")) | ||
nodeDesc.getAttribute("name") should include ("collect at <console>:25") | ||
} | ||
} | ||
} | ||
|
||
def goToUi(sc: SparkContext, path: String): Unit = { | ||
goToUi(sc.ui.get, path) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, did we handle all callers of
renderDot
in this PR?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I've noticed StagePage needs those escaping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current implementation, a source text is used for tooltip text as HTML and used for node label as plain text for DAG Viz.
I changed the usage of the source text for node label to interpret it as HTML.
So, I believe these escaping I remove from
renderDot
are not needed.