From 62a86d54f5524986f7eeaaaabe765b4a1cb77b6d Mon Sep 17 00:00:00 2001 From: Richard Zowalla Date: Tue, 31 Mar 2026 20:40:59 +0200 Subject: [PATCH] Fix scientific notation display for large numbers in Storm UI tables (#8456) - Replace DataTables' built-in render.number() with a custom formatter that uses toLocaleString() to reliably format large integers with comma separators. - Add Cypress E2E tests with large fixture values to prevent regression. --- storm-webapp/cypress/e2e/component-page.cy.js | 28 +++++++++++++++++++ storm-webapp/cypress/e2e/index-page.cy.js | 8 ++++++ storm-webapp/cypress/e2e/topology-page.cy.js | 28 +++++++++++++++++++ .../cypress/fixtures/component-detail.json | 8 +++--- .../cypress/fixtures/topology-detail.json | 22 +++++++-------- .../storm/daemon/ui/WEB-INF/component.html | 14 +++++----- .../apache/storm/daemon/ui/WEB-INF/index.html | 4 +-- .../apache/storm/daemon/ui/WEB-INF/owner.html | 4 +-- .../storm/daemon/ui/WEB-INF/supervisor.html | 4 +-- .../storm/daemon/ui/WEB-INF/topology.html | 10 +++---- storm-webapp/src/main/webapp/js/script.js | 12 ++++++++ 11 files changed, 109 insertions(+), 33 deletions(-) diff --git a/storm-webapp/cypress/e2e/component-page.cy.js b/storm-webapp/cypress/e2e/component-page.cy.js index 367bee03ea6..4a128db1b7e 100644 --- a/storm-webapp/cypress/e2e/component-page.cy.js +++ b/storm-webapp/cypress/e2e/component-page.cy.js @@ -39,4 +39,32 @@ describe('Storm UI - Component Detail Page', () => { expect(win.moment).to.exist; }); }); + + it('formats large numbers with commas in bolt stats table', () => { + cy.get('#bolt-stats-table').within(() => { + cy.contains('td', '600,000,000,000').should('exist'); + cy.contains('td', '200,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); + + it('formats large numbers with commas in output stats table', () => { + cy.get('#bolt-output-stats-table').within(() => { + cy.contains('td', '600,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); + + it('formats large numbers with commas in input stats table', () => { + cy.get('#bolt-input-stats-table').within(() => { + cy.contains('td', '200,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); }); diff --git a/storm-webapp/cypress/e2e/index-page.cy.js b/storm-webapp/cypress/e2e/index-page.cy.js index f460cde4950..221ad48eae1 100644 --- a/storm-webapp/cypress/e2e/index-page.cy.js +++ b/storm-webapp/cypress/e2e/index-page.cy.js @@ -79,4 +79,12 @@ describe('Storm UI - Index Page', () => { it('shows the page rendered timestamp', () => { cy.get('#page-rendered-at-timestamp').should('contain', 'Page rendered at:'); }); + + it('does not display scientific notation in topology summary table', () => { + cy.get('#topology-summary-table').within(() => { + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); }); diff --git a/storm-webapp/cypress/e2e/topology-page.cy.js b/storm-webapp/cypress/e2e/topology-page.cy.js index 22f5dd13a57..010c2241cab 100644 --- a/storm-webapp/cypress/e2e/topology-page.cy.js +++ b/storm-webapp/cypress/e2e/topology-page.cy.js @@ -46,4 +46,32 @@ describe('Storm UI - Topology Detail Page', () => { it('renders topology action buttons', () => { cy.get('input[type="button"]').should('have.length.greaterThan', 0); }); + + it('formats large numbers with commas, not scientific notation', () => { + cy.get('#topology-stats-table').within(() => { + cy.contains('td', '200,000,000,000').should('exist'); + cy.contains('td', '100,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); + + it('formats large spout numbers with commas, not scientific notation', () => { + cy.get('#spout-stats-table').within(() => { + cy.contains('td', '200,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); + + it('formats large bolt numbers with commas, not scientific notation', () => { + cy.get('#bolt-stats-table').within(() => { + cy.contains('td', '600,000,000,000').should('exist'); + cy.get('td').each(($td) => { + expect($td.text()).not.to.match(/\de[+]\d/i); + }); + }); + }); }); diff --git a/storm-webapp/cypress/fixtures/component-detail.json b/storm-webapp/cypress/fixtures/component-detail.json index 5bed1680405..5313969a391 100644 --- a/storm-webapp/cypress/fixtures/component-detail.json +++ b/storm-webapp/cypress/fixtures/component-detail.json @@ -17,16 +17,16 @@ "profilerActive": [], "schedulerDisplayResource": false, "boltStats": [ - {"windowPretty": "All time", "window": ":all-time", "emitted": 7200000, "transferred": 7200000, "executeLatency": "0.020", "executed": 2400000, "processLatency": "0.015", "acked": 2400000, "failed": 0, "capacity": "0.150"} + {"windowPretty": "All time", "window": ":all-time", "emitted": 600000000000, "transferred": 600000000000, "executeLatency": "0.020", "executed": 200000000000, "processLatency": "0.015", "acked": 200000000000, "failed": 0, "capacity": "0.150"} ], "inputStats": [ - {"component": "spout", "encodedComponent": "spout", "stream": "default", "executeLatency": "0.020", "processLatency": "0.015", "executed": 2400000, "acked": 2400000, "failed": 0} + {"component": "spout", "encodedComponent": "spout", "stream": "default", "executeLatency": "0.020", "processLatency": "0.015", "executed": 200000000000, "acked": 200000000000, "failed": 0} ], "outputStats": [ - {"stream": "default", "emitted": 7200000, "transferred": 7200000} + {"stream": "default", "emitted": 600000000000, "transferred": 600000000000} ], "executorStats": [ - {"id": "[2, 2]", "encodedId": "%5B2%2C+2%5D", "uptime": "1d 5h 30m 10s", "uptimeSeconds": 106210, "host": "supervisor-host-1", "port": 6700, "emitted": 1200000, "transferred": 1200000, "capacity": "0.150", "executeLatency": "0.020", "executed": 400000, "processLatency": "0.015", "acked": 400000, "failed": 0, "workerLogLink": "http://supervisor-host-1:8000/log?file=worker.log"} + {"id": "[2, 2]", "encodedId": "%5B2%2C+2%5D", "uptime": "1d 5h 30m 10s", "uptimeSeconds": 106210, "host": "supervisor-host-1", "port": 6700, "emitted": 100000000000, "transferred": 100000000000, "capacity": "0.150", "executeLatency": "0.020", "executed": 33000000000, "processLatency": "0.015", "acked": 33000000000, "failed": 0, "workerLogLink": "http://supervisor-host-1:8000/log?file=worker.log"} ], "componentErrors": [] } diff --git a/storm-webapp/cypress/fixtures/topology-detail.json b/storm-webapp/cypress/fixtures/topology-detail.json index aeb0da4d394..e3a8fed5c79 100644 --- a/storm-webapp/cypress/fixtures/topology-detail.json +++ b/storm-webapp/cypress/fixtures/topology-detail.json @@ -22,7 +22,7 @@ {"windowPretty": "10m 0s", "window": "600", "emitted": 12000, "transferred": 12000, "completeLatency": "5.123", "acked": 6000, "failed": 0}, {"windowPretty": "3h 0m 0s", "window": "10800", "emitted": 180000, "transferred": 180000, "completeLatency": "4.567", "acked": 90000, "failed": 0}, {"windowPretty": "1d 0h 0m 0s", "window": "86400", "emitted": 1200000, "transferred": 1200000, "completeLatency": "4.890", "acked": 600000, "failed": 0}, - {"windowPretty": "All time", "window": ":all-time", "emitted": 2400000, "transferred": 2400000, "completeLatency": "4.800", "acked": 1200000, "failed": 0} + {"windowPretty": "All time", "window": ":all-time", "emitted": 200000000000, "transferred": 200000000000, "completeLatency": "4.800", "acked": 100000000000, "failed": 5000000000} ], "spouts": [ { @@ -30,10 +30,10 @@ "encodedSpoutId": "spout", "executors": 3, "tasks": 3, - "emitted": 2400000, - "transferred": 2400000, + "emitted": 200000000000, + "transferred": 200000000000, "completeLatency": "4.800", - "acked": 1200000, + "acked": 100000000000, "failed": 0, "errorHost": "", "errorPort": "", @@ -48,13 +48,13 @@ "encodedBoltId": "split", "executors": 6, "tasks": 6, - "emitted": 7200000, - "transferred": 7200000, + "emitted": 600000000000, + "transferred": 600000000000, "capacity": "0.150", "executeLatency": "0.020", - "executed": 2400000, + "executed": 200000000000, "processLatency": "0.015", - "acked": 2400000, + "acked": 200000000000, "failed": 0, "errorHost": "", "errorPort": "", @@ -67,13 +67,13 @@ "encodedBoltId": "count", "executors": 6, "tasks": 6, - "emitted": 7200000, + "emitted": 600000000000, "transferred": 0, "capacity": "0.200", "executeLatency": "0.010", - "executed": 7200000, + "executed": 600000000000, "processLatency": "0.008", - "acked": 7200000, + "acked": 600000000000, "failed": 0, "errorHost": "", "errorPort": "", diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html index 4aacd974402..3d1000c0d25 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html @@ -253,7 +253,7 @@ info: false, searching: false, columnDefs: [ - {type: "num", targets: [1, 2, 4, 5], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [1, 2, 4, 5], render: renderIntegerWithCommas}, {type: "num", targets: [3]}, {type: "time-str", targets: [0]} ] @@ -265,7 +265,7 @@ //stream, emitted, transferred, compltete latency, acked, failed dtAutoPage("#output-stats-table", { columnDefs: [ - {type: "num", targets: [1, 2, 4, 5], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [1, 2, 4, 5], render: renderIntegerWithCommas}, {type: "num", targets: [3]} ] }); @@ -278,7 +278,7 @@ columnDefs: [ {render: renderSupervisorPageLink, searchable: true, targets: [2]}, {render: renderActionCheckbox, searchable: false, targets: [4]}, - {type: "num", targets: [5, 6, 8, 9], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [5, 6, 8, 9], render: renderIntegerWithCommas}, {type: "num", targets: [7]}, {type: "time-str", targets: [1]}, ] @@ -290,7 +290,7 @@ //window, emitted, transferred, execute latency, executed, process latency, acked, failed dtAutoPage("#bolt-stats-table", { columnDefs: [ - {type: "num", targets: [1, 2, 4, 6, 7], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [1, 2, 4, 6, 7], render: renderIntegerWithCommas}, {type: "num", targets: [3, 5]}, {type: "time-str", targets: [0]} ] @@ -303,7 +303,7 @@ dtAutoPage("#bolt-input-stats-table", { columnDefs: [ {type: "num", targets: [2, 4]}, - {type: "num", targets: [3, 5, 6], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [3, 5, 6], render: renderIntegerWithCommas} ] }); }); @@ -313,7 +313,7 @@ //stream, emitted, transferred dtAutoPage("#bolt-output-stats-table", { columnDefs: [ - {type: "num", targets: [1, 2], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [1, 2], render: renderIntegerWithCommas} ] }); }); @@ -325,7 +325,7 @@ columnDefs: [ {render: renderSupervisorPageLink, searchable: true, targets: [2]}, {render: renderActionCheckbox, searchable: false, targets: [4]}, - {type: "num", targets: [5, 6, 9, 11, 12], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [5, 6, 9, 11, 12], render: renderIntegerWithCommas}, {type: "num", targets: [7, 8, 10]}, {type: "time-str", targets: [1]}, ] diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/index.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/index.html index a18220dd929..2afc415e015 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/index.html +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/index.html @@ -189,7 +189,7 @@

Nimbus Configuration

//name, owner, status, uptime, num workers, num executors, num tasks, replication count, assigned total mem, assigned total cpu, scheduler info dtAutoPage("#topology-summary-table", { columnDefs: [ - {type: "num", targets: [4, 5, 6, 7, 8, 9], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [4, 5, 6, 7, 8, 9], render: renderIntegerWithCommas}, {type: "time-str", targets: [3]} ] }); @@ -204,7 +204,7 @@

Nimbus Configuration

dtAutoPage("#supervisor-summary-table", { columnDefs: [ {type: "num", targets: [3, 4]}, - {type: "num", targets: [6], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [6], render: renderIntegerWithCommas}, {type: "time-str", targets: [2]} ] }); diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html index 0b039deb7fe..da7d0663cfd 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html @@ -186,7 +186,7 @@

Owner Topologies

columnDefs: [{ type: "num", targets: [4, 5, 6] }, { - type: "num", targets: [7, 8], render: $.fn.dataTable.render.number(',', '.', 0) + type: "num", targets: [7, 8], render: renderIntegerWithCommas }, { type: "time-str", targets: [3] }] @@ -198,7 +198,7 @@

Owner Topologies

columnDefs: [{ type: "num", targets: [4, 5, 6] }, { - type: "num", targets: [7], render: $.fn.dataTable.render.number(',', '.', 0) + type: "num", targets: [7], render: renderIntegerWithCommas }, { type: "time-str", targets: [3] }] diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html index dabd21e481d..022cd748ed5 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html @@ -119,7 +119,7 @@

Worker resources

dtAutoPage("#supervisor-summary-table", { columnDefs: [ {type: "num", targets: [3, 4]}, - {type: "num", targets: [5, 6, 7, 8, 9, 10, 11, 12, 13], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [5, 6, 7, 8, 9, 10, 11, 12, 13], render: renderIntegerWithCommas}, {type: "time-str", targets: [2]} ] }); @@ -128,7 +128,7 @@

Worker resources

dtAutoPage("#supervisor-summary-table", { columnDefs: [ {type: "num", targets: [3, 4]}, - {type: "num", targets: [5], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [5], render: renderIntegerWithCommas}, {type: "time-str", targets: [2]} ] }); diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html index a17cd638a0c..08200783021 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html @@ -319,7 +319,7 @@

Topology resources

info: false, searching: false, columnDefs: [ - {type: "num", targets: [1, 2, 4, 5], render: $.fn.dataTable.render.number(',', '.', 0)}, + {type: "num", targets: [1, 2, 4, 5], render: renderIntegerWithCommas}, {type: "num", targets: [3]}, {type: "time-str", targets: [0]} ] @@ -333,7 +333,7 @@

Topology resources

dtAutoPage("#spout-stats-table", { columnDefs: [ {type: "num", targets: [1, 6, 9]}, - {type: "num", targets: [2, 3, 4, 5, 7, 8, 10, 11], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [2, 3, 4, 5, 7, 8, 10, 11], render: renderIntegerWithCommas} ] }); } else { @@ -342,7 +342,7 @@

Topology resources

dtAutoPage("#spout-stats-table", { columnDefs: [ {type: "num", targets: [1, 2, 5]}, - {type: "num", targets: [3, 4, 6, 7], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [3, 4, 6, 7], render: renderIntegerWithCommas} ] }); } @@ -356,7 +356,7 @@

Topology resources

dtAutoPage("#bolt-stats-table", { columnDefs: [ {type: "num", targets: [1, 2, 9, 10, 12, 16]}, - {type: "num", targets: [3, 4, 5, 6, 7, 8, 11, 13, 14], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [3, 4, 5, 6, 7, 8, 11, 13, 14], render: renderIntegerWithCommas} ] }); } else { @@ -366,7 +366,7 @@

Topology resources

dtAutoPage("#bolt-stats-table", { columnDefs: [ {type: "num", targets: [1, 2, 5, 6, 8, 12]}, - {type: "num", targets: [3, 4, 7, 9, 10], render: $.fn.dataTable.render.number(',', '.', 0)} + {type: "num", targets: [3, 4, 7, 9, 10], render: renderIntegerWithCommas} ] }); } diff --git a/storm-webapp/src/main/webapp/js/script.js b/storm-webapp/src/main/webapp/js/script.js index cea402a67ae..f684b3ff973 100644 --- a/storm-webapp/src/main/webapp/js/script.js +++ b/storm-webapp/src/main/webapp/js/script.js @@ -125,6 +125,17 @@ function dtAutoPage(selector, conf) { return $(selector).DataTable(conf); } +function renderIntegerWithCommas(data, type) { + if (type !== 'display' && type !== 'filter') { + return data; + } + var n = Number(data); + if (isNaN(n)) { + return data; + } + return n.toLocaleString('en-US', {maximumFractionDigits: 0}); +} + function toggleSys() { var sys = $.cookies.get('sys') || false; sys = !sys; @@ -682,6 +693,7 @@ function setStormUITitle(uiTitle, clusterConfig) { // --- Expose functions to window for use by inline