Web console: better handle BigInt math (#11450)

* better handle BigInt math

* correctly brace bigint

* feedback fixes and tests
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index e16e337..1b0aa4f 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -33,6 +33,12 @@
 export const EMPTY_OBJECT: any = {};
 export const EMPTY_ARRAY: any[] = [];
 
+export type NumberLike = number | BigInt;
+
+export function isNumberLikeNaN(x: NumberLike): boolean {
+  return isNaN(Number(x));
+}
+
 export function wait(ms: number): Promise<void> {
   return new Promise(resolve => {
     setTimeout(resolve, ms);
@@ -228,29 +234,29 @@
 
 // ----------------------------
 
-export function formatInteger(n: number): string {
+export function formatInteger(n: NumberLike): string {
   return numeral(n).format('0,0');
 }
 
-export function formatBytes(n: number): string {
+export function formatBytes(n: NumberLike): string {
   return numeral(n).format('0.00 b');
 }
 
-export function formatBytesCompact(n: number): string {
+export function formatBytesCompact(n: NumberLike): string {
   return numeral(n).format('0.00b');
 }
 
-export function formatMegabytes(n: number): string {
-  return numeral(n / 1048576).format('0,0.0');
+export function formatMegabytes(n: NumberLike): string {
+  return numeral(Number(n) / 1048576).format('0,0.0');
 }
 
-export function formatPercent(n: number): string {
-  return (n * 100).toFixed(2) + '%';
+export function formatPercent(n: NumberLike): string {
+  return (Number(n) * 100).toFixed(2) + '%';
 }
 
-export function formatMillions(n: number): string {
-  const s = (n / 1e6).toFixed(3);
-  if (s === '0.000') return String(Math.round(n));
+export function formatMillions(n: NumberLike): string {
+  const s = (Number(n) / 1e6).toFixed(3);
+  if (s === '0.000') return String(Math.round(Number(n)));
   return s + ' M';
 }
 
@@ -258,14 +264,15 @@
   return ('00' + str).substr(-2);
 }
 
-export function formatDuration(ms: number): string {
-  const timeInHours = Math.floor(ms / 3600000);
-  const timeInMin = Math.floor(ms / 60000) % 60;
-  const timeInSec = Math.floor(ms / 1000) % 60;
+export function formatDuration(ms: NumberLike): string {
+  const n = Number(ms);
+  const timeInHours = Math.floor(n / 3600000);
+  const timeInMin = Math.floor(n / 60000) % 60;
+  const timeInSec = Math.floor(n / 1000) % 60;
   return timeInHours + ':' + pad2(timeInMin) + ':' + pad2(timeInSec);
 }
 
-export function pluralIfNeeded(n: number, singular: string, plural?: string): string {
+export function pluralIfNeeded(n: NumberLike, singular: string, plural?: string): string {
   if (!plural) plural = singular + 's';
   return `${formatInteger(n)} ${n === 1 ? singular : plural}`;
 }
@@ -274,7 +281,7 @@
 
 export function parseJson(json: string): any {
   try {
-    return JSON.parse(json);
+    return JSONBig.parse(json);
   } catch (e) {
     return undefined;
   }
@@ -282,7 +289,7 @@
 
 export function validJson(json: string): boolean {
   try {
-    JSON.parse(json);
+    JSONBig.parse(json);
     return true;
   } catch (e) {
     return false;
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 2901733..e9aff6c 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -57,8 +57,10 @@
   formatMillions,
   formatPercent,
   getDruidErrorMessage,
+  isNumberLikeNaN,
   LocalStorageKeys,
   lookupBy,
+  NumberLike,
   pluralIfNeeded,
   queryDruidSql,
   QueryManager,
@@ -114,7 +116,7 @@
 
 const DEFAULT_RULES_KEY = '_default';
 
-function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string {
+function formatLoadDrop(segmentsToLoad: NumberLike, segmentsToDrop: NumberLike): string {
   const loadDrop: string[] = [];
   if (segmentsToLoad) {
     loadDrop.push(`${pluralIfNeeded(segmentsToLoad, 'segment')} to load`);
@@ -152,21 +154,21 @@
 
 interface DatasourceQueryResultRow {
   readonly datasource: string;
-  readonly num_segments: number;
-  readonly num_segments_to_load: number;
-  readonly num_segments_to_drop: number;
-  readonly minute_aligned_segments: number;
-  readonly hour_aligned_segments: number;
-  readonly day_aligned_segments: number;
-  readonly month_aligned_segments: number;
-  readonly year_aligned_segments: number;
-  readonly total_data_size: number;
-  readonly replicated_size: number;
-  readonly min_segment_rows: number;
-  readonly avg_segment_rows: number;
-  readonly max_segment_rows: number;
-  readonly total_rows: number;
-  readonly avg_row_size: number;
+  readonly num_segments: NumberLike;
+  readonly num_segments_to_load: NumberLike;
+  readonly num_segments_to_drop: NumberLike;
+  readonly minute_aligned_segments: NumberLike;
+  readonly hour_aligned_segments: NumberLike;
+  readonly day_aligned_segments: NumberLike;
+  readonly month_aligned_segments: NumberLike;
+  readonly year_aligned_segments: NumberLike;
+  readonly total_data_size: NumberLike;
+  readonly replicated_size: NumberLike;
+  readonly min_segment_rows: NumberLike;
+  readonly avg_segment_rows: NumberLike;
+  readonly max_segment_rows: NumberLike;
+  readonly total_rows: NumberLike;
+  readonly avg_row_size: NumberLike;
 }
 
 function makeEmptyDatasourceQueryResultRow(datasource: string): DatasourceQueryResultRow {
@@ -224,7 +226,7 @@
 
 interface CompactionDialogOpenOn {
   readonly datasource: string;
-  readonly compactionConfig: CompactionConfig;
+  readonly compactionConfig?: CompactionConfig;
 }
 
 export interface DatasourcesViewProps {
@@ -800,9 +802,9 @@
 
   getDatasourceActions(
     datasource: string,
-    unused: boolean,
+    unused: boolean | undefined,
     rules: Rule[],
-    compactionConfig: CompactionConfig,
+    compactionConfig: CompactionConfig | undefined,
   ): BasicAction[] {
     const { goToQuery, goToTask, capabilities } = this.props;
 
@@ -1032,7 +1034,7 @@
               minWidth: 200,
               accessor: 'num_segments',
               Cell: ({ value: num_segments, original }) => {
-                const { datasource, unused, num_segments_to_load } = original;
+                const { datasource, unused, num_segments_to_load } = original as Datasource;
                 if (unused) {
                   return (
                     <span>
@@ -1086,7 +1088,7 @@
               filterable: false,
               minWidth: 100,
               Cell: ({ original }) => {
-                const { num_segments_to_load, num_segments_to_drop } = original;
+                const { num_segments_to_load, num_segments_to_drop } = original as Datasource;
                 return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
               },
             },
@@ -1107,8 +1109,13 @@
               filterable: false,
               width: 220,
               Cell: ({ value, original }) => {
-                const { min_segment_rows, max_segment_rows } = original;
-                if (isNaN(value) || isNaN(min_segment_rows) || isNaN(max_segment_rows)) return '-';
+                const { min_segment_rows, max_segment_rows } = original as Datasource;
+                if (
+                  isNumberLikeNaN(value) ||
+                  isNumberLikeNaN(min_segment_rows) ||
+                  isNumberLikeNaN(max_segment_rows)
+                )
+                  return '-';
                 return (
                   <>
                     <BracedText
@@ -1141,22 +1148,22 @@
                   day_aligned_segments,
                   month_aligned_segments,
                   year_aligned_segments,
-                } = original;
+                } = original as Datasource;
                 const segmentGranularities: string[] = [];
-                if (!num_segments || isNaN(year_aligned_segments)) return '-';
-                if (num_segments - minute_aligned_segments) {
+                if (!num_segments || isNumberLikeNaN(year_aligned_segments)) return '-';
+                if (num_segments !== minute_aligned_segments) {
                   segmentGranularities.push('Sub minute');
                 }
-                if (minute_aligned_segments - hour_aligned_segments) {
+                if (minute_aligned_segments !== hour_aligned_segments) {
                   segmentGranularities.push('Minute');
                 }
-                if (hour_aligned_segments - day_aligned_segments) {
+                if (hour_aligned_segments !== day_aligned_segments) {
                   segmentGranularities.push('Hour');
                 }
-                if (day_aligned_segments - month_aligned_segments) {
+                if (day_aligned_segments !== month_aligned_segments) {
                   segmentGranularities.push('Day');
                 }
-                if (month_aligned_segments - year_aligned_segments) {
+                if (month_aligned_segments !== year_aligned_segments) {
                   segmentGranularities.push('Month');
                 }
                 if (year_aligned_segments) {
@@ -1172,7 +1179,7 @@
               filterable: false,
               width: 100,
               Cell: ({ value }) => {
-                if (isNaN(value)) return '-';
+                if (isNumberLikeNaN(value)) return '-';
                 return <BracedText text={formatTotalRows(value)} braces={totalRowsValues} />;
               },
             },
@@ -1183,7 +1190,7 @@
               filterable: false,
               width: 100,
               Cell: ({ value }) => {
-                if (isNaN(value)) return '-';
+                if (isNumberLikeNaN(value)) return '-';
                 return <BracedText text={formatAvgRowSize(value)} braces={avgRowSizeValues} />;
               },
             },
@@ -1194,7 +1201,7 @@
               filterable: false,
               width: 100,
               Cell: ({ value }) => {
-                if (isNaN(value)) return '-';
+                if (isNumberLikeNaN(value)) return '-';
                 return (
                   <BracedText text={formatReplicatedSize(value)} braces={replicatedSizeValues} />
                 );
@@ -1208,7 +1215,7 @@
               filterable: false,
               width: 150,
               Cell: ({ original }) => {
-                const { datasource, compactionConfig, compactionStatus } = original;
+                const { datasource, compactionConfig, compactionStatus } = original as Datasource;
                 return (
                   <span
                     className="clickable-cell"
@@ -1239,7 +1246,7 @@
                   : 0,
               filterable: false,
               Cell: ({ original }) => {
-                const { compactionStatus } = original;
+                const { compactionStatus } = original as Datasource;
 
                 if (!compactionStatus || zeroCompactionStatus(compactionStatus)) {
                   return (
@@ -1296,7 +1303,7 @@
                 (compactionStatus && compactionStatus.bytesAwaitingCompaction) || 0,
               filterable: false,
               Cell: ({ original }) => {
-                const { compactionStatus } = original;
+                const { compactionStatus } = original as Datasource;
 
                 if (!compactionStatus) {
                   return <BracedText text="-" braces={leftToBeCompactedValues} />;
@@ -1318,7 +1325,7 @@
               filterable: false,
               minWidth: 100,
               Cell: ({ original }) => {
-                const { datasource, rules } = original;
+                const { datasource, rules } = original as Datasource;
                 return (
                   <span
                     onClick={() =>
@@ -1348,7 +1355,7 @@
               width: ACTION_COLUMN_WIDTH,
               filterable: false,
               Cell: ({ value: datasource, original }) => {
-                const { unused, rules, compactionConfig } = original;
+                const { unused, rules, compactionConfig } = original as Datasource;
                 const datasourceActions = this.getDatasourceActions(
                   datasource,
                   unused,
diff --git a/web-console/src/views/query-view/query-output/query-output.tsx b/web-console/src/views/query-view/query-output/query-output.tsx
index 504aba2..78df215 100644
--- a/web-console/src/views/query-view/query-output/query-output.tsx
+++ b/web-console/src/views/query-view/query-output/query-output.tsx
@@ -33,7 +33,14 @@
 
 import { BracedText, TableCell } from '../../../components';
 import { ShowValueDialog } from '../../../dialogs/show-value-dialog/show-value-dialog';
-import { copyAndAlert, deepSet, filterMap, prettyPrintSql, stringifyValue } from '../../../utils';
+import {
+  copyAndAlert,
+  deepSet,
+  filterMap,
+  oneOf,
+  prettyPrintSql,
+  stringifyValue,
+} from '../../../utils';
 import { BasicAction, basicActionsToMenu } from '../../../utils/basic-action';
 
 import { ColumnRenameInput } from './column-rename-input/column-rename-input';
@@ -65,7 +72,7 @@
     const numColumns = queryResult.header.length;
     for (let c = 0; c < numColumns; c++) {
       const brace = filterMap(rows, row =>
-        typeof row[c] === 'number' ? String(row[c]) : undefined,
+        oneOf(typeof row[c], 'number', 'bigint') ? String(row[c]) : undefined,
       );
       if (rows.length === brace.length) {
         numericColumnBraces[c] = brace;
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index 33f03dd..fa86426 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -51,6 +51,7 @@
   getNeedleAndMode,
   LocalStorageKeys,
   makeBooleanFilter,
+  NumberLike,
   queryDruidSql,
   QueryManager,
   QueryState,
@@ -144,7 +145,7 @@
   partitioning: string;
   size: number;
   partition_num: number;
-  num_rows: number;
+  num_rows: NumberLike;
   num_replicas: number;
   is_available: number;
   is_published: number;
diff --git a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
old mode 100755
new mode 100644
index a9e2be8..a7238af
--- a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
+++ b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`services view action services view 1`] = `
+exports[`ServicesView renders data 1`] = `
 <div
   className="services-view app-view"
 >
@@ -207,7 +207,40 @@
         },
       ]
     }
-    data={Array []}
+    data={
+      Array [
+        Array [
+          Object {
+            "curr_size": 0,
+            "host": "localhost",
+            "is_leader": 0,
+            "max_size": 0,
+            "plaintext_port": 8082,
+            "rank": 5,
+            "service": "localhost:8082",
+            "service_type": "broker",
+            "tier": null,
+            "tls_port": -1,
+          },
+          Object {
+            "curr_size": 179744287,
+            "host": "localhost",
+            "is_leader": 0,
+            "max_size": 3000000000n,
+            "plaintext_port": 8083,
+            "rank": 4,
+            "segmentsToDrop": 0,
+            "segmentsToDropSize": 0,
+            "segmentsToLoad": 0,
+            "segmentsToLoadSize": 0,
+            "service": "localhost:8083",
+            "service_type": "historical",
+            "tier": "_default_tier",
+            "tls_port": -1,
+          },
+        ],
+      ]
+    }
     defaultExpanded={Object {}}
     defaultFilterMethod={[Function]}
     defaultFiltered={Array []}
@@ -252,7 +285,7 @@
     getTrProps={[Function]}
     groupedByPivotKey="_groupedByPivot"
     indexKey="_index"
-    loading={true}
+    loading={false}
     loadingText="Loading..."
     multiSort={true}
     nestingLevelKey="_nestingLevel"
diff --git a/web-console/src/views/services-view/services-view.spec.tsx b/web-console/src/views/services-view/services-view.spec.tsx
index 32a5a6e..4336739 100644
--- a/web-console/src/views/services-view/services-view.spec.tsx
+++ b/web-console/src/views/services-view/services-view.spec.tsx
@@ -19,15 +19,75 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities, QueryState } from '../../utils';
 
 import { ServicesView } from './services-view';
 
-describe('services view', () => {
-  it('action services view', () => {
-    const servicesView = shallow(
-      <ServicesView goToQuery={() => {}} goToTask={() => {}} capabilities={Capabilities.FULL} />,
+jest.mock('../../utils', () => {
+  const originalUtils = jest.requireActual('../../utils');
+
+  class QueryManagerMock {
+    private readonly onStateChange: any;
+
+    constructor(opt: { onStateChange: any }) {
+      this.onStateChange = opt.onStateChange;
+    }
+
+    public runQuery() {
+      this.onStateChange(
+        new QueryState({
+          data: [
+            [
+              {
+                service: 'localhost:8082',
+                service_type: 'broker',
+                tier: null,
+                host: 'localhost',
+                plaintext_port: 8082,
+                tls_port: -1,
+                curr_size: 0,
+                max_size: 0,
+                is_leader: 0,
+                rank: 5,
+              },
+              {
+                service: 'localhost:8083',
+                service_type: 'historical',
+                tier: '_default_tier',
+                host: 'localhost',
+                plaintext_port: 8083,
+                tls_port: -1,
+                curr_size: 179744287,
+                max_size: BigInt(3000000000),
+                is_leader: 0,
+                rank: 4,
+                segmentsToLoad: 0,
+                segmentsToDrop: 0,
+                segmentsToLoadSize: 0,
+                segmentsToDropSize: 0,
+              },
+            ],
+          ],
+        }) as any,
+      );
+    }
+
+    public terminate() {}
+  }
+
+  return {
+    ...originalUtils,
+    QueryManager: QueryManagerMock,
+  };
+});
+
+describe('ServicesView', () => {
+  it('renders data', () => {
+    const comp = (
+      <ServicesView goToQuery={() => {}} goToTask={() => {}} capabilities={Capabilities.FULL} />
     );
+
+    const servicesView = shallow(comp);
     expect(servicesView).toMatchSnapshot();
   });
 });
diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx
index 0f9df1a..8a90090 100644
--- a/web-console/src/views/services-view/services-view.tsx
+++ b/web-console/src/views/services-view/services-view.tsx
@@ -43,7 +43,9 @@
   formatBytesCompact,
   LocalStorageKeys,
   lookupBy,
+  NumberLike,
   oneOf,
+  pluralIfNeeded,
   queryDruidSql,
   QueryManager,
   QueryState,
@@ -73,20 +75,24 @@
 };
 
 function formatQueues(
-  segmentsToLoad: number,
-  segmentsToLoadSize: number,
-  segmentsToDrop: number,
-  segmentsToDropSize: number,
+  segmentsToLoad: NumberLike,
+  segmentsToLoadSize: NumberLike,
+  segmentsToDrop: NumberLike,
+  segmentsToDropSize: NumberLike,
 ): string {
   const queueParts: string[] = [];
   if (segmentsToLoad) {
     queueParts.push(
-      `${segmentsToLoad} segments to load (${formatBytesCompact(segmentsToLoadSize)})`,
+      `${pluralIfNeeded(segmentsToLoad, 'segment')} to load (${formatBytesCompact(
+        segmentsToLoadSize,
+      )})`,
     );
   }
   if (segmentsToDrop) {
     queueParts.push(
-      `${segmentsToDrop} segments to drop (${formatBytesCompact(segmentsToDropSize)})`,
+      `${pluralIfNeeded(segmentsToDrop, 'segment')} to drop (${formatBytesCompact(
+        segmentsToDropSize,
+      )})`,
     );
   }
   return queueParts.join(', ') || 'Empty load/drop queues';
@@ -110,38 +116,38 @@
 }
 
 interface ServiceQueryResultRow {
-  service: string;
-  service_type: string;
-  tier: string;
-  is_leader: number;
-  curr_size: number;
-  host: string;
-  max_size: number;
-  plaintext_port: number;
-  tls_port: number;
+  readonly service: string;
+  readonly service_type: string;
+  readonly tier: string;
+  readonly is_leader: number;
+  readonly host: string;
+  readonly curr_size: NumberLike;
+  readonly max_size: NumberLike;
+  readonly plaintext_port: number;
+  readonly tls_port: number;
 }
 
 interface LoadQueueStatus {
-  segmentsToDrop: number;
-  segmentsToDropSize: number;
-  segmentsToLoad: number;
-  segmentsToLoadSize: number;
+  readonly segmentsToDrop: NumberLike;
+  readonly segmentsToDropSize: NumberLike;
+  readonly segmentsToLoad: NumberLike;
+  readonly segmentsToLoadSize: NumberLike;
 }
 
 interface MiddleManagerQueryResultRow {
-  availabilityGroups: string[];
-  blacklistedUntil: string | null;
-  currCapacityUsed: number;
-  lastCompletedTaskTime: string;
-  category: string;
-  runningTasks: string[];
-  worker: {
-    capacity: number;
-    host: string;
-    ip: string;
-    scheme: string;
-    version: string;
-    category: string;
+  readonly availabilityGroups: string[];
+  readonly blacklistedUntil: string | null;
+  readonly currCapacityUsed: NumberLike;
+  readonly lastCompletedTaskTime: string;
+  readonly category: string;
+  readonly runningTasks: string[];
+  readonly worker: {
+    readonly capacity: NumberLike;
+    readonly host: string;
+    readonly ip: string;
+    readonly scheme: string;
+    readonly version: string;
+    readonly category: string;
   };
 }
 
@@ -164,7 +170,15 @@
   //   peon => 1
 
   static SERVICE_SQL = `SELECT
-  "server" AS "service", "server_type" AS "service_type", "tier", "host", "plaintext_port", "tls_port", "curr_size", "max_size", "is_leader",
+  "server" AS "service",
+  "server_type" AS "service_type",
+  "tier",
+  "host",
+  "plaintext_port",
+  "tls_port",
+  "curr_size",
+  "max_size",
+  "is_leader",
   (
     CASE "server_type"
     WHEN 'coordinator' THEN 8
@@ -430,26 +444,30 @@
             filterable: false,
             accessor: row => {
               if (oneOf(row.service_type, 'middle_manager', 'indexer')) {
-                return row.worker ? (row.currCapacityUsed || 0) / row.worker.capacity : null;
+                return row.worker
+                  ? (Number(row.currCapacityUsed) || 0) / Number(row.worker.capacity)
+                  : null;
               } else {
-                return row.max_size ? row.curr_size / row.max_size : null;
+                return row.max_size ? Number(row.curr_size) / Number(row.max_size) : null;
               }
             },
             Aggregated: row => {
               switch (row.row._pivotVal) {
                 case 'historical': {
-                  const originalHistoricals = row.subRows.map(r => r._original);
-                  const totalCurr = sum(originalHistoricals, s => s.curr_size);
-                  const totalMax = sum(originalHistoricals, s => s.max_size);
+                  const originalHistoricals: ServiceResultRow[] = row.subRows.map(r => r._original);
+                  const totalCurr = sum(originalHistoricals, s => Number(s.curr_size));
+                  const totalMax = sum(originalHistoricals, s => Number(s.max_size));
                   return fillIndicator(totalCurr / totalMax);
                 }
 
                 case 'indexer':
                 case 'middle_manager': {
-                  const originalMiddleManagers = row.subRows.map(r => r._original);
+                  const originalMiddleManagers: ServiceResultRow[] = row.subRows.map(
+                    r => r._original,
+                  );
                   const totalCurrCapacityUsed = sum(
                     originalMiddleManagers,
-                    s => s.currCapacityUsed || 0,
+                    s => Number(s.currCapacityUsed) || 0,
                   );
                   const totalWorkerCapacity = sum(
                     originalMiddleManagers,
@@ -506,7 +524,7 @@
               } else if (oneOf(row.service_type, 'coordinator', 'overlord')) {
                 return (row.is_leader || 0) === 1 ? 'leader' : '';
               } else {
-                return (row.segmentsToLoad || 0) + (row.segmentsToDrop || 0);
+                return (Number(row.segmentsToLoad) || 0) + (Number(row.segmentsToDrop) || 0);
               }
             },
             Cell: row => {
@@ -542,11 +560,11 @@
             },
             Aggregated: row => {
               if (row.row._pivotVal !== 'historical') return '';
-              const originals = row.subRows.map(r => r._original);
-              const segmentsToLoad = sum(originals, s => s.segmentsToLoad);
-              const segmentsToLoadSize = sum(originals, s => s.segmentsToLoadSize);
-              const segmentsToDrop = sum(originals, s => s.segmentsToDrop);
-              const segmentsToDropSize = sum(originals, s => s.segmentsToDropSize);
+              const originals: ServiceResultRow[] = row.subRows.map(r => r._original);
+              const segmentsToLoad = sum(originals, s => Number(s.segmentsToLoad) || 0);
+              const segmentsToLoadSize = sum(originals, s => Number(s.segmentsToLoadSize) || 0);
+              const segmentsToDrop = sum(originals, s => Number(s.segmentsToDrop) || 0);
+              const segmentsToDropSize = sum(originals, s => Number(s.segmentsToDropSize) || 0);
               return formatQueues(
                 segmentsToLoad,
                 segmentsToLoadSize,