This is an automated email from the ASF dual-hosted git repository.

vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 1873fca6c7 Web console: update DQT to latest version and fix bigint 
crash (#14318)
1873fca6c7 is described below

commit 1873fca6c73f816db45482eda3e59abc6fd4e8f5
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Wed May 24 17:40:45 2023 -0700

    Web console: update DQT to latest version and fix bigint crash (#14318)
    
    * update dqt
    
    * don't crash on bigint values
    
    * better submit experiance
    
    * bump to an even version
---
 licenses.yaml                                      |  2 +-
 web-console/package-lock.json                      | 14 ++++-----
 web-console/package.json                           |  2 +-
 .../src/components/json-collapse/json-collapse.tsx |  2 +-
 .../src/dialogs/spec-dialog/spec-dialog.tsx        | 34 ++++++++++++++--------
 web-console/src/utils/general.spec.ts              | 16 ++++++++++
 web-console/src/utils/general.tsx                  | 23 +++++++++++++++
 web-console/src/utils/object-change.spec.ts        |  2 +-
 .../load-data-view/filter-table/filter-table.tsx   |  8 ++---
 .../parse-time-table/parse-time-table.tsx          |  5 ++--
 .../load-data-view/schema-table/schema-table.tsx   |  5 ++--
 .../transform-table/transform-table.tsx            |  5 ++--
 .../execution-submit-dialog.tsx                    | 22 ++++++++------
 13 files changed, 98 insertions(+), 42 deletions(-)

diff --git a/licenses.yaml b/licenses.yaml
index 608633a01a..62f1991f76 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -5814,7 +5814,7 @@ license_category: binary
 module: web-console
 license_name: Apache License version 2.0
 copyright: Imply Data
-version: 0.18.3
+version: 0.18.12
 
 ---
 
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 119224bff1..10ba50cf2f 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -22,7 +22,7 @@
         "d3-axis": "^2.1.0",
         "d3-scale": "^3.3.0",
         "d3-selection": "^2.0.0",
-        "druid-query-toolkit": "^0.18.3",
+        "druid-query-toolkit": "^0.18.12",
         "file-saver": "^2.0.2",
         "follow-redirects": "^1.14.7",
         "fontsource-open-sans": "^3.0.9",
@@ -8211,9 +8211,9 @@
       }
     },
     "node_modules/druid-query-toolkit": {
-      "version": "0.18.3",
-      "resolved": 
"https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.18.3.tgz";,
-      "integrity": 
"sha512-Za2U2NsFyun5HXeWnLCICnTFzZp4aC17aSOjgVbQgEWZNMPht51U4paE3SVhPDObkWDjDUYAqVv+mO+ZyMx9Og==",
+      "version": "0.18.12",
+      "resolved": 
"https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.18.12.tgz";,
+      "integrity": 
"sha512-wDcZUW8vhiJXARC44EFFwUeZW6lawXWv++bxHIUKaxq3M5byBuWPKjEDTCdPEHprxmR2sxaTpsPw4A6KiRmBog==",
       "dependencies": {
         "tslib": "^2.3.1"
       },
@@ -32625,9 +32625,9 @@
       }
     },
     "druid-query-toolkit": {
-      "version": "0.18.3",
-      "resolved": 
"https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.18.3.tgz";,
-      "integrity": 
"sha512-Za2U2NsFyun5HXeWnLCICnTFzZp4aC17aSOjgVbQgEWZNMPht51U4paE3SVhPDObkWDjDUYAqVv+mO+ZyMx9Og==",
+      "version": "0.18.12",
+      "resolved": 
"https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.18.12.tgz";,
+      "integrity": 
"sha512-wDcZUW8vhiJXARC44EFFwUeZW6lawXWv++bxHIUKaxq3M5byBuWPKjEDTCdPEHprxmR2sxaTpsPw4A6KiRmBog==",
       "requires": {
         "tslib": "^2.3.1"
       }
diff --git a/web-console/package.json b/web-console/package.json
index 68b83bfe99..9dba6ca819 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -76,7 +76,7 @@
     "d3-axis": "^2.1.0",
     "d3-scale": "^3.3.0",
     "d3-selection": "^2.0.0",
-    "druid-query-toolkit": "^0.18.3",
+    "druid-query-toolkit": "^0.18.12",
     "file-saver": "^2.0.2",
     "follow-redirects": "^1.14.7",
     "fontsource-open-sans": "^3.0.9",
diff --git a/web-console/src/components/json-collapse/json-collapse.tsx 
b/web-console/src/components/json-collapse/json-collapse.tsx
index 3a92db4db6..5a0869a5bd 100644
--- a/web-console/src/components/json-collapse/json-collapse.tsx
+++ b/web-console/src/components/json-collapse/json-collapse.tsx
@@ -31,7 +31,7 @@ export const JsonCollapse = React.memo(function 
JsonCollapse(props: JsonCollapse
   const { stringValue, buttonText } = props;
   const [isOpen, setIsOpen] = useState(false);
 
-  const prettyValue = JSONBig.stringify(JSON.parse(stringValue), undefined, 2);
+  const prettyValue = JSONBig.stringify(JSONBig.parse(stringValue), undefined, 
2);
   return (
     <div className="json-collapse">
       <div className="collapse-buttons">
diff --git a/web-console/src/dialogs/spec-dialog/spec-dialog.tsx 
b/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
index c4bc5a3fa9..f38cd0d8df 100644
--- a/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
+++ b/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
@@ -21,13 +21,14 @@ import * as JSONBig from 'json-bigint-native';
 import React, { useState } from 'react';
 import AceEditor from 'react-ace';
 
-import { validJson } from '../../utils';
+import { AppToaster } from '../../singletons';
+import { offsetToRowColumn } from '../../utils';
 
 import './spec-dialog.scss';
 
 export interface SpecDialogProps {
-  onSubmit: (spec: JSON) => void | Promise<void>;
-  onClose: () => void;
+  onSubmit(spec: JSON): void | Promise<void>;
+  onClose(): void;
   title: string;
   initSpec?: any;
 }
@@ -38,9 +39,23 @@ export const SpecDialog = React.memo(function 
SpecDialog(props: SpecDialogProps)
     initSpec ? JSONBig.stringify(initSpec, undefined, 2) : '',
   );
 
-  function postSpec(): void {
-    if (!validJson(spec)) return;
-    void onSubmit(JSON.parse(spec));
+  function handleSubmit(): void {
+    let parsed: any;
+    try {
+      parsed = JSONBig.parse(spec);
+    } catch (e) {
+      const rowColumn = typeof e.at === 'number' ? offsetToRowColumn(spec, 
e.at) : undefined;
+      AppToaster.show({
+        intent: Intent.DANGER,
+        message: `Could not parse JSON: ${e.message}${
+          rowColumn ? ` (at line ${rowColumn.row + 1}, column 
${rowColumn.column + 1})` : ''
+        }`,
+        timeout: 5000,
+      });
+      return;
+    }
+
+    void onSubmit(parsed);
     onClose();
   }
 
@@ -78,12 +93,7 @@ export const SpecDialog = React.memo(function 
SpecDialog(props: SpecDialogProps)
       <div className={Classes.DIALOG_FOOTER}>
         <div className={Classes.DIALOG_FOOTER_ACTIONS}>
           <Button text="Close" onClick={onClose} />
-          <Button
-            text="Submit"
-            intent={Intent.PRIMARY}
-            onClick={postSpec}
-            disabled={!validJson(spec)}
-          />
+          <Button text="Submit" intent={Intent.PRIMARY} onClick={handleSubmit} 
disabled={!spec} />
         </div>
       </div>
     </Dialog>
diff --git a/web-console/src/utils/general.spec.ts 
b/web-console/src/utils/general.spec.ts
index fa11e1c871..7b380ae7cf 100644
--- a/web-console/src/utils/general.spec.ts
+++ b/web-console/src/utils/general.spec.ts
@@ -28,6 +28,7 @@ import {
   moveElement,
   moveToIndex,
   objectHash,
+  offsetToRowColumn,
   parseCsvLine,
   swapElements,
 } from './general';
@@ -171,4 +172,19 @@ describe('general', () => {
       expect(objectHash({ hello: 'world1' })).toEqual('cc14ad13');
     });
   });
+
+  describe('offsetToRowColumn', () => {
+    it('works', () => {
+      expect(offsetToRowColumn('Hello\nThis is a test\nstring.', 
-6)).toBeUndefined();
+      expect(offsetToRowColumn('Hello\nThis is a test\nstring.', 
666)).toBeUndefined();
+      expect(offsetToRowColumn('Hello\nThis is a test\nstring.', 3)).toEqual({
+        column: 3,
+        row: 0,
+      });
+      expect(offsetToRowColumn('Hello\nThis is a test\nstring.', 24)).toEqual({
+        column: 3,
+        row: 2,
+      });
+    });
+  });
 });
diff --git a/web-console/src/utils/general.tsx 
b/web-console/src/utils/general.tsx
index 3b29dceafd..f7c6823203 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -465,3 +465,26 @@ export function tickIcon(checked: boolean): IconName {
 export function generate8HexId(): string {
   return (Math.random() * 1e10).toString(16).replace('.', '').slice(0, 8);
 }
+
+export function offsetToRowColumn(
+  str: string,
+  offset: number,
+): { row: number; column: number } | undefined {
+  // Ensure offset is within the string length
+  if (offset < 0 || offset > str.length) return;
+
+  const lines = str.split('\n');
+  for (let row = 0; row < lines.length; row++) {
+    const line = lines[row];
+    if (offset < line.length) {
+      return {
+        row,
+        column: offset,
+      };
+    }
+
+    offset -= line.length + 1;
+  }
+
+  return;
+}
diff --git a/web-console/src/utils/object-change.spec.ts 
b/web-console/src/utils/object-change.spec.ts
index e7c28b2eff..bdfecca7ec 100644
--- a/web-console/src/utils/object-change.spec.ts
+++ b/web-console/src/utils/object-change.spec.ts
@@ -138,7 +138,7 @@ describe('object-change', () => {
     });
 
     it('works with arrays', () => {
-      expect(JSON.parse(JSONBig.stringify(deepDelete(thing, 
'hello.wow.0')))).toEqual({
+      expect(JSONBig.parse(JSONBig.stringify(deepDelete(thing, 
'hello.wow.0')))).toEqual({
         hello: {
           moon: 1,
           wow: [
diff --git a/web-console/src/views/load-data-view/filter-table/filter-table.tsx 
b/web-console/src/views/load-data-view/filter-table/filter-table.tsx
index 368072762f..3f0323e189 100644
--- a/web-console/src/views/load-data-view/filter-table/filter-table.tsx
+++ b/web-console/src/views/load-data-view/filter-table/filter-table.tsx
@@ -23,7 +23,7 @@ import ReactTable from 'react-table';
 
 import { TableCell } from '../../../components';
 import type { DruidFilter } from '../../../druid-models';
-import { getFilterDimension } from '../../../druid-models';
+import { getFilterDimension, TIME_COLUMN } from '../../../druid-models';
 import {
   DEFAULT_TABLE_CLASS_NAME,
   STANDARD_TABLE_PAGE_SIZE,
@@ -67,7 +67,7 @@ export const FilterTable = React.memo(function 
FilterTable(props: FilterTablePro
       showPagination={sampleResponse.data.length > STANDARD_TABLE_PAGE_SIZE}
       columns={filterMap(getHeaderNamesFromSampleResponse(sampleResponse), 
(columnName, i) => {
         if (!caseInsensitiveContains(columnName, columnFilter)) return;
-        const timestamp = columnName === '__time';
+        const isTimestamp = columnName === TIME_COLUMN;
         const filterIndex = dimensionFilters.findIndex(f => 
getFilterDimension(f) === columnName);
         const filter = dimensionFilters[filterIndex];
 
@@ -84,7 +84,7 @@ export const FilterTable = React.memo(function 
FilterTable(props: FilterTablePro
                   onFilterSelect(filter, filterIndex);
                 } else {
                   onFilterSelect(
-                    timestamp
+                    isTimestamp
                       ? { type: 'interval', dimension: columnName, intervals: 
[] }
                       : { type: 'selector', dimension: columnName, value: '' },
                     -1,
@@ -102,7 +102,7 @@ export const FilterTable = React.memo(function 
FilterTable(props: FilterTablePro
           accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] 
: null),
           width: 140,
           Cell: function FilterTableCell(row: RowRenderProps) {
-            return <TableCell value={timestamp ? new Date(row.value) : 
row.value} />;
+            return <TableCell value={isTimestamp ? new Date(Number(row.value)) 
: row.value} />;
           },
         };
       })}
diff --git 
a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx 
b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
index 61cc4aeecc..2d7a32bad1 100644
--- a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
+++ b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
@@ -27,6 +27,7 @@ import {
   getTimestampDetailFromSpec,
   getTimestampSpecColumnFromSpec,
   possibleDruidFormatForValues,
+  TIME_COLUMN,
 } from '../../../druid-models';
 import {
   DEFAULT_TABLE_CLASS_NAME,
@@ -86,7 +87,7 @@ export const ParseTimeTable = React.memo(function 
ParseTimeTable(props: ParseTim
       pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
       showPagination={sampleResponse.data.length > STANDARD_TABLE_PAGE_SIZE}
       columns={filterMap(getHeaderNamesFromSampleResponse(sampleResponse), 
(columnName, i) => {
-        const isTimestamp = columnName === '__time';
+        const isTimestamp = columnName === TIME_COLUMN;
         if (!isTimestamp && !caseInsensitiveContains(columnName, 
columnFilter)) return;
         const used = timestampSpecColumn === columnName;
         const possibleFormat = isTimestamp
@@ -134,7 +135,7 @@ export const ParseTimeTable = React.memo(function 
ParseTimeTable(props: ParseTim
             if (row.original.unparseable) {
               return <TableCellUnparseable timestamp={isTimestamp} />;
             }
-            return <TableCell value={isTimestamp ? new Date(row.value) : 
row.value} />;
+            return <TableCell value={isTimestamp ? new Date(Number(row.value)) 
: row.value} />;
           },
           width: isTimestamp ? 200 : 140,
           resizable: !isTimestamp,
diff --git a/web-console/src/views/load-data-view/schema-table/schema-table.tsx 
b/web-console/src/views/load-data-view/schema-table/schema-table.tsx
index b6ba0e58d3..b1245d6b39 100644
--- a/web-console/src/views/load-data-view/schema-table/schema-table.tsx
+++ b/web-console/src/views/load-data-view/schema-table/schema-table.tsx
@@ -28,6 +28,7 @@ import {
   getDimensionSpecType,
   getMetricSpecName,
   inflateDimensionSpec,
+  TIME_COLUMN,
 } from '../../../druid-models';
 import {
   DEFAULT_TABLE_CLASS_NAME,
@@ -109,7 +110,7 @@ export const SchemaTable = React.memo(function 
SchemaTable(props: SchemaTablePro
             },
           };
         } else {
-          const isTimestamp = columnName === '__time';
+          const isTimestamp = columnName === TIME_COLUMN;
           const dimensionSpecIndex = dimensions
             ? dimensions.findIndex(d => getDimensionSpecName(d) === columnName)
             : -1;
@@ -151,7 +152,7 @@ export const SchemaTable = React.memo(function 
SchemaTable(props: SchemaTablePro
             width: isTimestamp ? 200 : 140,
             accessor: (row: SampleEntry) => (row.parsed ? 
row.parsed[columnName] : null),
             Cell: function SchemaTableCell(row: RowRenderProps) {
-              return <TableCell value={isTimestamp ? new Date(row.value) : 
row.value} />;
+              return <TableCell value={isTimestamp ? new 
Date(Number(row.value)) : row.value} />;
             },
           };
         }
diff --git 
a/web-console/src/views/load-data-view/transform-table/transform-table.tsx 
b/web-console/src/views/load-data-view/transform-table/transform-table.tsx
index 55c4e091fb..02d253d3a5 100644
--- a/web-console/src/views/load-data-view/transform-table/transform-table.tsx
+++ b/web-console/src/views/load-data-view/transform-table/transform-table.tsx
@@ -23,6 +23,7 @@ import ReactTable from 'react-table';
 
 import { TableCell } from '../../../components';
 import type { Transform } from '../../../druid-models';
+import { TIME_COLUMN } from '../../../druid-models';
 import {
   DEFAULT_TABLE_CLASS_NAME,
   STANDARD_TABLE_PAGE_SIZE,
@@ -79,7 +80,7 @@ export const TransformTable = React.memo(function 
TransformTable(props: Transfor
       showPagination={sampleResponse.data.length > STANDARD_TABLE_PAGE_SIZE}
       columns={filterMap(getHeaderNamesFromSampleResponse(sampleResponse), 
(columnName, i) => {
         if (!caseInsensitiveContains(columnName, columnFilter)) return;
-        const timestamp = columnName === '__time';
+        const isTimestamp = columnName === TIME_COLUMN;
         const transformIndex = transforms.findIndex(f => f.name === 
columnName);
         if (transformIndex === -1 && transformedColumnsOnly) return;
         const transform = transforms[transformIndex];
@@ -119,7 +120,7 @@ export const TransformTable = React.memo(function 
TransformTable(props: Transfor
           accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] 
: null),
           width: 140,
           Cell: function TransformTableCell(row: RowRenderProps) {
-            return <TableCell value={timestamp ? new Date(row.value) : 
row.value} />;
+            return <TableCell value={isTimestamp ? new Date(Number(row.value)) 
: row.value} />;
           },
         };
       })}
diff --git 
a/web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx
 
b/web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx
index e6cc36ca64..e197e2d87c 100644
--- 
a/web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx
+++ 
b/web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx
@@ -17,18 +17,19 @@
  */
 
 import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
+import * as JSONBig from 'json-bigint-native';
 import React, { useState } from 'react';
 import AceEditor from 'react-ace';
 
 import { Execution } from '../../../druid-models';
 import { AppToaster } from '../../../singletons';
-import { validJson } from '../../../utils';
+import { offsetToRowColumn } from '../../../utils';
 
 import './execution-submit-dialog.scss';
 
 export interface ExecutionSubmitDialogProps {
-  onSubmit: (execution: Execution) => void;
-  onClose: () => void;
+  onSubmit(execution: Execution): void;
+  onClose(): void;
 }
 
 export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
@@ -37,15 +38,18 @@ export const ExecutionSubmitDialog = React.memo(function 
ExecutionSubmitDialog(
   const { onClose, onSubmit } = props;
   const [archive, setArchive] = useState('');
 
-  function submitProfile(): void {
-    if (!validJson(archive)) return;
+  function handleSubmit(): void {
     let parsed: any;
     try {
-      parsed = JSON.parse(archive);
+      parsed = JSONBig.parse(archive);
     } catch (e) {
+      const rowColumn = typeof e.at === 'number' ? offsetToRowColumn(archive, 
e.at) : undefined;
       AppToaster.show({
         intent: Intent.DANGER,
-        message: `Could not parse JSON: ${e.message}`,
+        message: `Could not parse JSON: ${e.message}${
+          rowColumn ? ` (at line ${rowColumn.row + 1}, column 
${rowColumn.column + 1})` : ''
+        }`,
+        timeout: 5000,
       });
       return;
     }
@@ -129,8 +133,8 @@ export const ExecutionSubmitDialog = React.memo(function 
ExecutionSubmitDialog(
           <Button
             text="Submit"
             intent={Intent.PRIMARY}
-            onClick={submitProfile}
-            disabled={!validJson(archive)}
+            onClick={handleSubmit}
+            disabled={!archive}
           />
         </div>
       </div>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to