From 1b71bc9bea41e0fb372b27495ca995a8fe21524c Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Mon, 12 Sep 2022 11:57:37 -0700 Subject: [PATCH 1/2] better detection for arrays containing objects --- .../table-cell/__snapshots__/table-cell.spec.tsx.snap | 8 ++++++++ .../src/components/table-cell/table-cell.spec.tsx | 7 +++++++ web-console/src/components/table-cell/table-cell.tsx | 3 ++- .../druid-models/ingestion-spec/ingestion-spec.spec.ts | 6 ++++++ .../src/druid-models/ingestion-spec/ingestion-spec.tsx | 5 +++-- web-console/src/utils/general.tsx | 10 ++++++++++ 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap b/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap index c019c38fa226..5ed0f315896d 100644 --- a/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap +++ b/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap @@ -49,6 +49,14 @@ exports[`TableCell matches snapshot array long 1`] = ` `; +exports[`TableCell matches snapshot array mixed 1`] = ` +
+ ["a",{"v":"b"},"c"] +
+`; + exports[`TableCell matches snapshot array short 1`] = `
{ expect(container.firstChild).toMatchSnapshot(); }); + it('matches snapshot array mixed', () => { + const tableCell = ; + + const { container } = render(tableCell); + expect(container.firstChild).toMatchSnapshot(); + }); + it('matches snapshot object', () => { const tableCell = ; diff --git a/web-console/src/components/table-cell/table-cell.tsx b/web-console/src/components/table-cell/table-cell.tsx index 3895a805a702..76a3bfba7825 100644 --- a/web-console/src/components/table-cell/table-cell.tsx +++ b/web-console/src/components/table-cell/table-cell.tsx @@ -21,6 +21,7 @@ import * as JSONBig from 'json-bigint-native'; import React, { useState } from 'react'; import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog'; +import { isStringOrNumberArray } from '../../utils'; import { ActionIcon } from '../action-icon/action-icon'; import './table-cell.scss'; @@ -97,7 +98,7 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) { {isNaN(dateValue) ? 'Unusable date' : value.toISOString()}
); - } else if (Array.isArray(value)) { + } else if (isStringOrNumberArray(value)) { return renderTruncated(`[${value.join(', ')}]`); } else if (typeof value === 'object') { return renderTruncated(JSONBig.stringify(value)); diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts b/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts index a846d29a8188..b6c7aa91d268 100644 --- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts +++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts @@ -727,6 +727,12 @@ describe('spec utils', () => { expect(guessColumnTypeFromInput([1, [2], 3], false)).toEqual('string'); }); + it('works for complex arrays', () => { + expect(guessColumnTypeFromInput([{ type: 'Dogs' }, { type: 'JavaScript' }], false)).toEqual( + 'COMPLEX', + ); + }); + it('works for strange json', () => { expect(guessColumnTypeFromInput([1, { hello: 'world' }, 3], false)).toEqual('COMPLEX'); }); diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx index d7bf50916347..4821e59f3e41 100644 --- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx +++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx @@ -32,6 +32,7 @@ import { EMPTY_ARRAY, EMPTY_OBJECT, filterMap, + isStringOrNumberArray, oneOf, parseCsvLine, typeIs, @@ -2330,7 +2331,7 @@ export function guessIsArrayFromHeaderAndRows( headerAndRows: SampleHeaderAndRows, column: string, ): boolean { - return headerAndRows.rows.some(r => Array.isArray(r.input?.[column])); + return headerAndRows.rows.some(r => isStringOrNumberArray(r.input?.[column])); } export function guessColumnTypeFromInput( @@ -2343,7 +2344,7 @@ export function guessColumnTypeFromInput( if (!definedValues.length) return 'string'; // If we see any arrays in the input this is a multi-value dimension that must be a string - if (definedValues.some(v => Array.isArray(v))) return 'string'; + if (definedValues.some(v => isStringOrNumberArray(v))) return 'string'; // If we see any JSON objects in the input assume COMPLEX if (definedValues.some(v => v && typeof v === 'object')) return 'COMPLEX'; diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx index 772e3c646f28..e39db7991cd1 100644 --- a/web-console/src/utils/general.tsx +++ b/web-console/src/utils/general.tsx @@ -40,6 +40,16 @@ export function nonEmptyArray(a: any): a is unknown[] { return Array.isArray(a) && Boolean(a.length); } +export function isStringOrNumberArray(a: any): a is (string | number)[] { + return ( + Array.isArray(a) && + a.every(x => { + const t = typeof x; + return t === 'string' || t === 'number'; + }) + ); +} + export function wait(ms: number): Promise { return new Promise(resolve => { setTimeout(resolve, ms); From 5497e0c77640f76f85a9f4ae29923dacb08d55e9 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Mon, 12 Sep 2022 12:34:01 -0700 Subject: [PATCH 2/2] include boolean also --- web-console/src/components/table-cell/table-cell.tsx | 4 ++-- .../src/druid-models/ingestion-spec/ingestion-spec.spec.ts | 3 +++ .../src/druid-models/ingestion-spec/ingestion-spec.tsx | 6 +++--- web-console/src/utils/general.tsx | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/web-console/src/components/table-cell/table-cell.tsx b/web-console/src/components/table-cell/table-cell.tsx index 76a3bfba7825..78f080b306d6 100644 --- a/web-console/src/components/table-cell/table-cell.tsx +++ b/web-console/src/components/table-cell/table-cell.tsx @@ -21,7 +21,7 @@ import * as JSONBig from 'json-bigint-native'; import React, { useState } from 'react'; import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog'; -import { isStringOrNumberArray } from '../../utils'; +import { isSimpleArray } from '../../utils'; import { ActionIcon } from '../action-icon/action-icon'; import './table-cell.scss'; @@ -98,7 +98,7 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) { {isNaN(dateValue) ? 'Unusable date' : value.toISOString()} ); - } else if (isStringOrNumberArray(value)) { + } else if (isSimpleArray(value)) { return renderTruncated(`[${value.join(', ')}]`); } else if (typeof value === 'object') { return renderTruncated(JSONBig.stringify(value)); diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts b/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts index b6c7aa91d268..3f4be422c79f 100644 --- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts +++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.spec.ts @@ -725,6 +725,9 @@ describe('spec utils', () => { it('works for multi-value', () => { expect(guessColumnTypeFromInput(['a', ['b'], 'c'], false)).toEqual('string'); expect(guessColumnTypeFromInput([1, [2], 3], false)).toEqual('string'); + expect(guessColumnTypeFromInput([true, [true, 7, false], false, 'x'], false)).toEqual( + 'string', + ); }); it('works for complex arrays', () => { diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx index 4821e59f3e41..fe9b8074c165 100644 --- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx +++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx @@ -32,7 +32,7 @@ import { EMPTY_ARRAY, EMPTY_OBJECT, filterMap, - isStringOrNumberArray, + isSimpleArray, oneOf, parseCsvLine, typeIs, @@ -2331,7 +2331,7 @@ export function guessIsArrayFromHeaderAndRows( headerAndRows: SampleHeaderAndRows, column: string, ): boolean { - return headerAndRows.rows.some(r => isStringOrNumberArray(r.input?.[column])); + return headerAndRows.rows.some(r => isSimpleArray(r.input?.[column])); } export function guessColumnTypeFromInput( @@ -2344,7 +2344,7 @@ export function guessColumnTypeFromInput( if (!definedValues.length) return 'string'; // If we see any arrays in the input this is a multi-value dimension that must be a string - if (definedValues.some(v => isStringOrNumberArray(v))) return 'string'; + if (definedValues.some(v => isSimpleArray(v))) return 'string'; // If we see any JSON objects in the input assume COMPLEX if (definedValues.some(v => v && typeof v === 'object')) return 'COMPLEX'; diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx index e39db7991cd1..5cf8ca0ce5fa 100644 --- a/web-console/src/utils/general.tsx +++ b/web-console/src/utils/general.tsx @@ -40,12 +40,12 @@ export function nonEmptyArray(a: any): a is unknown[] { return Array.isArray(a) && Boolean(a.length); } -export function isStringOrNumberArray(a: any): a is (string | number)[] { +export function isSimpleArray(a: any): a is (string | number | boolean)[] { return ( Array.isArray(a) && a.every(x => { const t = typeof x; - return t === 'string' || t === 'number'; + return t === 'string' || t === 'number' || t === 'boolean'; }) ); }