You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by ab...@apache.org on 2022/09/28 06:47:00 UTC
[druid] branch 24.0.1 updated: [backport] Backport 24.0.1 web console issue fixes (#13146)
This is an automated email from the ASF dual-hosted git repository.
abhishek pushed a commit to branch 24.0.1
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/24.0.1 by this push:
new cffa3bd263 [backport] Backport 24.0.1 web console issue fixes (#13146)
cffa3bd263 is described below
commit cffa3bd263d77c48019fadc313db506ffa3ba24c
Author: Vadim Ogievetsky <va...@ogievetsky.com>
AuthorDate: Tue Sep 27 23:46:48 2022 -0700
[backport] Backport 24.0.1 web console issue fixes (#13146)
* fix number of expected functions (#13050)
* default to no compare (#13041)
* quote columns, datasources in auto complete if needed (#13060)
* Web console: better detection for arrays containing objects (#13077)
* better detection for arrays containing objects
* include boolean also
* link to error docs (#13094)
* Web console: correctly escape path based flatten specs (#13105)
* fix path generation
* do escape
* fix replace
* fix replace for good
* append to exisitng callout (#13130)
* better spec conversion with issues (#13136)
* bump version to 24.0.1
---
web-console/package-lock.json | 2 +-
web-console/package.json | 2 +-
web-console/script/create-sql-docs.js | 8 +-
web-console/src/ace-modes/dsql.js | 12 +-
web-console/src/bootstrap/ace.scss | 12 +
.../__snapshots__/header-bar.spec.tsx.snap | 2 +-
.../__snapshots__/table-cell.spec.tsx.snap | 8 +
.../src/components/table-cell/table-cell.spec.tsx | 7 +
.../src/components/table-cell/table-cell.tsx | 3 +-
.../table-filterable-cell.tsx | 6 +-
...coordinator-dynamic-config-dialog.spec.tsx.snap | 2 +-
.../overload-dynamic-config-dialog.spec.tsx.snap | 2 +-
.../__snapshots__/retention-dialog.spec.tsx.snap | 2 +-
.../druid-models/flatten-spec/flatten-spec.spec.ts | 24 +-
.../src/druid-models/flatten-spec/flatten-spec.tsx | 24 +-
.../ingestion-spec/ingestion-spec.spec.ts | 9 +
.../druid-models/ingestion-spec/ingestion-spec.tsx | 5 +-
.../workbench-query/workbench-query.spec.ts | 14 ++
.../workbench-query/workbench-query.ts | 9 +-
web-console/src/helpers/spec-conversion.spec.ts | 258 +++++++++++++++++++++
web-console/src/helpers/spec-conversion.ts | 103 ++++----
web-console/src/links.ts | 5 +-
web-console/src/react-table/react-table-inputs.tsx | 4 +-
web-console/src/utils/general.tsx | 10 +
.../src/views/load-data-view/info-messages.tsx | 48 +++-
.../src/views/load-data-view/load-data-view.tsx | 9 +-
.../views/query-view/query-input/query-input.tsx | 7 +-
.../__snapshots__/segments-view.spec.tsx.snap | 3 +-
.../src/views/segments-view/segments-view.tsx | 11 +-
.../execution-error-pane.spec.tsx.snap | 7 +-
.../execution-error-pane/execution-error-pane.tsx | 10 +-
.../flexible-query-input/flexible-query-input.tsx | 7 +-
.../result-table-pane/result-table-pane.tsx | 2 +-
web-console/unified-console.html | 2 +-
34 files changed, 517 insertions(+), 122 deletions(-)
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 6ac0e3a2cd..e8609e31f4 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "web-console",
- "version": "24.0.0",
+ "version": "24.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/web-console/package.json b/web-console/package.json
index 0158b3bd62..241cafca6b 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -1,6 +1,6 @@
{
"name": "web-console",
- "version": "24.0.0",
+ "version": "24.0.1",
"description": "A web console for Apache Druid",
"author": "Apache Druid Developers <de...@druid.apache.org>",
"license": "Apache-2.0",
diff --git a/web-console/script/create-sql-docs.js b/web-console/script/create-sql-docs.js
index 4258a0e99e..6af65006f8 100755
--- a/web-console/script/create-sql-docs.js
+++ b/web-console/script/create-sql-docs.js
@@ -23,7 +23,7 @@ const snarkdown = require('snarkdown');
const writefile = 'lib/sql-docs.js';
-const MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS = 158;
+const MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS = 162;
const MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES = 14;
function hasHtmlTags(str) {
@@ -90,15 +90,15 @@ const readDoc = async () => {
// Make sure there are enough functions found
const numFunction = Object.keys(functionDocs).length;
- if (numFunction < MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS) {
+ if (!(MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS <= numFunction)) {
throw new Error(
`Did not find enough function entries did the structure of '${readfile}' change? (found ${numFunction} but expected at least ${MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS})`,
);
}
// Make sure there are at least 10 data types for sanity
- const numDataTypes = dataTypeDocs.length;
- if (numDataTypes < MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES) {
+ const numDataTypes = Object.keys(dataTypeDocs).length;
+ if (!(MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES <= numDataTypes)) {
throw new Error(
`Did not find enough data type entries did the structure of '${readfile}' change? (found ${numDataTypes} but expected at least ${MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES})`,
);
diff --git a/web-console/src/ace-modes/dsql.js b/web-console/src/ace-modes/dsql.js
index c54970b17c..3c743b7c49 100644
--- a/web-console/src/ace-modes/dsql.js
+++ b/web-console/src/ace-modes/dsql.js
@@ -63,6 +63,10 @@ ace.define(
this.$rules = {
start: [
+ {
+ token: 'comment.issue',
+ regex: '--:ISSUE:.*$',
+ },
{
token: 'comment',
regex: '--.*$',
@@ -73,17 +77,13 @@ ace.define(
end: '\\*/',
},
{
- token: 'string', // " string
+ token: 'variable.column', // " quoted reference
regex: '".*?"',
},
{
- token: 'string', // ' string
+ token: 'string', // ' string literal
regex: "'.*?'",
},
- {
- token: 'string', // ` string (apache drill)
- regex: '`.*?`',
- },
{
token: 'constant.numeric', // float
regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b',
diff --git a/web-console/src/bootstrap/ace.scss b/web-console/src/bootstrap/ace.scss
index ebe74d4c41..e764348a8e 100644
--- a/web-console/src/bootstrap/ace.scss
+++ b/web-console/src/bootstrap/ace.scss
@@ -25,6 +25,18 @@
.ace-solarized-dark {
background-color: rgba($dark-gray1, 0.5);
+ // START: Custom code styles
+ .ace_variable.ace_column {
+ color: #2ceefb;
+ }
+
+ .ace_comment.ace_issue {
+ color: #cb3116;
+ text-decoration: underline;
+ text-decoration-style: wavy;
+ }
+ // END: Custom code styles
+
&.no-background {
background-color: transparent;
}
diff --git a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
index 08619b2427..66b902596c 100644
--- a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
+++ b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
@@ -326,7 +326,7 @@ exports[`HeaderBar matches snapshot 1`] = `
<Blueprint4.MenuItem
active={false}
disabled={false}
- href="https://druid.apache.org/docs/24.0.0"
+ href="https://druid.apache.org/docs/24.0.1"
icon="th"
multiline={false}
popoverProps={Object {}}
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 c019c38fa2..5ed0f31589 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`] = `
</div>
`;
+exports[`TableCell matches snapshot array mixed 1`] = `
+<div
+ class="table-cell plain"
+>
+ ["a",{"v":"b"},"c"]
+</div>
+`;
+
exports[`TableCell matches snapshot array short 1`] = `
<div
class="table-cell plain"
diff --git a/web-console/src/components/table-cell/table-cell.spec.tsx b/web-console/src/components/table-cell/table-cell.spec.tsx
index bd9681bd94..74648dccb0 100644
--- a/web-console/src/components/table-cell/table-cell.spec.tsx
+++ b/web-console/src/components/table-cell/table-cell.spec.tsx
@@ -64,6 +64,13 @@ describe('TableCell', () => {
expect(container.firstChild).toMatchSnapshot();
});
+ it('matches snapshot array mixed', () => {
+ const tableCell = <TableCell value={['a', { v: 'b' }, 'c']} />;
+
+ const { container } = render(tableCell);
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
it('matches snapshot object', () => {
const tableCell = <TableCell value={{ hello: 'world' }} />;
diff --git a/web-console/src/components/table-cell/table-cell.tsx b/web-console/src/components/table-cell/table-cell.tsx
index 3895a805a7..78f080b306 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 { isSimpleArray } 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()}
</div>
);
- } else if (Array.isArray(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/components/table-filterable-cell/table-filterable-cell.tsx b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
index d3387fd37e..14f568013f 100644
--- a/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
+++ b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
@@ -34,14 +34,14 @@ export interface TableFilterableCellProps {
value: string;
filters: Filter[];
onFiltersChange(filters: Filter[]): void;
- disableComparisons?: boolean;
+ enableComparisons?: boolean;
children?: ReactNode;
}
export const TableFilterableCell = React.memo(function TableFilterableCell(
props: TableFilterableCellProps,
) {
- const { field, value, children, filters, disableComparisons, onFiltersChange } = props;
+ const { field, value, children, filters, enableComparisons, onFiltersChange } = props;
return (
<Popover2
@@ -51,7 +51,7 @@ export const TableFilterableCell = React.memo(function TableFilterableCell(
content={() => (
<Menu>
<MenuDivider title="Filter" />
- {(disableComparisons ? FILTER_MODES_NO_COMPARISONS : FILTER_MODES).map((mode, i) => (
+ {(enableComparisons ? FILTER_MODES : FILTER_MODES_NO_COMPARISONS).map((mode, i) => (
<MenuItem
key={i}
icon={filterModeToIcon(mode)}
diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap b/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
index 532a60414d..c29ca57cd7 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
@@ -12,7 +12,7 @@ exports[`CoordinatorDynamicConfigDialog matches snapshot 1`] = `
Edit the coordinator dynamic configuration on the fly. For more information please refer to the
<Memo(ExternalLink)
- href="https://druid.apache.org/docs/24.0.0/configuration/index.html#dynamic-configuration"
+ href="https://druid.apache.org/docs/24.0.1/configuration/index.html#dynamic-configuration"
>
documentation
</Memo(ExternalLink)>
diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap b/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
index 14d240ec01..ac6e6eaf8a 100644
--- a/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
@@ -11,7 +11,7 @@ exports[`OverlordDynamicConfigDialog matches snapshot 1`] = `
Edit the overlord dynamic configuration on the fly. For more information please refer to the
<Memo(ExternalLink)
- href="https://druid.apache.org/docs/24.0.0/configuration/index.html#overlord-dynamic-configuration"
+ href="https://druid.apache.org/docs/24.0.1/configuration/index.html#overlord-dynamic-configuration"
>
documentation
</Memo(ExternalLink)>
diff --git a/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap b/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap
index c8b55ae594..668128421a 100644
--- a/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap
@@ -63,7 +63,7 @@ exports[`RetentionDialog matches snapshot 1`] = `
Druid uses rules to determine what data should be retained in the cluster. The rules are evaluated in order from top to bottom. For more information please refer to the
<a
- href="https://druid.apache.org/docs/24.0.0/operations/rule-configuration.html"
+ href="https://druid.apache.org/docs/24.0.1/operations/rule-configuration.html"
rel="noopener noreferrer"
target="_blank"
>
diff --git a/web-console/src/druid-models/flatten-spec/flatten-spec.spec.ts b/web-console/src/druid-models/flatten-spec/flatten-spec.spec.ts
index a35c185381..822dd48eba 100644
--- a/web-console/src/druid-models/flatten-spec/flatten-spec.spec.ts
+++ b/web-console/src/druid-models/flatten-spec/flatten-spec.spec.ts
@@ -22,7 +22,7 @@ describe('flatten-spec', () => {
describe('computeFlattenExprsForData', () => {
const data = [
{
- context: { host: 'cla', topic: 'moon', bonus: { foo: 'bar' } },
+ context: { host: 'cla', topic: 'moon', bonus: { 'fo.o': 'bar' } },
tags: ['a', 'b', 'c'],
messages: [
{ metric: 'request/time', value: 122 },
@@ -32,7 +32,7 @@ describe('flatten-spec', () => {
value: 5,
},
{
- context: { host: 'piv', popic: 'sun' },
+ context: { 'host': 'piv', '1pic': 'sun' },
tags: ['a', 'd'],
messages: [
{ metric: 'request/time', value: 44 },
@@ -41,7 +41,7 @@ describe('flatten-spec', () => {
value: 4,
},
{
- context: { host: 'imp', dopik: 'fun' },
+ context: { 'host': 'imp', "d\\o\npi'c'": 'fun' },
tags: ['x', 'y'],
messages: [
{ metric: 'request/time', value: 4 },
@@ -53,22 +53,12 @@ describe('flatten-spec', () => {
];
it('works for path, ignore-arrays', () => {
- expect(computeFlattenExprsForData(data, 'path', 'ignore-arrays')).toEqual([
- '$.context.bonus.foo',
- '$.context.dopik',
+ expect(computeFlattenExprsForData(data, 'ignore-arrays')).toEqual([
+ "$.context.bonus['fo.o']",
'$.context.host',
- '$.context.popic',
'$.context.topic',
- ]);
- });
-
- it('works for jq, ignore-arrays', () => {
- expect(computeFlattenExprsForData(data, 'jq', 'ignore-arrays')).toEqual([
- '.context.bonus.foo',
- '.context.dopik',
- '.context.host',
- '.context.popic',
- '.context.topic',
+ "$.context['1pic']",
+ "$.context['d\\\\o\npi\\'c\\'']",
]);
});
});
diff --git a/web-console/src/druid-models/flatten-spec/flatten-spec.tsx b/web-console/src/druid-models/flatten-spec/flatten-spec.tsx
index 3845a83916..5cf1d7c06c 100644
--- a/web-console/src/druid-models/flatten-spec/flatten-spec.tsx
+++ b/web-console/src/druid-models/flatten-spec/flatten-spec.tsx
@@ -61,18 +61,22 @@ export const FLATTEN_FIELD_FIELDS: Field<FlattenField>[] = [
},
];
-export type ExprType = 'path' | 'jq';
export type ArrayHandling = 'ignore-arrays' | 'include-arrays';
+function escapePathKey(pathKey: string): string {
+ return /^[a-z]\w*$/i.test(pathKey)
+ ? `.${pathKey}`
+ : `['${pathKey.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}']`;
+}
+
export function computeFlattenPathsForData(
data: Record<string, any>[],
- exprType: ExprType,
arrayHandling: ArrayHandling,
): FlattenField[] {
- return computeFlattenExprsForData(data, exprType, arrayHandling).map(expr => {
+ return computeFlattenExprsForData(data, arrayHandling).map(expr => {
return {
- name: expr.replace(/^\$?\./, ''),
- type: exprType,
+ name: expr.replace(/^\$\./, '').replace(/['\]]/g, '').replace(/\[/g, '.'),
+ type: 'path',
expr,
};
});
@@ -80,7 +84,6 @@ export function computeFlattenPathsForData(
export function computeFlattenExprsForData(
data: Record<string, any>[],
- exprType: ExprType,
arrayHandling: ArrayHandling,
includeTopLevel = false,
): string[] {
@@ -91,12 +94,7 @@ export function computeFlattenExprsForData(
for (const datumKey of datumKeys) {
const datumValue = datum[datumKey];
if (includeTopLevel || isNested(datumValue)) {
- addPath(
- seenPaths,
- exprType === 'path' ? `$.${datumKey}` : `.${datumKey}`,
- datumValue,
- arrayHandling,
- );
+ addPath(seenPaths, `$${escapePathKey(datumKey)}`, datumValue, arrayHandling);
}
}
}
@@ -114,7 +112,7 @@ function addPath(
if (!Array.isArray(value)) {
const valueKeys = Object.keys(value);
for (const valueKey of valueKeys) {
- addPath(paths, `${path}.${valueKey}`, value[valueKey], arrayHandling);
+ addPath(paths, `${path}${escapePathKey(valueKey)}`, value[valueKey], arrayHandling);
}
} else if (arrayHandling === 'include-arrays') {
for (let i = 0; i < value.length; i++) {
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 a846d29a81..3f4be422c7 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,15 @@ 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', () => {
+ expect(guessColumnTypeFromInput([{ type: 'Dogs' }, { type: 'JavaScript' }], false)).toEqual(
+ 'COMPLEX<json>',
+ );
});
it('works for strange json', () => {
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 4ce36c655d..f7492d3550 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,
+ isSimpleArray,
oneOf,
parseCsvLine,
typeIs,
@@ -2309,7 +2310,7 @@ export function guessIsArrayFromHeaderAndRows(
headerAndRows: SampleHeaderAndRows,
column: string,
): boolean {
- return headerAndRows.rows.some(r => Array.isArray(r.input?.[column]));
+ return headerAndRows.rows.some(r => isSimpleArray(r.input?.[column]));
}
export function guessColumnTypeFromInput(
@@ -2322,7 +2323,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 => isSimpleArray(v))) return 'string';
// If we see any JSON objects in the input assume COMPLEX<json>
if (definedValues.some(v => v && typeof v === 'object')) return 'COMPLEX<json>';
diff --git a/web-console/src/druid-models/workbench-query/workbench-query.spec.ts b/web-console/src/druid-models/workbench-query/workbench-query.spec.ts
index 008947994f..8f6f2581e1 100644
--- a/web-console/src/druid-models/workbench-query/workbench-query.spec.ts
+++ b/web-console/src/druid-models/workbench-query/workbench-query.spec.ts
@@ -423,6 +423,20 @@ describe('WorkbenchQuery', () => {
sqlPrefixLines: 0,
});
});
+
+ it('works with sql with ISSUE comment', () => {
+ const sql = sane`
+ SELECT *
+ --:ISSUE: There is something wrong with this query.
+ FROM wikipedia
+ `;
+
+ const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
+
+ expect(() => workbenchQuery.getApiQuery(makeQueryId)).toThrow(
+ `This query contains an ISSUE comment: There is something wrong with this query. (Please resolve the issue in the comment, delete the ISSUE comment and re-run the query.)`,
+ );
+ });
});
describe('#getIngestDatasource', () => {
diff --git a/web-console/src/druid-models/workbench-query/workbench-query.ts b/web-console/src/druid-models/workbench-query/workbench-query.ts
index 4200874aaf..d30ea7d4cf 100644
--- a/web-console/src/druid-models/workbench-query/workbench-query.ts
+++ b/web-console/src/druid-models/workbench-query/workbench-query.ts
@@ -576,10 +576,15 @@ export class WorkbenchQuery {
apiQuery.query = queryPrepend + apiQuery.query + queryAppend;
}
- const m = /(--:context\s.+)(?:\n|$)/.exec(apiQuery.query);
+ const m = /--:ISSUE:(.+)(?:\n|$)/.exec(apiQuery.query);
if (m) {
throw new Error(
- `This query contains a context comment '${m[1]}'. Context comments have been deprecated. Please rewrite the context comment as a context parameter. The context parameter editor is located in the "Engine" dropdown.`,
+ `This query contains an ISSUE comment: ${m[1]
+ .trim()
+ .replace(
+ /\.$/,
+ '',
+ )}. (Please resolve the issue in the comment, delete the ISSUE comment and re-run the query.)`,
);
}
diff --git a/web-console/src/helpers/spec-conversion.spec.ts b/web-console/src/helpers/spec-conversion.spec.ts
index 9b1e421e93..43e7d31fac 100644
--- a/web-console/src/helpers/spec-conversion.spec.ts
+++ b/web-console/src/helpers/spec-conversion.spec.ts
@@ -449,4 +449,262 @@ describe('spec conversion', () => {
finalizeAggregations: false,
});
});
+
+ it('converts with issue when there is a __time transform', () => {
+ const converted = convertSpecToSql({
+ type: 'index_parallel',
+ spec: {
+ ioConfig: {
+ type: 'index_parallel',
+ inputSource: {
+ type: 'http',
+ uris: ['https://druid.apache.org/data/wikipedia.json.gz'],
+ },
+ inputFormat: {
+ type: 'json',
+ },
+ },
+ dataSchema: {
+ granularitySpec: {
+ segmentGranularity: 'hour',
+ queryGranularity: 'none',
+ rollup: false,
+ },
+ dataSource: 'wikipedia',
+ transformSpec: {
+ transforms: [{ name: '__time', expression: '_some_time_parse_expression_' }],
+ },
+ timestampSpec: {
+ column: 'timestamp',
+ format: 'auto',
+ },
+ dimensionsSpec: {
+ dimensions: [
+ 'isRobot',
+ 'channel',
+ 'flags',
+ 'isUnpatrolled',
+ 'page',
+ 'diffUrl',
+ {
+ type: 'long',
+ name: 'added',
+ },
+ 'comment',
+ {
+ type: 'long',
+ name: 'commentLength',
+ },
+ 'isNew',
+ 'isMinor',
+ {
+ type: 'long',
+ name: 'delta',
+ },
+ 'isAnonymous',
+ 'user',
+ {
+ type: 'long',
+ name: 'deltaBucket',
+ },
+ {
+ type: 'long',
+ name: 'deleted',
+ },
+ 'namespace',
+ 'cityName',
+ 'countryName',
+ 'regionIsoCode',
+ 'metroCode',
+ 'countryIsoCode',
+ 'regionName',
+ ],
+ },
+ },
+ tuningConfig: {
+ type: 'index_parallel',
+ partitionsSpec: {
+ type: 'single_dim',
+ partitionDimension: 'isRobot',
+ targetRowsPerSegment: 150000,
+ },
+ forceGuaranteedRollup: true,
+ maxNumConcurrentSubTasks: 4,
+ maxParseExceptions: 3,
+ },
+ },
+ });
+
+ expect(converted.queryString).toEqual(sane`
+ -- This SQL query was auto generated from an ingestion spec
+ REPLACE INTO wikipedia OVERWRITE ALL
+ WITH source AS (SELECT * FROM TABLE(
+ EXTERN(
+ '{"type":"http","uris":["https://druid.apache.org/data/wikipedia.json.gz"]}',
+ '{"type":"json"}',
+ '[{"name":"isRobot","type":"string"},{"name":"channel","type":"string"},{"name":"flags","type":"string"},{"name":"isUnpatrolled","type":"string"},{"name":"page","type":"string"},{"name":"diffUrl","type":"string"},{"name":"added","type":"long"},{"name":"comment","type":"string"},{"name":"commentLength","type":"long"},{"name":"isNew","type":"string"},{"name":"isMinor","type":"string"},{"name":"delta","type":"long"},{"name":"isAnonymous","type":"string"},{"name":"user","type":"str [...]
+ )
+ ))
+ SELECT
+ --:ISSUE: The spec contained transforms that could not be automatically converted.
+ REWRITE_[_some_time_parse_expression_]_TO_SQL AS __time, --:ISSUE: Transform for __time could not be converted
+ "isRobot",
+ "channel",
+ "flags",
+ "isUnpatrolled",
+ "page",
+ "diffUrl",
+ "added",
+ "comment",
+ "commentLength",
+ "isNew",
+ "isMinor",
+ "delta",
+ "isAnonymous",
+ "user",
+ "deltaBucket",
+ "deleted",
+ "namespace",
+ "cityName",
+ "countryName",
+ "regionIsoCode",
+ "metroCode",
+ "countryIsoCode",
+ "regionName"
+ FROM source
+ PARTITIONED BY HOUR
+ CLUSTERED BY "isRobot"
+ `);
+ });
+
+ it('converts with issue when there is a dimension transform and strange filter', () => {
+ const converted = convertSpecToSql({
+ type: 'index_parallel',
+ spec: {
+ ioConfig: {
+ type: 'index_parallel',
+ inputSource: {
+ type: 'http',
+ uris: ['https://druid.apache.org/data/wikipedia.json.gz'],
+ },
+ inputFormat: {
+ type: 'json',
+ },
+ },
+ dataSchema: {
+ granularitySpec: {
+ segmentGranularity: 'hour',
+ queryGranularity: 'none',
+ rollup: false,
+ },
+ dataSource: 'wikipedia',
+ transformSpec: {
+ transforms: [{ name: 'comment', expression: '_some_expression_' }],
+ filter: {
+ type: 'strange',
+ },
+ },
+ timestampSpec: {
+ column: 'timestamp',
+ format: 'auto',
+ },
+ dimensionsSpec: {
+ dimensions: [
+ 'isRobot',
+ 'channel',
+ 'flags',
+ 'isUnpatrolled',
+ 'page',
+ 'diffUrl',
+ {
+ type: 'long',
+ name: 'added',
+ },
+ 'comment',
+ {
+ type: 'long',
+ name: 'commentLength',
+ },
+ 'isNew',
+ 'isMinor',
+ {
+ type: 'long',
+ name: 'delta',
+ },
+ 'isAnonymous',
+ 'user',
+ {
+ type: 'long',
+ name: 'deltaBucket',
+ },
+ {
+ type: 'long',
+ name: 'deleted',
+ },
+ 'namespace',
+ 'cityName',
+ 'countryName',
+ 'regionIsoCode',
+ 'metroCode',
+ 'countryIsoCode',
+ 'regionName',
+ ],
+ },
+ },
+ tuningConfig: {
+ type: 'index_parallel',
+ partitionsSpec: {
+ type: 'single_dim',
+ partitionDimension: 'isRobot',
+ targetRowsPerSegment: 150000,
+ },
+ forceGuaranteedRollup: true,
+ maxNumConcurrentSubTasks: 4,
+ maxParseExceptions: 3,
+ },
+ },
+ });
+
+ expect(converted.queryString).toEqual(sane`
+ -- This SQL query was auto generated from an ingestion spec
+ REPLACE INTO wikipedia OVERWRITE ALL
+ WITH source AS (SELECT * FROM TABLE(
+ EXTERN(
+ '{"type":"http","uris":["https://druid.apache.org/data/wikipedia.json.gz"]}',
+ '{"type":"json"}',
+ '[{"name":"timestamp","type":"string"},{"name":"isRobot","type":"string"},{"name":"channel","type":"string"},{"name":"flags","type":"string"},{"name":"isUnpatrolled","type":"string"},{"name":"page","type":"string"},{"name":"diffUrl","type":"string"},{"name":"added","type":"long"},{"name":"comment","type":"string"},{"name":"commentLength","type":"long"},{"name":"isNew","type":"string"},{"name":"isMinor","type":"string"},{"name":"delta","type":"long"},{"name":"isAnonymous","type" [...]
+ )
+ ))
+ SELECT
+ --:ISSUE: The spec contained transforms that could not be automatically converted.
+ CASE WHEN CAST("timestamp" AS BIGINT) > 0 THEN MILLIS_TO_TIMESTAMP(CAST("timestamp" AS BIGINT)) ELSE TIME_PARSE("timestamp") END AS __time,
+ "isRobot",
+ "channel",
+ "flags",
+ "isUnpatrolled",
+ "page",
+ "diffUrl",
+ "added",
+ REWRITE_[_some_expression_]_TO_SQL AS "comment", --:ISSUE: Transform for dimension could not be converted
+ "commentLength",
+ "isNew",
+ "isMinor",
+ "delta",
+ "isAnonymous",
+ "user",
+ "deltaBucket",
+ "deleted",
+ "namespace",
+ "cityName",
+ "countryName",
+ "regionIsoCode",
+ "metroCode",
+ "countryIsoCode",
+ "regionName"
+ FROM source
+ WHERE REWRITE_[{"type":"strange"}]_TO_SQL --:ISSUE: The spec contained a filter that could not be automatically converted, please convert it manually
+ PARTITIONED BY HOUR
+ CLUSTERED BY "isRobot"
+ `);
+ });
});
diff --git a/web-console/src/helpers/spec-conversion.ts b/web-console/src/helpers/spec-conversion.ts
index 498c908595..9eedcce9f7 100644
--- a/web-console/src/helpers/spec-conversion.ts
+++ b/web-console/src/helpers/spec-conversion.ts
@@ -102,45 +102,55 @@ export function convertSpecToSql(spec: any): QueryWithContext {
);
}
+ const transforms: Transform[] = deepGet(spec, 'spec.dataSchema.transformSpec.transforms') || [];
+ if (!Array.isArray(transforms)) {
+ throw new Error(`spec.dataSchema.transformSpec.transforms is not an array`);
+ }
+
let timeExpression: string;
const column = timestampSpec.column || 'timestamp';
const columnRef = SqlRef.column(column);
const format = timestampSpec.format || 'auto';
- switch (format) {
- case 'auto':
- columns.unshift({ name: column, type: 'string' });
- timeExpression = `CASE WHEN CAST(${columnRef} AS BIGINT) > 0 THEN MILLIS_TO_TIMESTAMP(CAST(${columnRef} AS BIGINT)) ELSE TIME_PARSE(${columnRef}) END`;
- break;
-
- case 'iso':
- columns.unshift({ name: column, type: 'string' });
- timeExpression = `TIME_PARSE(${columnRef})`;
- break;
-
- case 'posix':
- columns.unshift({ name: column, type: 'long' });
- timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} * 1000)`;
- break;
-
- case 'millis':
- columns.unshift({ name: column, type: 'long' });
- timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef})`;
- break;
-
- case 'micro':
- columns.unshift({ name: column, type: 'long' });
- timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} / 1000)`;
- break;
-
- case 'nano':
- columns.unshift({ name: column, type: 'long' });
- timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} / 1000000)`;
- break;
-
- default:
- columns.unshift({ name: column, type: 'string' });
- timeExpression = `TIME_PARSE(${columnRef}, ${SqlLiteral.create(format)})`;
- break;
+ const timeTransform = transforms.find(t => t.name === '__time');
+ if (timeTransform) {
+ timeExpression = `REWRITE_[${timeTransform.expression}]_TO_SQL`;
+ } else {
+ switch (format) {
+ case 'auto':
+ columns.unshift({ name: column, type: 'string' });
+ timeExpression = `CASE WHEN CAST(${columnRef} AS BIGINT) > 0 THEN MILLIS_TO_TIMESTAMP(CAST(${columnRef} AS BIGINT)) ELSE TIME_PARSE(${columnRef}) END`;
+ break;
+
+ case 'iso':
+ columns.unshift({ name: column, type: 'string' });
+ timeExpression = `TIME_PARSE(${columnRef})`;
+ break;
+
+ case 'posix':
+ columns.unshift({ name: column, type: 'long' });
+ timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} * 1000)`;
+ break;
+
+ case 'millis':
+ columns.unshift({ name: column, type: 'long' });
+ timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef})`;
+ break;
+
+ case 'micro':
+ columns.unshift({ name: column, type: 'long' });
+ timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} / 1000)`;
+ break;
+
+ case 'nano':
+ columns.unshift({ name: column, type: 'long' });
+ timeExpression = `MILLIS_TO_TIMESTAMP(${columnRef} / 1000000)`;
+ break;
+
+ default:
+ columns.unshift({ name: column, type: 'string' });
+ timeExpression = `TIME_PARSE(${columnRef}, ${SqlLiteral.create(format)})`;
+ break;
+ }
}
if (timestampSpec.missingValue) {
@@ -238,19 +248,24 @@ export function convertSpecToSql(spec: any): QueryWithContext {
lines.push(`SELECT`);
- const transforms: Transform[] = deepGet(spec, 'spec.dataSchema.transformSpec.transforms') || [];
- if (!Array.isArray(transforms))
- throw new Error(`spec.dataSchema.transformSpec.transforms is not an array`);
if (transforms.length) {
- lines.push(` -- The spec contained transforms that could not be automatically converted.`);
+ lines.push(
+ ` --:ISSUE: The spec contained transforms that could not be automatically converted.`,
+ );
}
- const dimensionExpressions = [` ${timeExpression} AS __time,`].concat(
+ const dimensionExpressions = [
+ ` ${timeExpression} AS __time,${
+ timeTransform ? ` --:ISSUE: Transform for __time could not be converted` : ''
+ }`,
+ ].concat(
dimensions.flatMap((dimension: DimensionSpec) => {
const dimensionName = dimension.name;
const relevantTransform = transforms.find(t => t.name === dimensionName);
- return ` ${SqlRef.columnWithQuotes(dimensionName)},${
- relevantTransform ? ` -- Relevant transform: ${JSONBig.stringify(relevantTransform)}` : ''
+ return ` ${
+ relevantTransform ? `REWRITE_[${relevantTransform.expression}]_TO_SQL AS ` : ''
+ }${SqlRef.columnWithQuotes(dimensionName)},${
+ relevantTransform ? ` --:ISSUE: Transform for dimension could not be converted` : ''
}`;
}),
);
@@ -275,9 +290,9 @@ export function convertSpecToSql(spec: any): QueryWithContext {
lines.push(`WHERE ${convertFilter(filter)}`);
} catch {
lines.push(
- `-- The spec contained a filter that could not be automatically converted: ${JSONBig.stringify(
+ `WHERE REWRITE_[${JSONBig.stringify(
filter,
- )}`,
+ )}]_TO_SQL --:ISSUE: The spec contained a filter that could not be automatically converted, please convert it manually`,
);
}
}
diff --git a/web-console/src/links.ts b/web-console/src/links.ts
index d083a517c1..096b2f3eed 100644
--- a/web-console/src/links.ts
+++ b/web-console/src/links.ts
@@ -19,7 +19,7 @@
import hasOwnProp from 'has-own-prop';
// This is set to the latest available version and should be updated to the next version before release
-const DRUID_DOCS_VERSION = '24.0.0';
+const DRUID_DOCS_VERSION = '24.0.1';
function fillVersion(str: string): string {
return str.replace(/\{\{VERSION}}/g, DRUID_DOCS_VERSION);
@@ -63,6 +63,7 @@ export type LinkNames =
| 'DOCS_SQL'
| 'DOCS_RUNE'
| 'DOCS_API'
+ | 'DOCS_MSQ_ERROR'
| 'COMMUNITY'
| 'SLACK'
| 'USER_GROUP'
@@ -82,6 +83,8 @@ export function getLink(linkName: LinkNames): string {
return `${links.docsHref}/querying/querying.html`;
case 'DOCS_API':
return `${links.docsHref}/operations/api-reference.html`;
+ case 'DOCS_MSQ_ERROR':
+ return `${links.docsHref}/multi-stage-query/concepts.html#error-codes`;
case 'COMMUNITY':
return links.communityHref;
case 'SLACK':
diff --git a/web-console/src/react-table/react-table-inputs.tsx b/web-console/src/react-table/react-table-inputs.tsx
index d7e02c3dc3..7dbb69b18f 100644
--- a/web-console/src/react-table/react-table-inputs.tsx
+++ b/web-console/src/react-table/react-table-inputs.tsx
@@ -43,7 +43,7 @@ export function GenericFilterInput({ column, filter, onChange, key }: FilterRend
const [menuOpen, setMenuOpen] = useState(false);
const [focused, setFocused] = useState(false);
- const disableComparisons = String(column.headerClassName).includes('disable-comparisons');
+ const enableComparisons = String(column.headerClassName).includes('enable-comparisons');
const { mode, needle } = (filter ? parseFilterModeAndNeedle(filter, true) : undefined) || {
mode: '~',
@@ -64,7 +64,7 @@ export function GenericFilterInput({ column, filter, onChange, key }: FilterRend
onInteraction={setMenuOpen}
content={
<Menu>
- {(disableComparisons ? FILTER_MODES_NO_COMPARISON : FILTER_MODES).map((m, i) => (
+ {(enableComparisons ? FILTER_MODES : FILTER_MODES_NO_COMPARISON).map((m, i) => (
<MenuItem
key={i}
icon={filterModeToIcon(m)}
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index 772e3c646f..5cf8ca0ce5 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 isSimpleArray(a: any): a is (string | number | boolean)[] {
+ return (
+ Array.isArray(a) &&
+ a.every(x => {
+ const t = typeof x;
+ return t === 'string' || t === 'number' || t === 'boolean';
+ })
+ );
+}
+
export function wait(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, ms);
diff --git a/web-console/src/views/load-data-view/info-messages.tsx b/web-console/src/views/load-data-view/info-messages.tsx
index 7d7235bc3d..2d151ea995 100644
--- a/web-console/src/views/load-data-view/info-messages.tsx
+++ b/web-console/src/views/load-data-view/info-messages.tsx
@@ -16,12 +16,13 @@
* limitations under the License.
*/
-import { Callout, Code, FormGroup } from '@blueprintjs/core';
+import { Button, Callout, Code, FormGroup, Intent } from '@blueprintjs/core';
import React from 'react';
import { ExternalLink, LearnMore } from '../../components';
import { DimensionMode, getIngestionDocLink, IngestionSpec } from '../../druid-models';
import { getLink } from '../../links';
+import { deepGet, deepSet } from '../../utils';
export interface ConnectMessageProps {
inlineMode: boolean;
@@ -216,3 +217,48 @@ export const SpecMessage = React.memo(function SpecMessage() {
</FormGroup>
);
});
+
+export interface AppendToExistingIssueProps {
+ spec: Partial<IngestionSpec>;
+ onChangeSpec(newSpec: Partial<IngestionSpec>): void;
+}
+
+export const AppendToExistingIssue = React.memo(function AppendToExistingIssue(
+ props: AppendToExistingIssueProps,
+) {
+ const { spec, onChangeSpec } = props;
+
+ const partitionsSpecType = deepGet(spec, 'spec.tuningConfig.partitionsSpec.type');
+ if (
+ partitionsSpecType === 'dynamic' ||
+ deepGet(spec, 'spec.ioConfig.appendToExisting') !== true
+ ) {
+ return null;
+ }
+
+ const dynamicPartitionSpec = {
+ type: 'dynamic',
+ maxRowsPerSegment:
+ deepGet(spec, 'spec.tuningConfig.partitionsSpec.maxRowsPerSegment') ||
+ deepGet(spec, 'spec.tuningConfig.partitionsSpec.targetRowsPerSegment'),
+ };
+
+ return (
+ <FormGroup>
+ <Callout intent={Intent.DANGER}>
+ <p>
+ Only <Code>dynamic</Code> partitioning supports <Code>appendToExisting: true</Code>. You
+ have currently selected <Code>{partitionsSpecType}</Code> partitioning.
+ </p>
+ <Button
+ intent={Intent.SUCCESS}
+ onClick={() =>
+ onChangeSpec(deepSet(spec, 'spec.tuningConfig.partitionsSpec', dynamicPartitionSpec))
+ }
+ >
+ Change to <Code>dynamic</Code> partitioning
+ </Button>
+ </Callout>
+ </FormGroup>
+ );
+});
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx b/web-console/src/views/load-data-view/load-data-view.tsx
index 7afc13aa75..e4322e34e5 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -168,6 +168,7 @@ import { ExamplePicker } from './example-picker/example-picker';
import { FilterTable, filterTableSelectedColumnName } from './filter-table/filter-table';
import { FormEditor } from './form-editor/form-editor';
import {
+ AppendToExistingIssue,
ConnectMessage,
FilterMessage,
ParserMessage,
@@ -1490,7 +1491,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
if (canFlatten && !flattenFields.length && parserQueryState.data) {
suggestedFlattenFields = computeFlattenPathsForData(
filterMap(parserQueryState.data.rows, r => r.input),
- 'path',
'ignore-arrays',
);
}
@@ -3003,6 +3003,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<div className="control">
<PartitionMessage />
{nonsensicalSingleDimPartitioningMessage}
+ <AppendToExistingIssue spec={spec} onChangeSpec={this.updateSpec} />
</div>
{this.renderNextBar({
disabled: invalidPartitionConfig(spec),
@@ -3096,8 +3097,8 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
label: 'Append to existing',
type: 'boolean',
defaultValue: false,
- defined: spec =>
- deepGet(spec, 'spec.tuningConfig.partitionsSpec.type') === 'dynamic',
+ // appendToExisting can only be set on 'dynamic' portioning.
+ // We chose to show it always and instead have a specific message, separate from this form, to notify the user of the issue.
info: (
<>
Creates segments as additional shards of the latest version, effectively
@@ -3166,6 +3167,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
</div>
<div className="control">
<PublishMessage />
+ <AppendToExistingIssue spec={spec} onChangeSpec={this.updateSpec} />
</div>
{this.renderNextBar({})}
</>
@@ -3234,6 +3236,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
>{`There is an issue with the spec: ${issueWithSpec}`}</Callout>
</FormGroup>
)}
+ <AppendToExistingIssue spec={spec} onChangeSpec={this.updateSpec} />
</div>
<div className="next-bar">
{!isEmptyIngestionSpec(spec) && (
diff --git a/web-console/src/views/query-view/query-input/query-input.tsx b/web-console/src/views/query-view/query-input/query-input.tsx
index d4fd3fe6ae..9f85c9de08 100644
--- a/web-console/src/views/query-view/query-input/query-input.tsx
+++ b/web-console/src/views/query-view/query-input/query-input.tsx
@@ -20,6 +20,7 @@ import { ResizeEntry } from '@blueprintjs/core';
import { ResizeSensor2 } from '@blueprintjs/popover2';
import type { Ace } from 'ace-builds';
import ace from 'ace-builds';
+import { SqlRef, SqlTableRef } from 'druid-query-toolkit';
import escape from 'lodash.escape';
import React from 'react';
import AceEditor from 'react-ace';
@@ -150,7 +151,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
) {
const completions = ([] as any[]).concat(
uniq(columnMetadata.map(d => d.TABLE_SCHEMA)).map(v => ({
- value: v,
+ value: SqlTableRef.create(v).toString(),
score: 10,
meta: 'schema',
})),
@@ -159,7 +160,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
.filter(d => (currentSchema ? d.TABLE_SCHEMA === currentSchema : true))
.map(d => d.TABLE_NAME),
).map(v => ({
- value: v,
+ value: SqlTableRef.create(v).toString(),
score: 49,
meta: 'datasource',
})),
@@ -172,7 +173,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
)
.map(d => d.COLUMN_NAME),
).map(v => ({
- value: v,
+ value: SqlRef.column(v).toString(),
score: 50,
meta: 'column',
})),
diff --git a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
index 6ac125bfdf..5d273a2869 100755
--- a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
+++ b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
@@ -169,6 +169,7 @@ exports[`SegmentsView matches snapshot 1`] = `
"accessor": "start",
"defaultSortDesc": true,
"filterable": true,
+ "headerClassName": "enable-comparisons",
"show": true,
"sortable": true,
"width": 160,
@@ -179,6 +180,7 @@ exports[`SegmentsView matches snapshot 1`] = `
"accessor": "end",
"defaultSortDesc": true,
"filterable": true,
+ "headerClassName": "enable-comparisons",
"show": true,
"sortable": true,
"width": 160,
@@ -206,7 +208,6 @@ exports[`SegmentsView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Shard type",
"accessor": [Function],
- "headerClassName": "disable-comparisons",
"id": "shard_type",
"show": true,
"sortable": false,
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index b0327182be..85d0279622 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -485,7 +485,7 @@ END AS "time_span"`,
});
}
- private renderFilterableCell(field: string, disableComparisons = false) {
+ private renderFilterableCell(field: string, enableComparisons = false) {
const { segmentFilter } = this.state;
return (row: { value: any }) => (
@@ -494,7 +494,7 @@ END AS "time_span"`,
value={row.value}
filters={segmentFilter}
onFiltersChange={filters => this.setState({ segmentFilter: filters })}
- disableComparisons={disableComparisons}
+ enableComparisons={enableComparisons}
>
{row.value}
</TableFilterableCell>
@@ -582,21 +582,23 @@ END AS "time_span"`,
Header: 'Start',
show: visibleColumns.shown('Start'),
accessor: 'start',
+ headerClassName: 'enable-comparisons',
width: 160,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
- Cell: this.renderFilterableCell('start'),
+ Cell: this.renderFilterableCell('start', true),
},
{
Header: 'End',
show: visibleColumns.shown('End'),
accessor: 'end',
+ headerClassName: 'enable-comparisons',
width: 160,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
- Cell: this.renderFilterableCell('end'),
+ Cell: this.renderFilterableCell('end', true),
},
{
Header: 'Version',
@@ -623,7 +625,6 @@ END AS "time_span"`,
id: 'shard_type',
width: 100,
sortable: false,
- headerClassName: 'disable-comparisons',
accessor: d => {
let v: any;
try {
diff --git a/web-console/src/views/workbench-view/execution-error-pane/__snapshots__/execution-error-pane.spec.tsx.snap b/web-console/src/views/workbench-view/execution-error-pane/__snapshots__/execution-error-pane.spec.tsx.snap
index cf8bccd618..cf7ed02ec7 100644
--- a/web-console/src/views/workbench-view/execution-error-pane/__snapshots__/execution-error-pane.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/execution-error-pane/__snapshots__/execution-error-pane.spec.tsx.snap
@@ -8,7 +8,12 @@ exports[`ExecutionErrorPane matches snapshot 1`] = `
<p
className="error-message-text"
>
- TooManyWarnings:
+ <Memo(ExternalLink)
+ href="https://druid.apache.org/docs/24.0.1/multi-stage-query/concepts.html#error-codes"
+ >
+ TooManyWarnings
+ </Memo(ExternalLink)>
+ :
Too many warnings of type CannotParseExternalData generated (max = 10)
</p>
<div>
diff --git a/web-console/src/views/workbench-view/execution-error-pane/execution-error-pane.tsx b/web-console/src/views/workbench-view/execution-error-pane/execution-error-pane.tsx
index da7e3173b3..b0368534cc 100644
--- a/web-console/src/views/workbench-view/execution-error-pane/execution-error-pane.tsx
+++ b/web-console/src/views/workbench-view/execution-error-pane/execution-error-pane.tsx
@@ -20,9 +20,10 @@ import { Callout } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
-import { ClickToCopy } from '../../../components';
+import { ClickToCopy, ExternalLink } from '../../../components';
import { ShowValueDialog } from '../../../dialogs/show-value-dialog/show-value-dialog';
import { Execution } from '../../../druid-models';
+import { getLink } from '../../../links';
import { downloadQueryDetailArchive } from '../../../utils';
import './execution-error-pane.scss';
@@ -43,7 +44,12 @@ export const ExecutionErrorPane = React.memo(function ExecutionErrorPane(
return (
<Callout className="execution-error-pane" icon={IconNames.ERROR}>
<p className="error-message-text">
- {error.errorCode && <>{`${error.errorCode}: `}</>}
+ {error.errorCode && (
+ <>
+ <ExternalLink href={getLink('DOCS_MSQ_ERROR')}>{error.errorCode}</ExternalLink>
+ {': '}
+ </>
+ )}
{error.errorMessage || (exceptionStackTrace || '').split('\n')[0]}
{exceptionStackTrace && (
<>
diff --git a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
index c83865cdb1..1559e7981c 100644
--- a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
+++ b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
@@ -21,6 +21,7 @@ import { ResizeSensor2 } from '@blueprintjs/popover2';
import type { Ace } from 'ace-builds';
import ace from 'ace-builds';
import classNames from 'classnames';
+import { SqlRef, SqlTableRef } from 'druid-query-toolkit';
import escape from 'lodash.escape';
import React from 'react';
import AceEditor from 'react-ace';
@@ -163,7 +164,7 @@ export class FlexibleQueryInput extends React.PureComponent<
) {
const completions = ([] as any[]).concat(
uniq(columnMetadata.map(d => d.TABLE_SCHEMA)).map(v => ({
- value: v,
+ value: SqlTableRef.create(v).toString(),
score: 10,
meta: 'schema',
})),
@@ -172,7 +173,7 @@ export class FlexibleQueryInput extends React.PureComponent<
.filter(d => (currentSchema ? d.TABLE_SCHEMA === currentSchema : true))
.map(d => d.TABLE_NAME),
).map(v => ({
- value: v,
+ value: SqlTableRef.create(v).toString(),
score: 49,
meta: 'datasource',
})),
@@ -185,7 +186,7 @@ export class FlexibleQueryInput extends React.PureComponent<
)
.map(d => d.COLUMN_NAME),
).map(v => ({
- value: v,
+ value: SqlRef.column(v).toString(),
score: 50,
meta: 'column',
})),
diff --git a/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx b/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
index e4b78b5af7..ba8b11001a 100644
--- a/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
+++ b/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
@@ -82,7 +82,7 @@ function jsonValue(ex: SqlExpression, path: string): SqlExpression {
}
function getJsonPaths(jsons: Record<string, any>[]): string[] {
- return ['$.'].concat(computeFlattenExprsForData(jsons, 'path', 'include-arrays', true));
+ return ['$.'].concat(computeFlattenExprsForData(jsons, 'include-arrays', true));
}
function isComparable(x: unknown): boolean {
diff --git a/web-console/unified-console.html b/web-console/unified-console.html
index 19fa9d10bf..ab0f717392 100644
--- a/web-console/unified-console.html
+++ b/web-console/unified-console.html
@@ -71,6 +71,6 @@
};
</script>
<script src="console-config.js"></script>
- <script src="public/web-console-24.0.0.js"></script>
+ <script src="public/web-console-24.0.1.js"></script>
</body>
</html>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org