From 7ad032a4edae8b0907f9f7955fd11f84111f0c7b Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Mon, 16 Mar 2026 18:08:22 +0800 Subject: [PATCH] [SPARK-56002][UI] Make SQL plan visualization metrics table sortable Add 'sortable' class to metrics tables in the SQL plan viz side panel and call sorttable.makeSortable() after dynamic injection. Users can now click Metric/Value column headers to sort by name or value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../sql/execution/ui/static/spark-sql-viz.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js index ac266bd63d080..a287f5da9acac 100644 --- a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js +++ b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* global $, d3, dagreD3, graphlibDot, uiRoot, appBasePath */ +/* global $, d3, dagreD3, graphlibDot, uiRoot, appBasePath, sorttable */ var PlanVizConstants = { svgMarginX: 16, @@ -441,7 +441,7 @@ function updateDetailsPanel(nodeId, nodeDetails) { var html = ""; if (details.metrics && details.metrics.length > 0) { - html += buildMetricsTable(details.metrics, showStageTask); + html += buildMetricsTable(details.metrics, showStageTask, true); } else if (!details.children) { html += '

No metrics

'; } @@ -453,7 +453,7 @@ function updateDetailsPanel(nodeId, nodeDetails) { if (child) { html += '
' + htmlEscape(child.name) + '
'; if (child.metrics && child.metrics.length > 0) { - html += buildMetricsTable(child.metrics, showStageTask); + html += buildMetricsTable(child.metrics, showStageTask, true); } else { html += '

No metrics

'; } @@ -461,6 +461,13 @@ function updateDetailsPanel(nodeId, nodeDetails) { }); } bodyEl.innerHTML = html; + + // Initialize sorttable on dynamically injected metrics tables + if (typeof sorttable !== "undefined") { + bodyEl.querySelectorAll("table.sortable").forEach(function (table) { + sorttable.makeSortable(table); + }); + } } function htmlEscape(str) { @@ -471,8 +478,9 @@ function htmlEscape(str) { /* * Build an HTML metrics table from a metrics array. */ -function buildMetricsTable(metrics, showStageTask) { - var html = ''; +function buildMetricsTable(metrics, showStageTask, enableSort) { + var cls = "table table-sm table-bordered mb-0" + (enableSort ? " sortable" : ""); + var html = '
'; html += ""; metrics.forEach(function (m) { var name = htmlEscape(m.name); @@ -643,7 +651,7 @@ function rerenderWithDetailedLabels() { if (details.metrics && details.metrics.length > 0) { var html = '
'; html += '' + htmlEscape(details.name) + ''; - html += buildMetricsTable(details.metrics, showStageTask); + html += buildMetricsTable(details.metrics, showStageTask, false); html += '
'; node.labelType = "html"; node.label = html;
MetricValue