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 2023/08/02 05:35:41 UTC

[druid] branch 27.0.0 updated: Web console: fix grouped filtering and add complex menu (#14668) (#14731)

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

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


The following commit(s) were added to refs/heads/27.0.0 by this push:
     new 0f7fa75e57 Web console: fix grouped filtering and add complex menu (#14668) (#14731)
0f7fa75e57 is described below

commit 0f7fa75e578aaa2aa0e37bea9313d7728a576d34
Author: AmatyaAvadhanula <am...@imply.io>
AuthorDate: Wed Aug 2 11:05:34 2023 +0530

    Web console: fix grouped filtering and add complex menu (#14668) (#14731)
    
    Backport #14668 to 27.0.0
---
 web-console/src/bootstrap/react-table-defaults.tsx |  7 +-
 .../segment-timeline/segment-timeline.tsx          |  3 +-
 web-console/src/react-table/react-table-utils.ts   |  2 +-
 .../__snapshots__/column-tree.spec.tsx.snap        | 24 ++++++-
 .../__snapshots__/complex-menu-items.spec.tsx.snap | 74 +++++++++++++++++++
 .../complex-menu-items.spec.tsx}                   | 26 ++++++-
 .../complex-menu-items/complex-menu-items.tsx      | 83 ++++++++++++++++++++++
 .../column-tree/column-tree-menu/index.ts          |  1 +
 .../number-menu-items/number-menu-items.tsx        | 12 +---
 .../string-menu-items/string-menu-items.tsx        | 14 +---
 .../time-menu-items/time-menu-items.tsx            | 11 +--
 .../workbench-view/column-tree/column-tree.tsx     | 29 +++++++-
 .../flexible-query-input/flexible-query-input.tsx  |  3 +-
 13 files changed, 246 insertions(+), 43 deletions(-)

diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx
index 42039b36da..531bc8127b 100644
--- a/web-console/src/bootstrap/react-table-defaults.tsx
+++ b/web-console/src/bootstrap/react-table-defaults.tsx
@@ -40,7 +40,12 @@ export function bootstrapReactTable() {
     className: DEFAULT_TABLE_CLASS_NAME,
     defaultFilterMethod: (filter: Filter, row: any) => {
       const id = filter.pivotId || filter.id;
-      return booleanCustomTableFilter(filter, row[id]);
+      const subRows = row._subRows;
+      if (Array.isArray(subRows)) {
+        return subRows.some(r => booleanCustomTableFilter(filter, r[id]));
+      } else {
+        return booleanCustomTableFilter(filter, row[id]);
+      }
     },
     LoadingComponent: Loader,
     loadingText: '',
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx b/web-console/src/components/segment-timeline/segment-timeline.tsx
index 150e94f2ab..3fc9e1ad87 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-import type { IResizeEntry } from '@blueprintjs/core';
 import { FormGroup, HTMLSelect, Radio, RadioGroup, ResizeSensor } from '@blueprintjs/core';
 import type { AxisScale } from 'd3-axis';
 import { scaleLinear, scaleUtc } from 'd3-scale';
@@ -428,7 +427,7 @@ ORDER BY "start" DESC`;
     }
   };
 
-  private readonly handleResize = (entries: IResizeEntry[]) => {
+  private readonly handleResize = (entries: ResizeObserverEntry[]) => {
     const chartRect = entries[0].contentRect;
     this.setState({
       chartWidth: chartRect.width,
diff --git a/web-console/src/react-table/react-table-utils.ts b/web-console/src/react-table/react-table-utils.ts
index e1ae5b9b15..91248c5520 100644
--- a/web-console/src/react-table/react-table-utils.ts
+++ b/web-console/src/react-table/react-table-utils.ts
@@ -107,7 +107,7 @@ export function addOrUpdateFilter(filters: readonly Filter[], filter: Filter): F
   return addOrUpdate(filters, filter, f => f.id);
 }
 
-export function booleanCustomTableFilter(filter: Filter, value: any): boolean {
+export function booleanCustomTableFilter(filter: Filter, value: unknown): boolean {
   if (value == null) return false;
   const modeAndNeedle = parseFilterModeAndNeedle(filter);
   if (!modeAndNeedle) return true;
diff --git a/web-console/src/views/workbench-view/column-tree/__snapshots__/column-tree.spec.tsx.snap b/web-console/src/views/workbench-view/column-tree/__snapshots__/column-tree.spec.tsx.snap
index f1ed721698..93e3c83d04 100644
--- a/web-console/src/views/workbench-view/column-tree/__snapshots__/column-tree.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/column-tree/__snapshots__/column-tree.spec.tsx.snap
@@ -32,7 +32,13 @@ exports[`ColumnTree matches snapshot 1`] = `
           Object {
             "childNodes": Array [
               Object {
-                "icon": "time",
+                "icon": <Blueprint4.Icon
+                  aria-hidden={true}
+                  className="bp4-tree-node-icon"
+                  icon="time"
+                  tabIndex={-1}
+                  title="TIMESTAMP"
+                />,
                 "id": "__time",
                 "label": <Blueprint4.Popover2
                   autoFocus={false}
@@ -65,7 +71,13 @@ exports[`ColumnTree matches snapshot 1`] = `
                 </Blueprint4.Popover2>,
               },
               Object {
-                "icon": "numerical",
+                "icon": <Blueprint4.Icon
+                  aria-hidden={true}
+                  className="bp4-tree-node-icon"
+                  icon="numerical"
+                  tabIndex={-1}
+                  title="BIGINT"
+                />,
                 "id": "added",
                 "label": <Blueprint4.Popover2
                   autoFocus={false}
@@ -98,7 +110,13 @@ exports[`ColumnTree matches snapshot 1`] = `
                 </Blueprint4.Popover2>,
               },
               Object {
-                "icon": "floating-point",
+                "icon": <Blueprint4.Icon
+                  aria-hidden={true}
+                  className="bp4-tree-node-icon"
+                  icon="floating-point"
+                  tabIndex={-1}
+                  title="FLOAT"
+                />,
                 "id": "addedBy10",
                 "label": <Blueprint4.Popover2
                   autoFocus={false}
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/__snapshots__/complex-menu-items.spec.tsx.snap b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/__snapshots__/complex-menu-items.spec.tsx.snap
new file mode 100644
index 0000000000..8281c3f085
--- /dev/null
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/__snapshots__/complex-menu-items.spec.tsx.snap
@@ -0,0 +1,74 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ComplexMenuItems matches snapshot when menu is opened for column not inside group by 1`] = `
+<div>
+  <li
+    class="bp4-submenu"
+    role="none"
+  >
+    <span
+      class="bp4-popover-wrapper"
+    >
+      <span
+        aria-haspopup="true"
+        class="bp4-popover-target"
+        role="menuitem"
+        tabindex="0"
+      >
+        <a
+          class="bp4-menu-item"
+          role="none"
+          tabindex="-1"
+        >
+          <span
+            class="bp4-menu-item-icon"
+          >
+            <span
+              aria-hidden="true"
+              class="bp4-icon bp4-icon-function"
+              icon="function"
+              tabindex="-1"
+            >
+              <svg
+                data-icon="function"
+                height="16"
+                role="img"
+                viewBox="0 0 16 16"
+                width="16"
+              >
+                <path
+                  d="M8.12 4.74H6.98c.33-1.29.75-2.24 1.28-2.84.33-.37.64-.56.95-.56.06 0 .11.02.15.05.04.04.06.09.06.15 0 .05-.04.15-.13.29-.09.14-.13.28-.13.4 0 .18.07.33.2.46.14.13.31.19.52.19.22 0 .41-.08.56-.23.15-.16.23-.37.23-.63 0-.3-.11-.55-.34-.74C10.1 1.09 9.74 1 9.24 1c-.78 0-1.49.22-2.12.67-.64.45-1.24 1.2-1.81 2.23-.2.36-.38.59-.56.69-.18.1-.46.15-.85.15l-.26.9h1.08l-1.59 6.12c-.27 1.01-.44 1.63-.54 1.86-.14.34-.34.63-.62.87-.11.1-.24.15-.4.15a.15.15 0 01-.11-.04l-.04-.05c0 [...]
+                  fill-rule="evenodd"
+                />
+              </svg>
+            </span>
+          </span>
+          <div
+            class="bp4-fill bp4-text-overflow-ellipsis"
+          >
+            Aggregate
+          </div>
+          <span
+            aria-hidden="true"
+            class="bp4-icon bp4-icon-caret-right bp4-submenu-icon"
+            icon="caret-right"
+          >
+            <svg
+              data-icon="caret-right"
+              height="16"
+              role="img"
+              viewBox="0 0 16 16"
+              width="16"
+            >
+              <path
+                d="M11 8c0-.15-.07-.28-.17-.37l-4-3.5A.495.495 0 006 4.5v7a.495.495 0 00.83.37l4-3.5c.1-.09.17-.22.17-.37z"
+                fill-rule="evenodd"
+              />
+            </svg>
+          </span>
+        </a>
+      </span>
+    </span>
+  </li>
+</div>
+`;
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.spec.tsx
similarity index 52%
copy from web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts
copy to web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.spec.tsx
index 0af13c5eb6..a7d7fdcf2c 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.spec.tsx
@@ -16,6 +16,26 @@
  * limitations under the License.
  */
 
-export * from './number-menu-items/number-menu-items';
-export * from './string-menu-items/string-menu-items';
-export * from './time-menu-items/time-menu-items';
+import { SqlQuery } from '@druid-toolkit/query';
+import { render } from '@testing-library/react';
+import React from 'react';
+
+import { ComplexMenuItems } from './complex-menu-items';
+
+describe('ComplexMenuItems', () => {
+  it('matches snapshot when menu is opened for column not inside group by', () => {
+    const numberMenu = (
+      <ComplexMenuItems
+        schema="schema"
+        table="table"
+        columnName="user_theta"
+        columnType="COMPLEX<thetaSketch>"
+        parsedQuery={SqlQuery.parse(`SELECT channel, count(*) as cnt FROM wikipedia GROUP BY 1`)}
+        onQueryChange={() => {}}
+      />
+    );
+
+    const { container } = render(numberMenu);
+    expect(container).toMatchSnapshot();
+  });
+});
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.tsx
new file mode 100644
index 0000000000..fa172262f0
--- /dev/null
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/complex-menu-items/complex-menu-items.tsx
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { MenuItem } from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
+import type { SqlExpression, SqlQuery } from '@druid-toolkit/query';
+import { C, F } from '@druid-toolkit/query';
+import type { JSX } from 'react';
+import React from 'react';
+
+import { prettyPrintSql } from '../../../../../utils';
+
+const UNIQUE_FUNCTIONS: Record<string, string> = {
+  'COMPLEX<hyperUnique>': 'APPROX_COUNT_DISTINCT_BUILTIN',
+  'COMPLEX<thetaSketch>': 'APPROX_COUNT_DISTINCT_DS_THETA',
+  'COMPLEX<HLLSketch>': 'APPROX_COUNT_DISTINCT_DS_HLL',
+};
+
+const QUANTILE_FUNCTIONS: Record<string, string> = {
+  'COMPLEX<quantilesDoublesSketch>': 'APPROX_QUANTILE_DS',
+};
+
+export interface ComplexMenuItemsProps {
+  table: string;
+  schema: string;
+  columnName: string;
+  columnType: string;
+  parsedQuery: SqlQuery;
+  onQueryChange: (query: SqlQuery, run?: boolean) => void;
+}
+
+export const ComplexMenuItems = React.memo(function ComplexMenuItems(props: ComplexMenuItemsProps) {
+  const { columnName, columnType, parsedQuery, onQueryChange } = props;
+  const column = C(columnName);
+
+  function renderAggregateMenu(): JSX.Element | undefined {
+    if (!parsedQuery.hasGroupBy()) return;
+
+    function aggregateMenuItem(ex: SqlExpression, alias: string) {
+      return (
+        <MenuItem
+          text={prettyPrintSql(ex)}
+          onClick={() => {
+            onQueryChange(parsedQuery.addSelect(ex.as(alias)), true);
+          }}
+        />
+      );
+    }
+
+    const uniqueFn = UNIQUE_FUNCTIONS[columnType];
+    const quantileFn = QUANTILE_FUNCTIONS[columnType];
+    if (!uniqueFn && !quantileFn) return;
+
+    return (
+      <MenuItem icon={IconNames.FUNCTION} text="Aggregate">
+        {uniqueFn && aggregateMenuItem(F(uniqueFn, column), `unique_${columnName}`)}
+        {quantileFn && (
+          <>
+            {aggregateMenuItem(F(quantileFn, column, 0.5), `median_${columnName}`)}
+            {aggregateMenuItem(F(quantileFn, column, 0.98), `p98_${columnName}`)}
+          </>
+        )}
+      </MenuItem>
+    );
+  }
+
+  return <>{renderAggregateMenu()}</>;
+});
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts b/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts
index 0af13c5eb6..7fd3dced5c 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/index.ts
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+export * from './complex-menu-items/complex-menu-items';
 export * from './number-menu-items/number-menu-items';
 export * from './string-menu-items/string-menu-items';
 export * from './time-menu-items/time-menu-items';
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
index 9c1242dda5..79fbedd54d 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
@@ -36,9 +36,10 @@ export interface NumberMenuItemsProps {
 }
 
 export const NumberMenuItems = React.memo(function NumberMenuItems(props: NumberMenuItemsProps) {
-  function renderFilterMenu(): JSX.Element {
-    const { columnName, parsedQuery, onQueryChange } = props;
+  const { columnName, parsedQuery, onQueryChange } = props;
+  const column = C(columnName);
 
+  function renderFilterMenu(): JSX.Element {
     function filterMenuItem(clause: SqlExpression) {
       return (
         <MenuItem
@@ -50,7 +51,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.FILTER} text="Filter">
         {filterMenuItem(column.greaterThan(NINE_THOUSAND))}
@@ -60,7 +60,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
   }
 
   function renderRemoveFilter(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.getEffectiveWhereExpression().containsColumnName(columnName)) return;
 
     return (
@@ -75,7 +74,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
   }
 
   function renderGroupByMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function groupByMenuItem(ex: SqlExpression, alias?: string) {
@@ -95,7 +93,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.GROUP_OBJECTS} text="Group by">
         {groupByMenuItem(column)}
@@ -105,7 +102,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
   }
 
   function renderRemoveGroupBy(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName);
     if (!groupedSelectIndexes.length) return;
 
@@ -121,7 +117,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
   }
 
   function renderAggregateMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function aggregateMenuItem(ex: SqlExpression, alias: string) {
@@ -135,7 +130,6 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.FUNCTION} text="Aggregate">
         {aggregateMenuItem(F('SUM', column), `sum_${columnName}`)}
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx
index 5760010577..db566765d3 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx
@@ -35,9 +35,10 @@ export interface StringMenuItemsProps {
 }
 
 export const StringMenuItems = React.memo(function StringMenuItems(props: StringMenuItemsProps) {
-  function renderFilterMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
+  const { schema, table, columnName, parsedQuery, onQueryChange } = props;
+  const column = C(columnName);
 
+  function renderFilterMenu(): JSX.Element | undefined {
     function filterMenuItem(clause: SqlExpression, run = true) {
       return (
         <MenuItem
@@ -49,7 +50,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.FILTER} text="Filter">
         {filterMenuItem(column.isNotNull())}
@@ -61,7 +61,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderRemoveFilter(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.getEffectiveWhereExpression().containsColumnName(columnName)) return;
 
     return (
@@ -76,7 +75,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderRemoveGroupBy(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName);
     if (!groupedSelectIndexes.length) return;
 
@@ -92,7 +90,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderGroupByMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function groupByMenuItem(ex: SqlExpression, alias?: string) {
@@ -112,7 +109,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.GROUP_OBJECTS} text="Group by">
         {groupByMenuItem(column)}
@@ -123,7 +119,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderAggregateMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function aggregateMenuItem(ex: SqlExpression, alias: string, run = true) {
@@ -137,7 +132,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.FUNCTION} text="Aggregate">
         {aggregateMenuItem(F.countDistinct(column), `dist_${columnName}`)}
@@ -152,7 +146,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderJoinMenu(): JSX.Element | undefined {
-    const { schema, table, columnName, parsedQuery, onQueryChange } = props;
     if (schema !== 'lookup' || !parsedQuery) return;
     const firstTableName = parsedQuery.getFirstTableName();
     if (!firstTableName) return;
@@ -212,7 +205,6 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
   }
 
   function renderRemoveJoin(): JSX.Element | undefined {
-    const { schema, parsedQuery, onQueryChange } = props;
     if (schema !== 'lookup' || !parsedQuery) return;
     if (!parsedQuery.hasJoin()) return;
 
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx
index 706c1cab50..677667ddc8 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx
@@ -113,9 +113,10 @@ export interface TimeMenuItemsProps {
 }
 
 export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuItemsProps) {
-  function renderFilterMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
+  const { columnName, parsedQuery, onQueryChange } = props;
+  const column = C(columnName);
 
+  function renderFilterMenu(): JSX.Element | undefined {
     function filterMenuItem(label: string, clause: SqlExpression) {
       return (
         <MenuItem
@@ -161,7 +162,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
   }
 
   function renderRemoveFilter(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.getEffectiveWhereExpression().containsColumnName(columnName)) return;
 
     return (
@@ -176,7 +176,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
   }
 
   function renderRemoveGroupBy(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName);
     if (!groupedSelectIndexes.length) return;
 
@@ -192,7 +191,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
   }
 
   function renderGroupByMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function groupByMenuItem(ex: SqlExpression, alias: string) {
@@ -212,7 +210,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.GROUP_OBJECTS} text="Group by">
         {groupByMenuItem(F.timeFloor(column, 'PT1H'), `${columnName}_by_hour`)}
@@ -229,7 +226,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
   }
 
   function renderAggregateMenu(): JSX.Element | undefined {
-    const { columnName, parsedQuery, onQueryChange } = props;
     if (!parsedQuery.hasGroupBy()) return;
 
     function aggregateMenuItem(ex: SqlExpression, alias: string) {
@@ -243,7 +239,6 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt
       );
     }
 
-    const column = C(columnName);
     return (
       <MenuItem icon={IconNames.FUNCTION} text="Aggregate">
         {aggregateMenuItem(F.max(column), `max_${columnName}`)}
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree.tsx b/web-console/src/views/workbench-view/column-tree/column-tree.tsx
index e19c2417a4..eee22449c9 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree.tsx
@@ -17,7 +17,7 @@
  */
 
 import type { TreeNodeInfo } from '@blueprintjs/core';
-import { HTMLSelect, Menu, MenuItem, Position, Tree } from '@blueprintjs/core';
+import { Classes, HTMLSelect, Icon, Menu, MenuItem, Position, Tree } from '@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import { Popover2 } from '@blueprintjs/popover2';
 import type { SqlExpression } from '@druid-toolkit/query';
@@ -39,7 +39,12 @@ import { Deferred, Loader } from '../../../components';
 import type { ColumnMetadata } from '../../../utils';
 import { copyAndAlert, dataTypeToIcon, groupBy, oneOf, prettyPrintSql } from '../../../utils';
 
-import { NumberMenuItems, StringMenuItems, TimeMenuItems } from './column-tree-menu';
+import {
+  ComplexMenuItems,
+  NumberMenuItems,
+  StringMenuItems,
+  TimeMenuItems,
+} from './column-tree-menu';
 
 import './column-tree.scss';
 
@@ -400,7 +405,15 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
               childNodes: metadata.map(
                 (columnData): TreeNodeInfo => ({
                   id: columnData.COLUMN_NAME,
-                  icon: dataTypeToIcon(columnData.DATA_TYPE),
+                  icon: (
+                    <Icon
+                      className={Classes.TREE_NODE_ICON}
+                      icon={dataTypeToIcon(columnData.DATA_TYPE)}
+                      aria-hidden
+                      tabIndex={-1}
+                      title={columnData.DATA_TYPE}
+                    />
+                  ),
                   label: (
                     <Popover2
                       position={Position.RIGHT}
@@ -454,6 +467,16 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
                                     onQueryChange={onQueryChange}
                                   />
                                 )}
+                                {parsedQuery && columnData.DATA_TYPE.startsWith('COMPLEX<') && (
+                                  <ComplexMenuItems
+                                    table={tableName}
+                                    schema={schemaName}
+                                    columnName={columnData.COLUMN_NAME}
+                                    columnType={columnData.DATA_TYPE}
+                                    parsedQuery={parsedQuery}
+                                    onQueryChange={onQueryChange}
+                                  />
+                                )}
                                 <MenuItem
                                   icon={IconNames.CLIPBOARD}
                                   text={`Copy: ${columnData.COLUMN_NAME}`}
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 62ca7e60fb..5f1141aaf2 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
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-import type { ResizeEntry } from '@blueprintjs/core';
 import { ResizeSensor2 } from '@blueprintjs/popover2';
 import { C, T } from '@druid-toolkit/query';
 import type { Ace } from 'ace-builds';
@@ -270,7 +269,7 @@ export class FlexibleQueryInput extends React.PureComponent<
     }
   }
 
-  private readonly handleAceContainerResize = (entries: ResizeEntry[]) => {
+  private readonly handleAceContainerResize = (entries: ResizeObserverEntry[]) => {
     if (entries.length !== 1) return;
     this.setState({ editorHeight: entries[0].contentRect.height });
   };


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org