This is an automated email from the ASF dual-hosted git repository.
xiangfu0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 4e12c0f68b8 Fix query console blank page when result contains a MAP
(complex) column (#18783)
4e12c0f68b8 is described below
commit 4e12c0f68b851937e6ac50bfeefea1568de55a4e
Author: Xiang Fu <[email protected]>
AuthorDate: Tue Jun 16 18:55:30 2026 -0700
Fix query console blank page when result contains a MAP (complex) column
(#18783)
* Fix query console blank page when result contains a MAP (complex) column
The query console white-screens whenever a result set includes a MAP-typed
column. The broker serializes a MAP cell as an object, e.g.
`{"value":{"k":"v", ...}}`. In Table.tsx, `makeCell` treats any object that
has a truthy `value` key as an internal "rich cell" (`{value, tooltip,
component}` where `value` is a status string) and calls
`styleCell(cellData.value)`. For a MAP column `cellData.value` is itself an
object, so `styleCell`'s first line `str.toLowerCase()` throws
`TypeError: str.toLowerCase is not a function` during render. There is no
error boundary, so the whole results view unmounts and the page goes blank.
Add a single guarded `toDisplayString` helper that coerces any non-string
cell value to a string before the String methods run, shared by both
`styleCell` and `makeCell`. It also guards against `JSON.stringify` throwing
(circular references, BigInt) or returning `undefined` (functions, symbols),
so the renderer never throws on an unexpected cell value. MAP columns now
render their contents as JSON text instead of blanking the page.
* Render rich-cell value path for falsy values (0/false/null)
Address review feedback: makeCell only entered the `{value, ...}` cell path
when `cellData.value` was truthy, so a rich cell with a valid falsy value
(0, false, null, '') fell through to the fallback and rendered the whole
object. styleCell now coerces any type safely, so switch the guard to a
presence check (`has(cellData, 'value')`) and render falsy values correctly.
---
.../src/main/resources/app/components/Table.tsx | 41 ++++++++++++++++------
1 file changed, 31 insertions(+), 10 deletions(-)
diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx
b/pinot-controller/src/main/resources/app/components/Table.tsx
index 829933a8dea..c158da4ec07 100644
--- a/pinot-controller/src/main/resources/app/components/Table.tsx
+++ b/pinot-controller/src/main/resources/app/components/Table.tsx
@@ -89,6 +89,26 @@ staticSortFunctions.set("Estimated Size", sortBytes);
staticSortFunctions.set("Reported Size", sortBytes);
staticSortFunctions.set("Status", sortCellValue);
+// Safely coerce an arbitrary cell value to a string for display. Most cells
are strings, but
+// complex column types (e.g. MAP, which the broker serializes as an object)
arrive as non-strings.
+// JSON.stringify can itself throw (circular references, BigInt) or return
undefined (functions,
+// symbols), so both are guarded — the results view has no error boundary, and
any throw here
+// white-screens the whole page.
+const toDisplayString = (value: any): string => {
+ if (typeof value === 'string') {
+ return value;
+ }
+ if (value === null || value === undefined) {
+ return String(value);
+ }
+ try {
+ const json = JSON.stringify(value);
+ return json === undefined ? String(value) : json;
+ } catch (e) {
+ return '<DATA COULD NOT BE PARSED TO DISPLAY>';
+ }
+};
+
const StyledTableRow = withStyles((theme) =>
createStyles({
root: {
@@ -414,7 +434,10 @@ export default function CustomizedTables({
};
}, [search, timeoutId, filterSearchResults]);
- const styleCell = (str: string) => {
+ const styleCell = (cellValue: any) => {
+ // Coerce to a string first so the String methods below
(toLowerCase/search/replace) never
+ // throw on a non-string cell value (e.g. a MAP column) and crash the
whole results view.
+ const str = toDisplayString(cellValue);
if (str.toLowerCase() === 'good' || str.toLowerCase() === 'healthy' ||
str.toLowerCase() === 'online' || str.toLowerCase() === 'alive' ||
str.toLowerCase() === 'true') {
return (
<StyledChip
@@ -533,17 +556,15 @@ export default function CustomizedTables({
{styleCell(cellData.value)}
</Tooltip>
);
- } else if(has(cellData, 'value') && cellData.value) {
+ } else if (has(cellData, 'value')) {
+ // Render via the value path whenever the key is present, including
falsy values
+ // (0, false, null, ''). styleCell safely coerces any type, so we no
longer fall
+ // through to dumping the whole {value: ...} object for a falsy value.
return styleCell(cellData.value);
} else {
- try {
- const stringifiedJSON = JSON.stringify(cellData)
- return stringifiedJSON
- } catch(e) {
- // If the data is corrupted and not recognizable by
JSON.stringify, fallback to below error message instead
- // of crashing the whole page for the user.
- return '<DATA COULD NOT BE PARSED TO DISPLAY>'
- }
+ // Fall back to a stringified representation. toDisplayString guards
against
+ // JSON.stringify throwing (e.g. corrupted/circular data) so we
never crash the page.
+ return toDisplayString(cellData);
}
}
return styleCell(cellData.toString());
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]