diff --git a/licenses.yaml b/licenses.yaml
index ea993bb905e..bd03e20d67d 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -5094,7 +5094,7 @@ license_category: binary
module: web-console
license_name: Apache License version 2.0
copyright: Imply Data
-version: 0.22.11
+version: 0.22.13
---
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 816f734948e..9d835e3da04 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -14,7 +14,7 @@
"@blueprintjs/datetime2": "^0.9.35",
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.9",
- "@druid-toolkit/query": "^0.22.11",
+ "@druid-toolkit/query": "^0.22.13",
"@druid-toolkit/visuals-core": "^0.3.3",
"@druid-toolkit/visuals-react": "^0.3.3",
"ace-builds": "~1.4.14",
@@ -1004,9 +1004,9 @@
}
},
"node_modules/@druid-toolkit/query": {
- "version": "0.22.11",
- "resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.11.tgz",
- "integrity": "sha512-VVEn/tsEr9fb+8eKc+nu3/YH7l+LZ1vd0D32UDo66GLS3cI+EKOCM7VYC8lTvB1tAS+98w/EzfbdlRPlkSeOoQ==",
+ "version": "0.22.13",
+ "resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.13.tgz",
+ "integrity": "sha512-p0Cmmbk55vLaYs2WWcUr09qDRU2IrkXOxGgUG+wS6Uuq/ALBqSmUDlbMSxB3vJjMvegiwgJ8+n7VfVpO0t/bJg==",
"dependencies": {
"tslib": "^2.5.2"
}
@@ -19146,9 +19146,9 @@
"dev": true
},
"@druid-toolkit/query": {
- "version": "0.22.11",
- "resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.11.tgz",
- "integrity": "sha512-VVEn/tsEr9fb+8eKc+nu3/YH7l+LZ1vd0D32UDo66GLS3cI+EKOCM7VYC8lTvB1tAS+98w/EzfbdlRPlkSeOoQ==",
+ "version": "0.22.13",
+ "resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.13.tgz",
+ "integrity": "sha512-p0Cmmbk55vLaYs2WWcUr09qDRU2IrkXOxGgUG+wS6Uuq/ALBqSmUDlbMSxB3vJjMvegiwgJ8+n7VfVpO0t/bJg==",
"requires": {
"tslib": "^2.5.2"
}
diff --git a/web-console/package.json b/web-console/package.json
index 8cd0985f644..3e713fc2f68 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -68,7 +68,7 @@
"@blueprintjs/datetime2": "^0.9.35",
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.9",
- "@druid-toolkit/query": "^0.22.11",
+ "@druid-toolkit/query": "^0.22.13",
"@druid-toolkit/visuals-core": "^0.3.3",
"@druid-toolkit/visuals-react": "^0.3.3",
"ace-builds": "~1.4.14",
diff --git a/web-console/src/components/record-table-pane/record-table-pane.tsx b/web-console/src/components/record-table-pane/record-table-pane.tsx
index a2849ed0d5c..bfd9b644de9 100644
--- a/web-console/src/components/record-table-pane/record-table-pane.tsx
+++ b/web-console/src/components/record-table-pane/record-table-pane.tsx
@@ -104,7 +104,7 @@ export const RecordTablePane = React.memo(function RecordTablePane(props: Record
const finalPage =
hasMoreResults && Math.floor(queryResult.rows.length / pagination.pageSize) === pagination.page; // on the last page
- const numericColumnBraces = getNumericColumnBraces(queryResult, pagination);
+ const numericColumnBraces = getNumericColumnBraces(queryResult, undefined, pagination);
return (
{finalPage ? (
diff --git a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
index e36ae511271..b8816fd493a 100644
--- a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
+++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
@@ -38,8 +38,8 @@ export interface AsyncActionDialogProps {
className?: string;
icon?: IconName;
intent?: Intent;
- successText: string;
- failText: string;
+ successText: ReactNode;
+ failText: ReactNode;
warningChecks?: ReactNode[];
children?: ReactNode;
}
diff --git a/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx b/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
index dba85268d00..f5e2ca8add6 100644
--- a/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
+++ b/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
@@ -66,7 +66,12 @@ export const KillDatasourceDialog = function KillDatasourceDialog(
return resp.data;
}}
confirmButtonText="Permanently delete unused segments"
- successText="Kill task was issued. Unused segments in datasource will be deleted"
+ successText={
+ <>
+ Kill task was issued. Unused segments in datasource
{datasource} will
+ be deleted
+ >
+ }
failText="Failed submit kill task"
intent={Intent.DANGER}
onClose={onClose}
diff --git a/web-console/src/druid-models/execution/execution.ts b/web-console/src/druid-models/execution/execution.ts
index 0cf8d5d0ed3..799de6f9c51 100644
--- a/web-console/src/druid-models/execution/execution.ts
+++ b/web-console/src/druid-models/execution/execution.ts
@@ -440,7 +440,10 @@ export class Execution {
value.queryContext = queryContext;
const parsedQuery = parseSqlQuery(sqlQuery);
if (value.result && (parsedQuery || queryContext)) {
- value.result = value.result.attachQuery({ context: queryContext }, parsedQuery);
+ value.result = value.result.attachQuery(
+ { ...this.nativeQuery, context: queryContext },
+ parsedQuery,
+ );
}
return new Execution(value);
@@ -463,7 +466,10 @@ export class Execution {
public changeResult(result: QueryResult): Execution {
return new Execution({
...this.valueOf(),
- result: result.attachQuery({}, this.sqlQuery ? parseSqlQuery(this.sqlQuery) : undefined),
+ result: result.attachQuery(
+ this.nativeQuery,
+ this.sqlQuery ? parseSqlQuery(this.sqlQuery) : undefined,
+ ),
});
}
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 d59cdbbe92e..e912e61ed57 100644
--- a/web-console/src/druid-models/workbench-query/workbench-query.ts
+++ b/web-console/src/druid-models/workbench-query/workbench-query.ts
@@ -18,6 +18,7 @@
import type {
QueryParameter,
+ QueryPayload,
SqlClusteredByClause,
SqlExpression,
SqlPartitionedByClause,
@@ -446,7 +447,7 @@ export class WorkbenchQuery {
public getApiQuery(makeQueryId: () => string = uuidv4): {
engine: DruidEngine;
- query: Record
;
+ query: QueryPayload;
prefixLines: number;
cancelQueryId?: string;
} {
@@ -478,7 +479,7 @@ export class WorkbenchQuery {
};
}
- let apiQuery: Record = {};
+ let apiQuery: QueryPayload;
if (this.isJsonLike()) {
try {
apiQuery = Hjson.parse(queryString);
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index b4537a63e08..1ea872b68f2 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -338,6 +338,22 @@ export function pluralIfNeeded(n: NumberLike, singular: string, plural?: string)
// ----------------------------
+export function partition(xs: T[], predicate: (x: T, i: number) => boolean): [T[], T[]] {
+ const match: T[] = [];
+ const nonMatch: T[] = [];
+
+ for (let i = 0; i < xs.length; i++) {
+ const x = xs[i];
+ if (predicate(x, i)) {
+ match.push(x);
+ } else {
+ nonMatch.push(x);
+ }
+ }
+
+ return [match, nonMatch];
+}
+
export function filterMap(xs: readonly T[], f: (x: T, i: number) => Q | undefined): Q[] {
return xs.map(f).filter((x: Q | undefined) => typeof x !== 'undefined') as Q[];
}
diff --git a/web-console/src/utils/table-helpers.ts b/web-console/src/utils/table-helpers.ts
index 7eedd1acaab..a04635c61c5 100644
--- a/web-console/src/utils/table-helpers.ts
+++ b/web-console/src/utils/table-helpers.ts
@@ -32,9 +32,16 @@ export function changePage(pagination: Pagination, page: number): Pagination {
return deepSet(pagination, 'page', page);
}
+export interface ColumnHint {
+ displayName?: string;
+ group?: string;
+ formatter?: (x: any) => string;
+}
+
export function getNumericColumnBraces(
queryResult: QueryResult,
- pagination?: Pagination,
+ columnHints: Map | undefined,
+ pagination: Pagination | undefined,
): Record {
let rows = queryResult.rows;
@@ -47,8 +54,9 @@ export function getNumericColumnBraces(
if (rows.length) {
queryResult.header.forEach((column, i) => {
if (!oneOf(column.nativeType, 'LONG', 'FLOAT', 'DOUBLE')) return;
+ const formatter = columnHints?.get(column.name)?.formatter || formatNumber;
const brace = filterMap(rows, row =>
- oneOf(typeof row[i], 'number', 'bigint') ? formatNumber(row[i]) : undefined,
+ oneOf(typeof row[i], 'number', 'bigint') ? formatter(row[i]) : undefined,
);
if (rows.length === brace.length) {
numericColumnBraces[i] = brace;
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx b/web-console/src/views/datasources-view/datasources-view.tsx
index 713df9b18b1..54b11a5a0cb 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { FormGroup, InputGroup, Intent, MenuItem, Switch } from '@blueprintjs/core';
+import { FormGroup, InputGroup, Intent, MenuItem, Switch, Tag } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { SqlQuery, T } from '@druid-toolkit/query';
import classNames from 'classnames';
@@ -651,8 +651,18 @@ GROUP BY 1, 2`;
return resp.data;
}}
confirmButtonText="Mark as unused all segments"
- successText="All segments in datasource have been marked as unused"
- failText="Failed to mark as unused all segments in datasource"
+ successText={
+ <>
+ All segments in datasource {datasourceToMarkAsUnusedAllSegmentsIn}{' '}
+ have been marked as unused
+ >
+ }
+ failText={
+ <>
+ Failed to mark as unused all segments in datasource{' '}
+ {datasourceToMarkAsUnusedAllSegmentsIn}
+ >
+ }
intent={Intent.DANGER}
onClose={() => {
this.setState({ datasourceToMarkAsUnusedAllSegmentsIn: undefined });
@@ -684,8 +694,19 @@ GROUP BY 1, 2`;
return resp.data;
}}
confirmButtonText="Mark as used all segments"
- successText="All non-overshadowed segments in datasource have been marked as used"
- failText="Failed to mark as used all non-overshadowed segments in datasource"
+ successText={
+ <>
+ All non-overshadowed segments in datasource{' '}
+ {datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn} have been marked
+ as used
+ >
+ }
+ failText={
+ <>
+ Failed to mark as used all non-overshadowed segments in datasource{' '}
+ {datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn}
+ >
+ }
intent={Intent.PRIMARY}
onClose={() => {
this.setState({ datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn: undefined });
diff --git a/web-console/src/views/explore-view/modules/components/generic-output-table/generic-output-table.tsx b/web-console/src/views/explore-view/modules/components/generic-output-table/generic-output-table.tsx
index 255a3a9b6fa..4f79156175b 100644
--- a/web-console/src/views/explore-view/modules/components/generic-output-table/generic-output-table.tsx
+++ b/web-console/src/views/explore-view/modules/components/generic-output-table/generic-output-table.tsx
@@ -31,7 +31,7 @@ import ReactTable from 'react-table';
import { BracedText, Deferred, TableCell } from '../../../../../components';
import { possibleDruidFormatForValues, TIME_COLUMN } from '../../../../../druid-models';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../../../../react-table';
-import type { Pagination, QueryAction } from '../../../../../utils';
+import type { ColumnHint, Pagination, QueryAction } from '../../../../../utils';
import {
columnToIcon,
columnToWidth,
@@ -60,30 +60,34 @@ function isComparable(x: unknown): boolean {
return x !== null && x !== '';
}
-function columnNester(columns: TableColumn[], groupHints: string[] | undefined): TableColumn[] {
- if (!groupHints) return columns;
+function columnNester(
+ tableColumns: TableColumn[],
+ resultColumns: readonly Column[],
+ columnHints: Map | undefined,
+): TableColumn[] {
+ if (!columnHints) return tableColumns;
const ret: TableColumn[] = [];
- let currentGroupHint: string | null = null;
+ let currentGroupName: string | null = null;
let currentColumnGroup: TableColumn | null = null;
- for (let i = 0; i < columns.length; i++) {
- const column = columns[i];
- const groupHint = groupHints[i];
- if (groupHint) {
- if (currentGroupHint === groupHint) {
- currentColumnGroup!.columns!.push(column);
+ for (let i = 0; i < tableColumns.length; i++) {
+ const tableColumn = tableColumns[i];
+ const group = columnHints.get(resultColumns[i].name)?.group;
+ if (group) {
+ if (currentGroupName === group) {
+ currentColumnGroup!.columns!.push(tableColumn);
} else {
- currentGroupHint = groupHint;
+ currentGroupName = group;
ret.push(
(currentColumnGroup = {
- Header: {currentGroupHint}
,
- columns: [column],
+ Header: {currentGroupName}
,
+ columns: [tableColumn],
}),
);
}
} else {
- ret.push(column);
- currentGroupHint = null;
+ ret.push(tableColumn);
+ currentGroupName = null;
currentColumnGroup = null;
}
}
@@ -94,12 +98,12 @@ function columnNester(columns: TableColumn[], groupHints: string[] | undefined):
export interface GenericOutputTableProps {
queryResult: QueryResult;
onQueryAction(action: QueryAction): void;
- onOrderByChange?(columnIndex: number, desc: boolean): void;
+ onOrderByChange?(columnName: string, desc: boolean): void;
onExport?(): void;
runeMode: boolean;
showTypeIcons: boolean;
initPageSize?: number;
- groupHints?: string[];
+ columnHints?: Map;
}
export const GenericOutputTable = React.memo(function GenericOutputTable(
@@ -113,7 +117,7 @@ export const GenericOutputTable = React.memo(function GenericOutputTable(
runeMode,
showTypeIcons,
initPageSize,
- groupHints,
+ columnHints,
} = props;
const parsedQuery = queryResult.sqlQuery;
const [pagination, setPagination] = useState({
@@ -159,7 +163,7 @@ export const GenericOutputTable = React.memo(function GenericOutputTable(
icon={reverseOrderByDirection === 'ASC' ? IconNames.SORT_ASC : IconNames.SORT_DESC}
text={`Order ${reverseOrderByDirection === 'ASC' ? 'ascending' : 'descending'}`}
onClick={() => {
- onOrderByChange(headerIndex, reverseOrderByDirection !== 'ASC');
+ onOrderByChange(header, reverseOrderByDirection !== 'ASC');
}}
/>,
);
@@ -170,7 +174,7 @@ export const GenericOutputTable = React.memo(function GenericOutputTable(
icon={IconNames.SORT_DESC}
text="Order descending"
onClick={() => {
- onOrderByChange(headerIndex, true);
+ onOrderByChange(header, true);
}}
/>,