From cb070efdb23823a6d67539b9658590990f38e0b6 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 18 Mar 2025 13:10:56 -0700 Subject: [PATCH 01/13] better debounce --- .../src/utils/query-manager/query-manager.ts | 13 ++++++++++++- .../components/preview-pane/preview-pane.tsx | 9 ++++++--- .../resource-pane/column-dialog/column-dialog.tsx | 11 +++++++---- .../src/views/explore-view/explore-view.scss | 1 + 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/web-console/src/utils/query-manager/query-manager.ts b/web-console/src/utils/query-manager/query-manager.ts index 0b7fcf5680ed..b7cbf3ab1c5d 100644 --- a/web-console/src/utils/query-manager/query-manager.ts +++ b/web-console/src/utils/query-manager/query-manager.ts @@ -39,6 +39,7 @@ export interface QueryManagerOptions { cancelToken: CancelToken, ) => Promise | ResultWithAuxiliaryWork>; onStateChange?: (queryResolve: QueryState) => void; + debounceInit?: number; debounceIdle?: number; debounceLoading?: number; backgroundStatusCheckInitDelay?: number; @@ -78,6 +79,7 @@ export class QueryManager { private state: QueryState; private currentQueryId = 0; + private readonly runWhenInit: () => void | Promise; private readonly runWhenIdle: () => void | Promise; private readonly runWhenLoading: () => void | Promise; @@ -88,6 +90,11 @@ export class QueryManager { this.backgroundStatusCheckInitDelay = options.backgroundStatusCheckInitDelay || 500; this.backgroundStatusCheckDelay = options.backgroundStatusCheckDelay || 1000; this.swallowBackgroundError = options.swallowBackgroundError; + if (options.debounceInit !== 0) { + this.runWhenInit = debounce(this.run, options.debounceInit || 50); + } else { + this.runWhenInit = this.run; + } if (options.debounceIdle !== 0) { this.runWhenIdle = debounce(this.run, options.debounceIdle || 100); } else { @@ -257,7 +264,11 @@ export class QueryManager { }), ); - void this.runWhenIdle(); + if (this.lastQuery) { + void this.runWhenIdle(); + } else { + void this.runWhenInit(); + } } } diff --git a/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx b/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx index 41bfd4bd14ac..051653c6882a 100644 --- a/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx +++ b/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx @@ -19,6 +19,7 @@ import { Callout } from '@blueprintjs/core'; import classNames from 'classnames'; import type { QueryResult, SqlQuery } from 'druid-query-toolkit'; +import { dedupe } from 'druid-query-toolkit'; import React from 'react'; import { useQueryManager } from '../../../../hooks'; @@ -38,15 +39,17 @@ function getPreviewValues(queryResult: QueryResult): any[] { export interface PreviewPaneProps { previewQuery: string | undefined; runSqlQuery(query: string | SqlQuery): Promise; + deduplicate?: boolean; } export const PreviewPane = React.memo(function PreviewPane(props: PreviewPaneProps) { - const { previewQuery, runSqlQuery } = props; + const { previewQuery, runSqlQuery, deduplicate } = props; const [previewState] = useQueryManager({ query: previewQuery, processQuery: runSqlQuery, - debounceIdle: 1000, + debounceIdle: 2000, + debounceLoading: 3000, }); const previewValues = previewState.data ? getPreviewValues(previewState.data) : undefined; @@ -58,7 +61,7 @@ export const PreviewPane = React.memo(function PreviewPane(props: PreviewPanePro (previewValues.length ? (
- {previewValues.map((v, i) => ( + {(deduplicate ? dedupe(previewValues) : previewValues).map((v, i) => (
{ const expression = SqlExpression.maybeParse(formula); if (!expression) return; + + const expressionToPreview = F.cast(expression, 'VARCHAR'); return querySource .getInitBaseQuery() - .addSelect(F.cast(expression, 'VARCHAR').as('v'), { addToGroupBy: 'end' }) + .addSelect(expressionToPreview.as('v')) + .addWhere(expressionToPreview.isNotNull()) .applyIf(querySource.hasBaseTimeColumn(), q => - q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`), + q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '1' DAY <= __time`), ) - .changeLimitValue(100) + .changeLimitValue(200) .toString(); }, [querySource, formula]); @@ -90,7 +93,7 @@ export const ColumnDialog = React.memo(function ColumnDialog(props: ColumnDialog />
- +
diff --git a/web-console/src/views/explore-view/explore-view.scss b/web-console/src/views/explore-view/explore-view.scss index 54a976f780db..d05418700897 100644 --- a/web-console/src/views/explore-view/explore-view.scss +++ b/web-console/src/views/explore-view/explore-view.scss @@ -72,6 +72,7 @@ $resources-width: 240px; .source-pane-container { padding: 8px 0; border-right: 1px solid $dark-gray2; + align-self: stretch; } .filter-pane { From ef0a1928e8e23b1bfb431f653ddf982437dffec8 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 18 Mar 2025 16:31:28 -0700 Subject: [PATCH 02/13] better cumpose filter --- .../column-picker-menu/column-picker-menu.tsx | 39 +++++++++---- .../filter-pane/filter-menu/filter-menu.tsx | 23 +++++++- .../time-interval-filter-control.tsx | 50 ++++++++++++++--- .../values-filter-control.tsx | 55 ++++++++++++------- .../views/explore-view/components/index.ts | 2 +- .../iso-date-input.tsx} | 40 ++++++++++---- 6 files changed, 156 insertions(+), 53 deletions(-) rename web-console/src/views/explore-view/components/{utc-date-input/utc-date-input.tsx => iso-date-input/iso-date-input.tsx} (62%) diff --git a/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx b/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx index 7634cbae4ccd..ef3f8186428a 100644 --- a/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx +++ b/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx @@ -17,13 +17,14 @@ */ import type { IconName } from '@blueprintjs/core'; -import { Icon, InputGroup, Menu, MenuItem } from '@blueprintjs/core'; +import { ContextMenu, Icon, InputGroup, Menu, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; import type { Column } from 'druid-query-toolkit'; +import { C } from 'druid-query-toolkit'; import { useState } from 'react'; -import { caseInsensitiveContains, columnToIcon, filterMap } from '../../../../utils'; +import { caseInsensitiveContains, columnToIcon, copyAndAlert, filterMap } from '../../../../utils'; import './column-picker-menu.scss'; @@ -66,17 +67,33 @@ export const ColumnPickerMenu = function ColumnPickerMenu(props: ColumnPickerMen /> )} {filterMap(columns, (c, i) => { - if (!caseInsensitiveContains(c.name, columnSearch)) return; + const columnName = c.name; + if (!caseInsensitiveContains(columnName, columnSearch)) return; const iconName = rightIconForColumn?.(c); return ( - } - onClick={() => onSelectColumn(c)} - shouldDismissPopover={shouldDismissPopover} - /> + + copyAndAlert(String(columnName), `Copied to clipboard`)} + /> + copyAndAlert(String(C(columnName)), `Copied to clipboard`)} + /> + + } + > + } + onClick={() => onSelectColumn(c)} + shouldDismissPopover={shouldDismissPopover} + /> + ); })} diff --git a/web-console/src/views/explore-view/components/filter-pane/filter-menu/filter-menu.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/filter-menu.tsx index b5f5ee2fc9b7..28da18dd2269 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/filter-menu.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/filter-menu.tsx @@ -136,8 +136,18 @@ export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) initPattern?.type === 'custom' ? filterPatternToExpression(initPattern).toString() : '', ); const [pattern, setPattern] = useState(initPattern); + const [issue, setIssue] = useState(); const { columns } = querySource; + function setFilterPatternOrIssue(pattern: FilterPattern | undefined, issue: string | undefined) { + if (pattern) { + setPattern(pattern); + setIssue(undefined); + } else { + setIssue(issue || 'Issue'); + } + } + function onAcceptPattern(pattern: FilterPattern) { onPatternChange(pattern); onClose(); @@ -198,7 +208,8 @@ export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) ); break; @@ -281,6 +292,7 @@ export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) active={tab === 'sql'} onClick={() => { setFormula(pattern ? filterPatternToExpression(pattern).toString() : ''); + setIssue(undefined); setTab('sql'); }} /> @@ -416,8 +428,17 @@ export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) intent={Intent.PRIMARY} text="Apply" disabled={tab === 'sql' && formula === ''} + data-tooltip={issue ? `Issue: ${issue}` : undefined} onClick={() => { if (tab === 'compose') { + if (issue) { + AppToaster.show({ + message: issue, + intent: Intent.DANGER, + }); + return; + } + if (pattern) { onAcceptPattern(pattern); } diff --git a/web-console/src/views/explore-view/components/filter-pane/filter-menu/time-interval-filter-control/time-interval-filter-control.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/time-interval-filter-control/time-interval-filter-control.tsx index 9200ee9e07e1..36bee8702838 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/time-interval-filter-control/time-interval-filter-control.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/time-interval-filter-control/time-interval-filter-control.tsx @@ -18,35 +18,69 @@ import { FormGroup } from '@blueprintjs/core'; import type { TimeIntervalFilterPattern } from 'druid-query-toolkit'; -import React from 'react'; +import React, { useState } from 'react'; import type { QuerySource } from '../../../../models'; -import { UtcDateInput } from '../../../utc-date-input/utc-date-input'; +import { IsoDateInput } from '../../../iso-date-input/iso-date-input'; import './time-interval-filter-control.scss'; +function isSwappedFilterPattern(pattern: TimeIntervalFilterPattern) { + return pattern.end <= pattern.start; +} + export interface TimeIntervalFilterControlProps { querySource: QuerySource; filterPattern: TimeIntervalFilterPattern; - setFilterPattern(filterPattern: TimeIntervalFilterPattern): void; + setFilterPatternOrIssue( + filterPattern: TimeIntervalFilterPattern | undefined, + issue: string | undefined, + ): void; + onIssue(issue: string): void; } export const TimeIntervalFilterControl = React.memo(function TimeIntervalFilterControl( props: TimeIntervalFilterControlProps, ) { - const { filterPattern, setFilterPattern } = props; - const { start, end } = filterPattern; + const { filterPattern, setFilterPatternOrIssue, onIssue } = props; + const [swappedFilterPattern, setSwappedFilterPattern] = useState< + TimeIntervalFilterPattern | undefined + >(); + const { start, end } = swappedFilterPattern || filterPattern; return (
- setFilterPattern({ ...filterPattern, start })} + onChange={start => { + const newPattern = { ...filterPattern, start }; + if (isSwappedFilterPattern(newPattern)) { + setSwappedFilterPattern(newPattern); + setFilterPatternOrIssue(undefined, 'Start date must be before end date'); + } else { + setSwappedFilterPattern(undefined); + setFilterPatternOrIssue(newPattern, undefined); + } + }} + onIssue={() => onIssue('Bad start date')} /> - setFilterPattern({ ...filterPattern, end })} /> + { + const newPattern = { ...filterPattern, end }; + if (isSwappedFilterPattern(newPattern)) { + setSwappedFilterPattern(newPattern); + setFilterPatternOrIssue(undefined, 'End date must be after start date'); + } else { + setSwappedFilterPattern(undefined); + setFilterPatternOrIssue(newPattern, undefined); + } + }} + onIssue={() => onIssue('Bad end date')} + />
); diff --git a/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx index f14851f081fb..43e8e2c36119 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx @@ -16,16 +16,16 @@ * limitations under the License. */ -import { FormGroup, Menu, MenuItem } from '@blueprintjs/core'; +import { ContextMenu, FormGroup, Menu, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import type { CancelToken } from 'axios'; import type { QueryResult, SqlQuery, ValuesFilterPattern } from 'druid-query-toolkit'; -import { C, F, SqlExpression } from 'druid-query-toolkit'; +import { C, F, L, SqlExpression } from 'druid-query-toolkit'; import React, { useMemo, useState } from 'react'; import { ClearableInput } from '../../../../../../components'; import { useQueryManager } from '../../../../../../hooks'; -import { caseInsensitiveContains, filterMap, toggle } from '../../../../../../utils'; +import { caseInsensitiveContains, copyAndAlert, filterMap, toggle } from '../../../../../../utils'; import type { QuerySource } from '../../../../models'; import { ColumnValue } from '../../../column-value/column-value'; @@ -90,24 +90,39 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl( {filterMap(valuesToShow, (v, i) => { if (!caseInsensitiveContains(v, searchString)) return; return ( - + copyAndAlert(String(v), `Copied to clipboard`)} + /> + copyAndAlert(String(L(v)), `Copied to clipboard`)} + /> + } - text={} - shouldDismissPopover={false} - onClick={e => { - setFilterPattern({ - ...filterPattern, - values: e.altKey ? [v] : toggle(selectedValues, v), - }); - }} - /> + > + } + shouldDismissPopover={false} + onClick={e => { + setFilterPattern({ + ...filterPattern, + values: e.altKey ? [v] : toggle(selectedValues, v), + }); + }} + /> + ); })} {valuesState.loading && } diff --git a/web-console/src/views/explore-view/components/index.ts b/web-console/src/views/explore-view/components/index.ts index 085bbff3c3e0..58a8fb6fa72e 100644 --- a/web-console/src/views/explore-view/components/index.ts +++ b/web-console/src/views/explore-view/components/index.ts @@ -21,6 +21,7 @@ export * from './droppable-container/droppable-container'; export * from './filter-pane/filter-pane'; export * from './generic-output-table/generic-output-table'; export * from './helper-table/helper-table'; +export * from './iso-date-input/iso-date-input'; export * from './issue/issue'; export * from './module-pane/module-pane'; export * from './module-picker/module-picker'; @@ -28,4 +29,3 @@ export * from './resource-pane/resource-pane'; export * from './source-pane/source-pane'; export * from './source-query-pane/source-query-pane'; export * from './sql-input/sql-input'; -export * from './utc-date-input/utc-date-input'; diff --git a/web-console/src/views/explore-view/components/utc-date-input/utc-date-input.tsx b/web-console/src/views/explore-view/components/iso-date-input/iso-date-input.tsx similarity index 62% rename from web-console/src/views/explore-view/components/utc-date-input/utc-date-input.tsx rename to web-console/src/views/explore-view/components/iso-date-input/iso-date-input.tsx index b1de4ce7c13d..b145e31a2211 100644 --- a/web-console/src/views/explore-view/components/utc-date-input/utc-date-input.tsx +++ b/web-console/src/views/explore-view/components/iso-date-input/iso-date-input.tsx @@ -16,10 +16,10 @@ * limitations under the License. */ -import { InputGroup } from '@blueprintjs/core'; +import { InputGroup, Intent } from '@blueprintjs/core'; import { useState } from 'react'; -function utcParseDate(dateString: string): Date | undefined { +function isoParseDate(dateString: string): Date | undefined { const dateParts = dateString.split(/[-T:. ]/g); // Extract the individual date and time components @@ -44,7 +44,7 @@ function utcParseDate(dateString: string): Date | undefined { const millisecond = parseInt(dateParts[6], 10); if (millisecond >= 1000) return; - const value = Date.UTC(year, month - 1, day, hour, minute, second); // Month is zero-based + const value = Date.UTC(year, month - 1, day, hour, minute, second, millisecond); // Month is zero-based if (isNaN(value)) return; return new Date(value); @@ -61,26 +61,42 @@ function formatDate(date: Date): string { export interface UtcDateInputProps { date: Date; onChange(newDate: Date): void; + onIssue(): void; } -export function UtcDateInput(props: UtcDateInputProps) { - const { date, onChange } = props; - const [dateString, setDateString] = useState(); +export function IsoDateInput(props: UtcDateInputProps) { + const { date, onChange, onIssue } = props; + const [invalidDateString, setInvalidDateString] = useState(); + const [customDateString, setCustomDateString] = useState(); + const [focused, setFocused] = useState(false); return ( { - const v = normalizeDateString(e.target.value); - const parsedDate = utcParseDate(v); - if (parsedDate && formatDate(parsedDate) === v) { + const normalizedDateString = normalizeDateString(e.target.value); + const parsedDate = isoParseDate(normalizedDateString); + if (parsedDate) { onChange(parsedDate); - setDateString(undefined); + setInvalidDateString(undefined); + setCustomDateString(normalizedDateString); } else { - setDateString(v); + onIssue(); + setInvalidDateString(normalizedDateString); + setCustomDateString(undefined); } }} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} /> ); } From a1197344b453d32c71598de465595b9284867221 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 19 Mar 2025 10:29:20 -0700 Subject: [PATCH 03/13] hook up preview filters --- .../column-picker-menu/column-picker-menu.tsx | 2 +- .../values-filter-control.tsx | 2 +- .../column-dialog/column-dialog.tsx | 12 +++++------ .../measure-dialog/measure-dialog.tsx | 20 ++++++------------- .../resource-pane/resource-pane.tsx | 6 +++++- .../src/views/explore-view/explore-view.tsx | 15 ++++++++++---- .../views/explore-view/models/query-source.ts | 2 +- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx b/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx index ef3f8186428a..597df5c4d817 100644 --- a/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx +++ b/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx @@ -72,6 +72,7 @@ export const ColumnPickerMenu = function ColumnPickerMenu(props: ColumnPickerMen const iconName = rightIconForColumn?.(c); return ( } diff --git a/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx index 43e8e2c36119..1f57d6218ddd 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/values-filter-control/values-filter-control.tsx @@ -91,6 +91,7 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl( if (!caseInsensitiveContains(v, searchString)) return; return ( ; onClose(): void; } export const ColumnDialog = React.memo(function ColumnDialog(props: ColumnDialogProps) { - const { initExpression, columnToDuplicate, onApply, querySource, runSqlQuery, onClose } = props; + const { initExpression, columnToDuplicate, onApply, querySource, where, runSqlQuery, onClose } = + props; const [outputName, setOutputName] = useState(initExpression?.getOutputName() || ''); const [formula, setFormula] = useState(String(initExpression?.getUnderlyingExpression() || '')); @@ -54,12 +56,10 @@ export const ColumnDialog = React.memo(function ColumnDialog(props: ColumnDialog .getInitBaseQuery() .addSelect(expressionToPreview.as('v')) .addWhere(expressionToPreview.isNotNull()) - .applyIf(querySource.hasBaseTimeColumn(), q => - q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '1' DAY <= __time`), - ) + .addWhere(querySource.transformExpressionToBaseColumns(where)) .changeLimitValue(200) .toString(); - }, [querySource, formula]); + }, [querySource, formula, where]); return ( ; onClose(): void; } export const MeasureDialog = React.memo(function MeasureDialog(props: MeasureDialogProps) { - const { initMeasure, measureToDuplicate, onApply, querySource, runSqlQuery, onClose } = props; + const { initMeasure, measureToDuplicate, onApply, querySource, where, runSqlQuery, onClose } = + props; const [outputName, setOutputName] = useState(initMeasure?.name || ''); const [formula, setFormula] = useState(String(initMeasure?.expression || '')); @@ -57,12 +52,9 @@ export const MeasureDialog = React.memo(function MeasureDialog(props: MeasureDia .changeWithParts([SqlWithPart.simple('t', QuerySource.stripToBaseSource(querySource.query))]) .addSelect(L('Overall').as('label')) .addSelect(expression.as('value')) - .applyIf(querySource.hasBaseTimeColumn(), q => - q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`), - ) + .changeWhereExpression(querySource.transformExpressionToBaseColumns(where)) .toString(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [querySource.query, formula]); + }, [querySource, formula, where]); return ( void; + where: SqlExpression; onFilter?: (column: Column) => void; onShowColumn(column: Column): void; onShowMeasure(measure: Measure): void; @@ -77,7 +78,8 @@ export interface ResourcePaneProps { } export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { - const { querySource, onQueryChange, onFilter, onShowColumn, onShowMeasure, runSqlQuery } = props; + const { querySource, onQueryChange, where, onFilter, onShowColumn, onShowMeasure, runSqlQuery } = + props; const [columnSearch, setColumnSearch] = useState(''); const [columnEditorOpenOn, setColumnEditorOpenOn] = useState(); @@ -317,6 +319,7 @@ export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { columnToDuplicate={columnEditorOpenOn.columnToDuplicate} onApply={onQueryChange} querySource={querySource} + where={where} runSqlQuery={runSqlQuery} onClose={() => setColumnEditorOpenOn(undefined)} /> @@ -336,6 +339,7 @@ export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { measureToDuplicate={measureEditorOpenOn.measureToDuplicate} onApply={onQueryChange} querySource={querySource} + where={where} runSqlQuery={runSqlQuery} onClose={() => setMeasureEditorOpenOn(undefined)} /> diff --git a/web-console/src/views/explore-view/explore-view.tsx b/web-console/src/views/explore-view/explore-view.tsx index 0d1624cbd7ff..e302cae3ccff 100644 --- a/web-console/src/views/explore-view/explore-view.tsx +++ b/web-console/src/views/explore-view/explore-view.tsx @@ -329,7 +329,9 @@ export const ExploreView = React.memo(function ExploreView() { if (!querySource) return; setExploreState( effectiveExploreState.changeSource( - querySource.addColumn(querySource.transformToBaseColumns(expression)), + querySource.addColumn( + querySource.transformExpressionToBaseColumns(expression), + ), undefined, ), ); @@ -340,7 +342,9 @@ export const ExploreView = React.memo(function ExploreView() { effectiveExploreState .change({ where: changeWhere }) .changeSource( - querySource.addWhereClause(querySource.transformToBaseColumns(expression)), + querySource.addWhereClause( + querySource.transformExpressionToBaseColumns(expression), + ), undefined, ), ); @@ -461,6 +465,7 @@ export const ExploreView = React.memo(function ExploreView() { { filterPane.current?.filterOn(c); }} @@ -502,7 +507,7 @@ export const ExploreView = React.memo(function ExploreView() { setExploreState( effectiveExploreState.changeSource( querySource.addColumn( - querySource.transformToBaseColumns(expression), + querySource.transformExpressionToBaseColumns(expression), ), undefined, ), @@ -514,7 +519,9 @@ export const ExploreView = React.memo(function ExploreView() { effectiveExploreState.changeSource( querySource.addMeasure( measure.changeExpression( - querySource.transformToBaseColumns(measure.expression), + querySource.transformExpressionToBaseColumns( + measure.expression, + ), ), ), undefined, diff --git a/web-console/src/views/explore-view/models/query-source.ts b/web-console/src/views/explore-view/models/query-source.ts index fa3f047426e1..cb67dbc89a93 100644 --- a/web-console/src/views/explore-view/models/query-source.ts +++ b/web-console/src/views/explore-view/models/query-source.ts @@ -224,7 +224,7 @@ export class QuerySource { ); } - public transformToBaseColumns(expression: SqlExpression): SqlExpression { + public transformExpressionToBaseColumns(expression: SqlExpression): SqlExpression { const sourceToBaseSubstitutions = this.getSourceToBaseSubstitutions(); return expression.walk(ex => { if (ex instanceof SqlColumn) { From df92f28c6235d6509ec8b5df2b516352a2830602 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 19 Mar 2025 11:15:08 -0700 Subject: [PATCH 04/13] better stack handling --- .../continuous-chart-render.tsx | 17 +++++++++++------ .../time-chart-module/time-chart-module.tsx | 11 ++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx index 378c652142e8..b0f97bcad146 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx @@ -391,6 +391,7 @@ export const ContinuousChartRender = function ContinuousChartRender( const byStack = useMemo(() => { if (markType === 'bar' || !stackedData.length) return []; + const isStacked = markType !== 'line'; const effectiveStacks = stacks || ['undefined']; const numStacks = effectiveStacks.length; @@ -408,12 +409,14 @@ export const ContinuousChartRender = function ContinuousChartRender( ...dataForStart[0], stack, measure: 0, - offset: Math.max( - 0, - ...filterMap(effectiveStacks.slice(0, stackIndex), s => stackToDatum[s]).map( - d => d.offset + d.measure, - ), - ), + offset: isStacked + ? Math.max( + 0, + ...filterMap(effectiveStacks.slice(0, stackIndex), s => stackToDatum[s]).map( + d => d.offset + d.measure, + ), + ) + : 0, }, ); }, @@ -566,6 +569,8 @@ export const ContinuousChartRender = function ContinuousChartRender( shiftEndForward < nowDayCeil.valueOf() ? shiftEndForward : nowDayCeil.valueOf(), ]; + console.log(byStack); + const nowX = timeScale(now); return (
diff --git a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx index 5ac00a310d05..7550eef60a8b 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx @@ -234,7 +234,7 @@ ModuleRepository.registerModule({ const granularity = new Duration(timeGranularity); - const vs = splitExpression + const vs: string[] | undefined = splitExpression ? ( await runSqlQuery( { @@ -242,7 +242,7 @@ ModuleRepository.registerModule({ .getInitQuery(where) .addSelect(splitExpression.cast('VARCHAR').as('v'), { addToGroupBy: 'end' }) .changeOrderByExpression(measure.expression.toOrderByExpression('DESC')) - .changeLimitValue(numberToStack), + .changeLimitValue(numberToStack + (showOthers ? 1 : 0)), // If we want to show others add 1 to check if we need to query for them timezone, }, cancelToken, @@ -262,13 +262,14 @@ ModuleRepository.registerModule({ }; } - const effectiveVs = vs && showOthers ? vs.concat(OTHER_VALUE) : vs; + const queryForOthers = Boolean(showOthers && vs && numberToStack < vs.length); + const effectiveVs = queryForOthers ? vs!.slice(0, numberToStack).concat(OTHER_VALUE) : vs; const result = await runSqlQuery( { query: querySource .getInitQuery(overqueryWhere(where, timeColumnName, granularity, oneExtra)) - .applyIf(splitExpression && vs && !showOthers, q => + .applyIf(splitExpression && vs && !queryForOthers, q => q.addWhere(splitExpression!.cast('VARCHAR').in(vs!)), ) .addSelect(F.timeFloor(C(timeColumnName), L(timeGranularity)).as(TIME_NAME), { @@ -279,7 +280,7 @@ ModuleRepository.registerModule({ .applyIf(splitExpression, q => { if (!splitExpression || !vs) return q; // Should never get here, doing this to make peace between eslint and TS return q.addSelect( - (showOthers + (queryForOthers ? SqlCase.ifThenElse(splitExpression.in(vs), splitExpression, L(OTHER_VALUE)) : splitExpression ) From 04dfc84530bf1354b6e85582026f93eac4962e75 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 19 Mar 2025 15:22:01 -0700 Subject: [PATCH 05/13] fix some props --- .../continuous-chart-render.scss | 20 ++++++----- .../time-chart-module/time-chart-module.tsx | 33 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.scss b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.scss index 5f7be42df956..63f237580914 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.scss +++ b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.scss @@ -18,6 +18,8 @@ @import '../../../../variables'; +$default-chart-color: $druid-brand; + .continuous-chart-render { position: relative; overflow: hidden; @@ -30,10 +32,6 @@ user-select: none; } - .mark-bar { - fill: #497ee6; - } - .selection { fill: white; fill-opacity: 0.1; @@ -89,25 +87,29 @@ opacity: 0.7; } + .mark-bar { + fill: #00b6c3; + } + .mark-line { stroke-width: 1.5px; - stroke: #497ee6; + stroke: $default-chart-color; fill: none; } .mark-area { - fill: #497ee6; - opacity: 0.7; + fill: $default-chart-color; + opacity: 0.5; } .single-point { - stroke: #497ee6; + stroke: $default-chart-color; opacity: 0.7; stroke-width: 1.5px; } .selected-point { - fill: #497ee6; + fill: $default-chart-color; } } diff --git a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx index 7550eef60a8b..8d1b59009e75 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx @@ -92,12 +92,12 @@ function getRangeInExpression( } interface TimeChartParameterValues { + markType: ContinuousChartMarkType; granularity: string; splitColumn?: ExpressionMeta; numberToStack: number; showOthers: boolean; measure: ExpressionMeta; - markType: ContinuousChartMarkType; curveType: ContinuousChartCurveType; } @@ -106,6 +106,12 @@ ModuleRepository.registerModule({ title: 'Time chart', icon: IconNames.TIMELINE_LINE_CHART, parameters: { + markType: { + type: 'option', + options: ['line', 'area', 'bar'], + defaultValue: 'line', + optionLabels: capitalizeFirst, + }, granularity: { type: 'option', options: ({ querySource, where }) => { @@ -142,7 +148,7 @@ ModuleRepository.registerModule({ type: 'number', label: 'Max stacks', defaultValue: 7, - min: 2, + min: 1, required: true, visible: ({ parameterValues }) => Boolean(parameterValues.splitColumn), }, @@ -159,12 +165,6 @@ ModuleRepository.registerModule({ defaultValue: ({ querySource }) => querySource?.getFirstAggregateMeasure(), required: true, }, - markType: { - type: 'option', - options: ['area', 'bar', 'line'], - defaultValue: 'area', - optionLabels: capitalizeFirst, - }, curveType: { type: 'option', options: ['smooth', 'linear', 'step'], @@ -262,14 +262,15 @@ ModuleRepository.registerModule({ }; } - const queryForOthers = Boolean(showOthers && vs && numberToStack < vs.length); - const effectiveVs = queryForOthers ? vs!.slice(0, numberToStack).concat(OTHER_VALUE) : vs; + const queryVs = + showOthers && vs && numberToStack < vs.length ? vs.slice(0, numberToStack) : undefined; + const effectiveVs = queryVs ? queryVs.concat(OTHER_VALUE) : vs; const result = await runSqlQuery( { query: querySource .getInitQuery(overqueryWhere(where, timeColumnName, granularity, oneExtra)) - .applyIf(splitExpression && vs && !queryForOthers, q => + .applyIf(splitExpression && vs && !queryVs, q => q.addWhere(splitExpression!.cast('VARCHAR').in(vs!)), ) .addSelect(F.timeFloor(C(timeColumnName), L(timeGranularity)).as(TIME_NAME), { @@ -278,10 +279,14 @@ ModuleRepository.registerModule({ direction: 'DESC', }) .applyIf(splitExpression, q => { - if (!splitExpression || !vs) return q; // Should never get here, doing this to make peace between eslint and TS + if (!splitExpression) return q; // Should never get here, doing this to make peace between eslint and TS return q.addSelect( - (queryForOthers - ? SqlCase.ifThenElse(splitExpression.in(vs), splitExpression, L(OTHER_VALUE)) + (queryVs + ? SqlCase.ifThenElse( + splitExpression.in(queryVs), + splitExpression, + L(OTHER_VALUE), + ) : splitExpression ) .cast('VARCHAR') From d9d23a7f1c6e15b8d0bdc5a5327a3d0c6bdcce24 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 20 Mar 2025 11:16:03 -0700 Subject: [PATCH 06/13] refactor stack to facet --- .../src/views/explore-view/explore-view.tsx | 4 +- .../views/explore-view/models/parameter.ts | 7 +- .../continuous-chart-render.tsx | 102 +++++++++--------- .../time-chart-module/time-chart-module.tsx | 83 +++++++------- 4 files changed, 102 insertions(+), 94 deletions(-) diff --git a/web-console/src/views/explore-view/explore-view.tsx b/web-console/src/views/explore-view/explore-view.tsx index e302cae3ccff..0d984d856213 100644 --- a/web-console/src/views/explore-view/explore-view.tsx +++ b/web-console/src/views/explore-view/explore-view.tsx @@ -144,9 +144,7 @@ export const ExploreView = React.memo(function ExploreView() { '#explore/v/', LocalStorageKeys.EXPLORE_STATE, ExploreState.DEFAULT_STATE, - s => { - return ExploreState.fromJS(s); - }, + s => ExploreState.fromJS(s), ); // ------------------------------------------------------- diff --git a/web-console/src/views/explore-view/models/parameter.ts b/web-console/src/views/explore-view/models/parameter.ts index ed021da6f5b7..1929795bc199 100644 --- a/web-console/src/views/explore-view/models/parameter.ts +++ b/web-console/src/views/explore-view/models/parameter.ts @@ -108,6 +108,7 @@ export type TypedParameterDefinition = TypedE placeholder?: string; defined?: ModuleFunctor; visible?: ModuleFunctor; + legacyName?: string; }; export type ParameterDefinition = @@ -157,7 +158,11 @@ export function inflateParameterValues( parameters: Parameters, ): ParameterValues { return mapRecord(parameters, (parameter, parameterName) => - inflateParameterValue(parameterValues?.[parameterName], parameter), + inflateParameterValue( + parameterValues?.[parameterName] ?? + (parameter.legacyName ? parameterValues?.[parameter.legacyName] : undefined), + parameter, + ), ); } diff --git a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx index b0f97bcad146..7117fe84c286 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx @@ -73,7 +73,7 @@ export interface RangeDatum { start: number; end: number; measure: number; - stack: string | undefined; + facet: string | undefined; } export interface StackedRangeDatum extends RangeDatum { @@ -126,7 +126,7 @@ export interface ContinuousChartRenderProps { * If stacking is used then the stack bars should be ordered bottom to top. */ data: RangeDatum[]; - stacks: string[] | undefined; + facets: string[] | undefined; /** * The granularity that was used for bucketing. @@ -161,7 +161,7 @@ export const ContinuousChartRender = function ContinuousChartRender( ) { const { data, - stacks, + facets, granularity, markType, @@ -199,10 +199,10 @@ export const ContinuousChartRender = function ContinuousChartRender( const svgRef = useRef(null); const stackedData: StackedRangeDatum[] = useMemo(() => { - const effectiveStacks = stacks || ['undefined']; - const stackToIndex = lookupBy( - effectiveStacks, - s => s, + const effectiveFacet = facets || ['undefined']; + const facetToIndex = lookupBy( + effectiveFacet, + f => f, (_, i) => i, ); @@ -211,7 +211,7 @@ export const ContinuousChartRender = function ContinuousChartRender( const diffStart = b.start - a.start; if (diffStart) return diffStart; - return stackToIndex[String(a.stack)] - stackToIndex[String(b.stack)]; + return facetToIndex[String(a.facet)] - facetToIndex[String(b.facet)]; }); if (markType === 'line') { @@ -230,7 +230,7 @@ export const ContinuousChartRender = function ContinuousChartRender( return withOffset; }); } - }, [data, stacks, markType]); + }, [data, facets, markType]); function findStackedDatum(time: number, measure: number): StackedRangeDatum | undefined { const dataInRange = stackedData.filter(d => d.start <= time && time < d.end); @@ -241,7 +241,7 @@ export const ContinuousChartRender = function ContinuousChartRender( ); } - const stackColorizer = useMemo(() => { + const facetColorizer = useMemo(() => { const s = scaleOrdinal(COLORS); return (v: string) => (v === OTHER_VALUE ? OTHER_COLOR : s(v)); }, []); @@ -389,30 +389,30 @@ export const ContinuousChartRender = function ContinuousChartRender( } }); - const byStack = useMemo(() => { + const byFacet = useMemo(() => { if (markType === 'bar' || !stackedData.length) return []; const isStacked = markType !== 'line'; - const effectiveStacks = stacks || ['undefined']; - const numStacks = effectiveStacks.length; + const effectiveFacets = facets || ['undefined']; + const numFacets = effectiveFacets.length; - // Fill in 0s and make sure that the stacks are in the same order + // Fill in 0s and make sure that the facets are in the same order const fullTimeIntervals = groupBy( stackedData, d => String(d.start), dataForStart => { - if (numStacks === 1) return [dataForStart[0]]; - const stackToDatum = lookupBy(dataForStart, d => d.stack!); - return effectiveStacks.map( - (stack, stackIndex) => - stackToDatum[stack] || { + if (numFacets === 1) return [dataForStart[0]]; + const facetToDatum = lookupBy(dataForStart, d => d.facet!); + return effectiveFacets.map( + (facet, facetIndex) => + facetToDatum[facet] || { ...dataForStart[0], - stack, + facet, measure: 0, offset: isStacked ? Math.max( 0, - ...filterMap(effectiveStacks.slice(0, stackIndex), s => stackToDatum[s]).map( + ...filterMap(effectiveFacets.slice(0, facetIndex), s => facetToDatum[s]).map( d => d.offset + d.measure, ), ) @@ -423,9 +423,9 @@ export const ContinuousChartRender = function ContinuousChartRender( ); // Add nulls to mark gaps in data - const seriesForStack: Record = {}; - for (const stack of effectiveStacks) { - seriesForStack[stack] = []; + const seriesForFacet: Record = {}; + for (const stack of effectiveFacets) { + seriesForFacet[stack] = []; } let lastDatum: StackedRangeDatum | undefined; @@ -433,19 +433,19 @@ export const ContinuousChartRender = function ContinuousChartRender( const datum = fullTimeInterval[0]; if (lastDatum && lastDatum.start !== datum.end) { - for (const stack of effectiveStacks) { - seriesForStack[stack].push(null); + for (const facet of effectiveFacets) { + seriesForFacet[facet].push(null); } } - for (let i = 0; i < numStacks; i++) { - seriesForStack[effectiveStacks[i]].push(fullTimeInterval[i]); + for (let i = 0; i < numFacets; i++) { + seriesForFacet[effectiveFacets[i]].push(fullTimeInterval[i]); } lastDatum = datum; } - return Object.values(seriesForStack); - }, [markType, stackedData, stacks]); + return Object.values(seriesForFacet); + }, [markType, stackedData, facets]); if (innerStage.isInvalid()) return; @@ -534,7 +534,7 @@ export const ContinuousChartRender = function ContinuousChartRender( title, text: ( <> - {selectedDatum?.stack &&
{selectedDatum?.stack}
} + {selectedDatum?.facet &&
{selectedDatum?.facet}
}
{info}
{selection.finalized && (
@@ -569,8 +569,6 @@ export const ContinuousChartRender = function ContinuousChartRender( shiftEndForward < nowDayCeil.valueOf() ? shiftEndForward : nowDayCeil.valueOf(), ]; - console.log(byStack); - const nowX = timeScale(now); return (
@@ -610,13 +608,13 @@ export const ContinuousChartRender = function ContinuousChartRender( if (!r) return; return ( )} {markType === 'area' && - byStack.map(ds => { - const stack = ds[0]!.stack; + byFacet.map(ds => { + const facet = ds[0]!.facet; return ( { - const stack = ds[0]!.stack; + byFacet.map(ds => { + const facet = ds[0]!.facet; return ( + byFacet.flatMap(ds => filterMap(ds, (d, i) => { if (!d || ds[i - 1] || ds[i + 1]) return; // Not a single point const x = timeScale((d.start + d.end) / 2); return ( ({ important: true, optionLabels: g => (g === 'auto' ? 'Auto' : new Duration(g).getDescription(true)), }, - splitColumn: { + facetColumn: { type: 'expression', - label: 'Stack by', + label: 'Facet by', transferGroup: 'show', important: true, + legacyName: 'splitColumn', }, - numberToStack: { + maxFacets: { type: 'number', - label: 'Max stacks', defaultValue: 7, min: 1, required: true, - visible: ({ parameterValues }) => Boolean(parameterValues.splitColumn), + visible: ({ parameterValues }) => Boolean(parameterValues.facetColumn), + legacyName: 'numberToStack', }, showOthers: { type: 'boolean', defaultValue: true, - visible: ({ parameterValues }) => Boolean(parameterValues.splitColumn), + visible: ({ parameterValues }) => Boolean(parameterValues.facetColumn), }, measure: { type: 'measure', @@ -186,7 +187,7 @@ ModuleRepository.registerModule({ ) : parameterValues.granularity; - const { splitColumn, numberToStack, showOthers, measure, markType } = parameterValues; + const { facetColumn, maxFacets, showOthers, measure, markType } = parameterValues; const dataQuery = useMemo(() => { return { @@ -195,8 +196,8 @@ ModuleRepository.registerModule({ where, timeGranularity, measure, - splitExpression: splitColumn?.expression, - numberToStack, + facetExpression: facetColumn?.expression, + maxFacets, showOthers, oneExtra: markType !== 'bar', }; @@ -206,8 +207,8 @@ ModuleRepository.registerModule({ where, timeGranularity, measure, - splitColumn, - numberToStack, + facetColumn, + maxFacets, showOthers, markType, ]); @@ -221,8 +222,8 @@ ModuleRepository.registerModule({ where, timeGranularity, measure, - splitExpression, - numberToStack, + facetExpression, + maxFacets, showOthers, oneExtra, }, @@ -234,15 +235,17 @@ ModuleRepository.registerModule({ const granularity = new Duration(timeGranularity); - const vs: string[] | undefined = splitExpression + const detectedFacets: string[] | undefined = facetExpression ? ( await runSqlQuery( { query: querySource .getInitQuery(where) - .addSelect(splitExpression.cast('VARCHAR').as('v'), { addToGroupBy: 'end' }) + .addSelect(facetExpression.cast('VARCHAR').as(FACET_NAME), { + addToGroupBy: 'end', + }) .changeOrderByExpression(measure.expression.toOrderByExpression('DESC')) - .changeLimitValue(numberToStack + (showOthers ? 1 : 0)), // If we want to show others add 1 to check if we need to query for them + .changeLimitValue(maxFacets + (showOthers ? 1 : 0)), // If we want to show others add 1 to check if we need to query for them timezone, }, cancelToken, @@ -252,50 +255,54 @@ ModuleRepository.registerModule({ cancelToken.throwIfRequested(); - if (vs?.length === 0) { - // If vs is empty then there is no data at all and no need to do a larger query + if (detectedFacets?.length === 0) { + // If detectedFacets is empty then there is no data at all and no need to do a larger query return { - effectiveVs: [], + effectiveFacets: [], sourceData: [], measure, granularity, }; } - const queryVs = - showOthers && vs && numberToStack < vs.length ? vs.slice(0, numberToStack) : undefined; - const effectiveVs = queryVs ? queryVs.concat(OTHER_VALUE) : vs; + const facetsToQuery = + showOthers && detectedFacets && maxFacets < detectedFacets.length + ? detectedFacets.slice(0, maxFacets) + : undefined; + const effectiveFacets = facetsToQuery ? facetsToQuery.concat(OTHER_VALUE) : detectedFacets; const result = await runSqlQuery( { query: querySource .getInitQuery(overqueryWhere(where, timeColumnName, granularity, oneExtra)) - .applyIf(splitExpression && vs && !queryVs, q => - q.addWhere(splitExpression!.cast('VARCHAR').in(vs!)), + .applyIf(facetExpression && detectedFacets && !facetsToQuery, q => + q.addWhere(facetExpression!.cast('VARCHAR').in(detectedFacets!)), ) .addSelect(F.timeFloor(C(timeColumnName), L(timeGranularity)).as(TIME_NAME), { addToGroupBy: 'end', addToOrderBy: 'end', direction: 'DESC', }) - .applyIf(splitExpression, q => { - if (!splitExpression) return q; // Should never get here, doing this to make peace between eslint and TS + .applyIf(facetExpression, q => { + if (!facetExpression) return q; // Should never get here, doing this to make peace between eslint and TS return q.addSelect( - (queryVs + (facetsToQuery ? SqlCase.ifThenElse( - splitExpression.in(queryVs), - splitExpression, + facetExpression.in(facetsToQuery), + facetExpression, L(OTHER_VALUE), ) - : splitExpression + : facetExpression ) .cast('VARCHAR') - .as(STACK_NAME), + .as(FACET_NAME), { addToGroupBy: 'end' }, ); }) .addSelect(measure.expression.as(MEASURE_NAME)) - .changeLimitValue(10000 * (effectiveVs ? Math.min(effectiveVs.length, 10) : 1)), + .changeLimitValue( + 10000 * (effectiveFacets ? Math.min(effectiveFacets.length, 10) : 1), + ), timezone, }, cancelToken, @@ -306,12 +313,12 @@ ModuleRepository.registerModule({ start: b[TIME_NAME].valueOf(), end: granularity.shift(b[TIME_NAME], Timezone.UTC, 1).valueOf(), measure: b[MEASURE_NAME], - stack: b[STACK_NAME], + facet: b[FACET_NAME], }), ); return { - effectiveVs, + effectiveFacets, sourceData: dataset, measure, granularity, @@ -332,7 +339,7 @@ ModuleRepository.registerModule({ {sourceData && ( Date: Thu, 20 Mar 2025 13:59:51 -0700 Subject: [PATCH 07/13] fix hover part 1 --- .../continuous-chart-render.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx index 7117fe84c286..9e7a1bf7d478 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx @@ -232,13 +232,22 @@ export const ContinuousChartRender = function ContinuousChartRender( } }, [data, facets, markType]); - function findStackedDatum(time: number, measure: number): StackedRangeDatum | undefined { + function findStackedDatum( + time: number, + measure: number, + isStacked: boolean, + ): StackedRangeDatum | undefined { const dataInRange = stackedData.filter(d => d.start <= time && time < d.end); if (!dataInRange.length) return; - return ( - dataInRange.find(r => r.offset <= measure && measure < r.measure + r.offset) || - dataInRange[dataInRange.length - 1] - ); + if (isStacked) { + return ( + dataInRange.find(r => r.offset <= measure && measure < r.measure + r.offset) || + dataInRange[dataInRange.length - 1] + ); + } else { + // ToDo: min + return undefined; + } } const facetColorizer = useMemo(() => { From 2ede6a772f378d962551ecffd30d6598022ec6f3 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 20 Mar 2025 15:40:55 -0700 Subject: [PATCH 08/13] line hover part 2 --- web-console/src/utils/general.tsx | 17 +++++++++++++++++ .../continuous-chart-render.tsx | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx index 8af818664fd1..d204c7a89e07 100644 --- a/web-console/src/utils/general.tsx +++ b/web-console/src/utils/general.tsx @@ -458,6 +458,23 @@ export function findMap( return filterMap(xs, f)[0]; } +export function minBy(xs: T[], f: (item: T, index: number) => number): T | undefined { + if (!xs.length) return undefined; + + let minItem = xs[0]; + let minValue = f(xs[0], 0); + + for (let i = 1; i < xs.length; i++) { + const currentValue = f(xs[i], i); + if (currentValue < minValue) { + minValue = currentValue; + minItem = xs[i]; + } + } + + return minItem; +} + export function changeByIndex( xs: readonly T[], i: number, diff --git a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx index 9e7a1bf7d478..c0d759530d50 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/continuous-chart-render.tsx @@ -42,6 +42,7 @@ import { formatStartDuration, groupBy, lookupBy, + minBy, tickFormatWithTimezone, timezoneAwareTicks, } from '../../../../utils'; @@ -245,8 +246,7 @@ export const ContinuousChartRender = function ContinuousChartRender( dataInRange[dataInRange.length - 1] ); } else { - // ToDo: min - return undefined; + return minBy(dataInRange, r => Math.abs(r.measure - measure)); } } @@ -351,7 +351,7 @@ export const ContinuousChartRender = function ContinuousChartRender( setSelectionIfNeeded({ start: start.valueOf(), end: end.valueOf(), - selectedDatum: findStackedDatum(time, measure), + selectedDatum: findStackedDatum(time, measure, markType !== 'line'), }); } else { setSelection(undefined); From ff9bfb59fa99cb03ab1e6fb72c8985def4bd870b Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Fri, 21 Mar 2025 11:08:43 -0700 Subject: [PATCH 09/13] start adding moduleWhere --- .../components/module-pane/module-pane.tsx | 3 +- .../views/explore-view/models/module-state.ts | 19 +++++++----- .../module-repository/module-repository.ts | 1 + .../bar-chart-module/bar-chart-module.tsx | 25 ++++++++++++++-- .../grouping-table-module.tsx | 15 +++++----- .../multi-axis-chart-module.tsx | 15 ++++++++-- .../pie-chart-module/pie-chart-module.tsx | 30 ++++++++++++------- .../record-table-module.tsx | 15 ++++++++-- .../time-chart-module/time-chart-module.tsx | 19 ++++++++++-- 9 files changed, 104 insertions(+), 38 deletions(-) diff --git a/web-console/src/views/explore-view/components/module-pane/module-pane.tsx b/web-console/src/views/explore-view/components/module-pane/module-pane.tsx index b06e7b1c3465..0db1abefdadb 100644 --- a/web-console/src/views/explore-view/components/module-pane/module-pane.tsx +++ b/web-console/src/views/explore-view/components/module-pane/module-pane.tsx @@ -115,7 +115,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { onAddToSourceQueryAsColumn, onAddToSourceQueryAsMeasure, } = props; - const { moduleId, parameterValues, showControls } = moduleState; + const { moduleId, moduleWhere, parameterValues, showControls } = moduleState; const [stage, setStage] = useState(); const module = ModuleRepository.getModule(moduleId); @@ -174,6 +174,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { timezone, where, setWhere, + moduleWhere, parameterValues: parameterValuesWithDefaults, setParameterValues: updateParameterValues, runSqlQuery, diff --git a/web-console/src/views/explore-view/models/module-state.ts b/web-console/src/views/explore-view/models/module-state.ts index 93959adea6d5..5e541f072a19 100644 --- a/web-console/src/views/explore-view/models/module-state.ts +++ b/web-console/src/views/explore-view/models/module-state.ts @@ -16,9 +16,9 @@ * limitations under the License. */ -import type { Column, SqlExpression } from 'druid-query-toolkit'; +import type { Column } from 'druid-query-toolkit'; +import { SqlExpression, SqlLiteral } from 'druid-query-toolkit'; -import { isEmpty } from '../../../utils'; import { ModuleRepository } from '../module-repository/module-repository'; import type { Rename } from '../utils'; @@ -30,6 +30,7 @@ import type { QuerySource } from './query-source'; interface ModuleStateValue { moduleId: string; + moduleWhere?: SqlExpression; parameterValues: ParameterValues; showControls?: boolean; } @@ -44,16 +45,19 @@ export class ModuleState { ); return new ModuleState({ ...js, + moduleWhere: SqlExpression.maybeParse(js.moduleWhere), parameterValues: inflatedParameterValues, }); } public readonly moduleId: string; + public readonly moduleWhere: SqlExpression; public readonly parameterValues: ParameterValues; public readonly showControls: boolean; constructor(value: ModuleStateValue) { this.moduleId = value.moduleId; + this.moduleWhere = value.moduleWhere || SqlLiteral.TRUE; this.parameterValues = value.parameterValues; this.showControls = Boolean(value.showControls); } @@ -63,6 +67,7 @@ export class ModuleState { moduleId: this.moduleId, parameterValues: this.parameterValues, }; + if (!SqlLiteral.isTrue(this.moduleWhere)) value.moduleWhere = this.moduleWhere; if (this.showControls) value.showControls = true; return value; } @@ -92,17 +97,19 @@ export class ModuleState { } public restrictToQuerySource(querySource: QuerySource, where: SqlExpression): ModuleState { - const { moduleId, parameterValues } = this; + const { moduleId, moduleWhere, parameterValues } = this; const module = ModuleRepository.getModule(moduleId); if (!module) return this; + const newModuleWhere = querySource.restrictWhere(moduleWhere); const newParameterValues = querySource.restrictParameterValues( parameterValues, module.parameters, where, ); - if (parameterValues === newParameterValues) return this; + if (moduleWhere === newModuleWhere && parameterValues === newParameterValues) return this; return this.change({ + moduleWhere: newModuleWhere, parameterValues: newParameterValues, }); } @@ -161,10 +168,6 @@ export class ModuleState { }, }); } - - public isInitState(): boolean { - return this.moduleId === 'record-table' && isEmpty(this.parameterValues); - } } ModuleState.INIT_STATE = new ModuleState({ diff --git a/web-console/src/views/explore-view/module-repository/module-repository.ts b/web-console/src/views/explore-view/module-repository/module-repository.ts index 11bd25145a5c..698dcf036722 100644 --- a/web-console/src/views/explore-view/module-repository/module-repository.ts +++ b/web-console/src/views/explore-view/module-repository/module-repository.ts @@ -38,6 +38,7 @@ interface ModuleComponentProps

{ timezone: Timezone; where: SqlExpression; setWhere(where: SqlExpression): void; + moduleWhere: SqlExpression; parameterValues: P; setParameterValues: (parameters: Partial

) => void; runSqlQuery( diff --git a/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx b/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx index bdf94e7196ea..6d08c77f9877 100644 --- a/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx @@ -96,7 +96,16 @@ ModuleRepository.registerModule({ }, }, component: function BarChartModule(props) { - const { querySource, timezone, where, setWhere, parameterValues, stage, runSqlQuery } = props; + const { + querySource, + timezone, + where, + setWhere, + moduleWhere, + parameterValues, + stage, + runSqlQuery, + } = props; const containerRef = useRef(); const chartRef = useRef(); const [highlight, setHighlight] = useState(); @@ -108,7 +117,7 @@ ModuleRepository.registerModule({ return { query: querySource - .getInitQuery(where) + .getInitQuery(where.and(moduleWhere)) .addSelect( splitExpression.applyIf(timeBucket, ex => F.timeFloor(ex, timeBucket)).as('dim'), { @@ -127,7 +136,17 @@ ModuleRepository.registerModule({ .changeLimitValue(limit), timezone, }; - }, [querySource, timezone, where, splitColumn, timeBucket, measure, measureToSort, limit]); + }, [ + querySource, + timezone, + where, + moduleWhere, + splitColumn, + timeBucket, + measure, + measureToSort, + limit, + ]); const [sourceDataState, queryManager] = useQueryManager({ query: dataQuery, diff --git a/web-console/src/views/explore-view/modules/grouping-table-module/grouping-table-module.tsx b/web-console/src/views/explore-view/modules/grouping-table-module/grouping-table-module.tsx index eeeee9252493..a06b6152722a 100644 --- a/web-console/src/views/explore-view/modules/grouping-table-module/grouping-table-module.tsx +++ b/web-console/src/views/explore-view/modules/grouping-table-module/grouping-table-module.tsx @@ -47,7 +47,7 @@ const NEEDS_GROUPING_TO_ORDER = true; interface QueryAndMore { timezone: Timezone; - originalWhere: SqlExpression; + globalWhere: SqlExpression; queryAndHints: QueryAndHints; } @@ -213,6 +213,7 @@ ModuleRepository.registerModule({ timezone, where, setWhere, + moduleWhere, parameterValues, setParameterValues, runSqlQuery, @@ -224,13 +225,13 @@ ModuleRepository.registerModule({ if (!pivotColumn) return; return querySource - .getInitQuery(where) + .getInitQuery(where.and(moduleWhere)) .addSelect(pivotColumn.expression.as('v'), { addToGroupBy: 'end' }) .changeOrderByExpression( (measures.length ? measures[0].expression : F.count()).toOrderByExpression('DESC'), ) .changeLimitValue(maxPivotValues); - }, [querySource, where, parameterValues]); + }, [querySource, where, moduleWhere, parameterValues]); const [pivotValueState, queryManager] = useQueryManager({ query: pivotValueQuery, @@ -249,10 +250,10 @@ ModuleRepository.registerModule({ return { timezone, - originalWhere: where, + globalWhere: where, queryAndHints: makeTableQueryAndHints({ source: querySource.query, - where, + where: where.and(moduleWhere), splitColumns: parameterValues.splitColumns, timeBucket: parameterValues.timeBucket, showColumns: parameterValues.showColumns, @@ -274,13 +275,13 @@ ModuleRepository.registerModule({ const [resultState] = useQueryManager({ query: queryAndMore, processQuery: async (queryAndMore, cancelToken) => { - const { timezone, originalWhere, queryAndHints } = queryAndMore; + const { timezone, globalWhere, queryAndHints } = queryAndMore; const { query, columnHints } = queryAndHints; let result = await runSqlQuery({ query, timezone }, cancelToken); if (result.sqlQuery) { result = result.attachQuery( { query: '' }, - result.sqlQuery.changeWhereExpression(originalWhere), + result.sqlQuery.changeWhereExpression(globalWhere), ); } return { diff --git a/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx b/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx index bffb93677aaa..2dff7e42eeb7 100644 --- a/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx @@ -71,7 +71,16 @@ ModuleRepository.registerModule({ }, }, component: function MultiAxisChartModule(props) { - const { querySource, timezone, where, setWhere, parameterValues, stage, runSqlQuery } = props; + const { + querySource, + timezone, + where, + setWhere, + moduleWhere, + parameterValues, + stage, + runSqlQuery, + } = props; const containerRef = useRef(); const chartRef = useRef(); const [highlight, setHighlight] = useState(); @@ -87,7 +96,7 @@ ModuleRepository.registerModule({ const dataQuery = useMemo(() => { return { query: querySource - .getInitQuery(where) + .getInitQuery(where.and(moduleWhere)) .addSelect(F.timeFloor(C(timeColumnName || '__time'), L(timeGranularity)).as('time'), { addToGroupBy: 'end', addToOrderBy: 'end', @@ -96,7 +105,7 @@ ModuleRepository.registerModule({ .applyForEach(measures, (q, measure) => q.addSelect(measure.expression.as(measure.name))), timezone, }; - }, [querySource, timezone, where, timeColumnName, timeGranularity, measures]); + }, [querySource, timezone, where, moduleWhere, timeColumnName, timeGranularity, measures]); const [sourceDataState, queryManager] = useQueryManager({ query: dataQuery, diff --git a/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx b/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx index 69c9b9278799..b98c876fb334 100644 --- a/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx @@ -97,7 +97,8 @@ ModuleRepository.registerModule({ }, }, component: function PieChartModule(props) { - const { querySource, where, setWhere, parameterValues, stage, runSqlQuery } = props; + const { querySource, where, setWhere, moduleWhere, parameterValues, stage, runSqlQuery } = + props; const containerRef = useRef(); const chartRef = useRef(); const [highlight, setHighlight] = useState(); @@ -106,34 +107,43 @@ ModuleRepository.registerModule({ const dataQueries = useMemo(() => { const splitExpression = splitColumn ? splitColumn.expression : L(OVERALL_LABEL); + const effectiveWhere = where.and(moduleWhere); return { mainQuery: querySource - .getInitQuery(where) + .getInitQuery(effectiveWhere) .addSelect(F.cast(splitExpression, 'VARCHAR').as('name'), { addToGroupBy: 'end' }) .addSelect(measure.expression.as('value'), { addToOrderBy: 'end', direction: 'DESC', }) - .changeLimitValue(limit), + .changeLimitValue(limit + (showOthers ? 1 : 0)), + limit, splitExpression: splitColumn?.expression, othersPartialQuery: showOthers - ? querySource.getInitQuery(where).addSelect(measure.expression.as('value')) + ? querySource.getInitQuery(effectiveWhere).addSelect(measure.expression.as('value')) : undefined, }; - }, [querySource, where, splitColumn, measure, limit, showOthers]); + }, [querySource, where, moduleWhere, splitColumn, measure, limit, showOthers]); const [sourceDataState, queryManager] = useQueryManager({ query: dataQueries, - processQuery: async ({ mainQuery, splitExpression, othersPartialQuery }, cancelToken) => { + processQuery: async ( + { mainQuery, limit, splitExpression, othersPartialQuery }, + cancelToken, + ) => { const result = await runSqlQuery({ query: mainQuery }, cancelToken); const data = result.toObjectArray(); if (splitExpression && othersPartialQuery) { - const othersResult = await runSqlQuery({ - query: othersPartialQuery.addWhere(splitExpression.notIn(result.getColumnByIndex(0)!)), - }); - data.push({ name: 'Others', value: othersResult.rows[0][0], __isOthers: true }); + const pieValues = result.getColumnByIndex(0)!; + + if (pieValues.length > limit) { + const othersResult = await runSqlQuery({ + query: othersPartialQuery.addWhere(splitExpression.notIn(pieValues.slice(0, limit))), + }); + data.push({ name: 'Others', value: othersResult.rows[0][0], __isOthers: true }); + } } return data; diff --git a/web-console/src/views/explore-view/modules/record-table-module/record-table-module.tsx b/web-console/src/views/explore-view/modules/record-table-module/record-table-module.tsx index c9d842dfb9c1..822f01c329ee 100644 --- a/web-console/src/views/explore-view/modules/record-table-module/record-table-module.tsx +++ b/web-console/src/views/explore-view/modules/record-table-module/record-table-module.tsx @@ -67,11 +67,20 @@ ModuleRepository.registerModule({ }, }, component: function RecordTableModule(props) { - const { stage, querySource, timezone, where, setWhere, parameterValues, runSqlQuery } = props; + const { + stage, + querySource, + timezone, + where, + setWhere, + moduleWhere, + parameterValues, + runSqlQuery, + } = props; const query = useMemo((): string | undefined => { return SqlQuery.create(querySource.query) - .changeWhereExpression(where) + .changeWhereExpression(where.and(moduleWhere)) .changeLimitValue(parameterValues.maxRows) .applyIf( querySource.columns.some(e => e.name === '__time') && !parameterValues.ascending, @@ -79,7 +88,7 @@ ModuleRepository.registerModule({ q => q.changeOrderByClause(querySource.query.orderByClause), ) .toString(); - }, [querySource, where, parameterValues]); + }, [querySource, where, moduleWhere, parameterValues]); const [resultState, queryManager] = useQueryManager({ query, diff --git a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx index 0859122da8c9..cd97284a770c 100644 --- a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx +++ b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx @@ -175,7 +175,16 @@ ModuleRepository.registerModule({ }, }, component: function TimeChartModule(props) { - const { querySource, timezone, where, setWhere, parameterValues, stage, runSqlQuery } = props; + const { + querySource, + timezone, + where, + setWhere, + moduleWhere, + parameterValues, + stage, + runSqlQuery, + } = props; const timeColumnName = querySource.columns.find(column => column.sqlType === 'TIMESTAMP')?.name; const timeGranularity = @@ -194,6 +203,7 @@ ModuleRepository.registerModule({ querySource, timezone, where, + moduleWhere, timeGranularity, measure, facetExpression: facetColumn?.expression, @@ -205,6 +215,7 @@ ModuleRepository.registerModule({ querySource, timezone, where, + moduleWhere, timeGranularity, measure, facetColumn, @@ -220,6 +231,7 @@ ModuleRepository.registerModule({ querySource, timezone, where, + moduleWhere, timeGranularity, measure, facetExpression, @@ -233,6 +245,7 @@ ModuleRepository.registerModule({ throw new Error(`Must have a column of type TIMESTAMP for the time chart to work`); } + const effectiveWhere = where.and(moduleWhere); const granularity = new Duration(timeGranularity); const detectedFacets: string[] | undefined = facetExpression @@ -240,7 +253,7 @@ ModuleRepository.registerModule({ await runSqlQuery( { query: querySource - .getInitQuery(where) + .getInitQuery(effectiveWhere) .addSelect(facetExpression.cast('VARCHAR').as(FACET_NAME), { addToGroupBy: 'end', }) @@ -274,7 +287,7 @@ ModuleRepository.registerModule({ const result = await runSqlQuery( { query: querySource - .getInitQuery(overqueryWhere(where, timeColumnName, granularity, oneExtra)) + .getInitQuery(overqueryWhere(effectiveWhere, timeColumnName, granularity, oneExtra)) .applyIf(facetExpression && detectedFacets && !facetsToQuery, q => q.addWhere(facetExpression!.cast('VARCHAR').in(detectedFacets!)), ) From 1e7c02ac78a575d7f3c0d05c389b23d226369433 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 25 Mar 2025 16:25:26 -0700 Subject: [PATCH 10/13] info popover --- .../components/preview-pane/preview-pane.scss | 6 ++++++ .../components/preview-pane/preview-pane.tsx | 12 ++++++++++-- .../resource-pane/column-dialog/column-dialog.tsx | 7 ++++++- .../resource-pane/measure-dialog/measure-dialog.tsx | 6 +++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/web-console/src/views/explore-view/components/preview-pane/preview-pane.scss b/web-console/src/views/explore-view/components/preview-pane/preview-pane.scss index eff7363ad647..8749d7419a18 100644 --- a/web-console/src/views/explore-view/components/preview-pane/preview-pane.scss +++ b/web-console/src/views/explore-view/components/preview-pane/preview-pane.scss @@ -21,6 +21,12 @@ display: flex; flex-direction: column; + .info-popover { + position: absolute; + top: 6px; + right: 6px; + } + .preview-values-wrapper { flex: 1; position: relative; diff --git a/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx b/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx index 051653c6882a..bb70bda5544a 100644 --- a/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx +++ b/web-console/src/views/explore-view/components/preview-pane/preview-pane.tsx @@ -16,12 +16,14 @@ * limitations under the License. */ -import { Callout } from '@blueprintjs/core'; +import { Button, Callout, Popover } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; import type { QueryResult, SqlQuery } from 'druid-query-toolkit'; import { dedupe } from 'druid-query-toolkit'; import React from 'react'; +import { PopoverText } from '../../../../components'; import { useQueryManager } from '../../../../hooks'; import { formatEmpty } from '../../../../utils'; @@ -40,10 +42,11 @@ export interface PreviewPaneProps { previewQuery: string | undefined; runSqlQuery(query: string | SqlQuery): Promise; deduplicate?: boolean; + info?: string; } export const PreviewPane = React.memo(function PreviewPane(props: PreviewPaneProps) { - const { previewQuery, runSqlQuery, deduplicate } = props; + const { previewQuery, runSqlQuery, deduplicate, info } = props; const [previewState] = useQueryManager({ query: previewQuery, @@ -55,6 +58,11 @@ export const PreviewPane = React.memo(function PreviewPane(props: PreviewPanePro const previewValues = previewState.data ? getPreviewValues(previewState.data) : undefined; return ( + {info && ( + {info}}> +

- +
diff --git a/web-console/src/views/explore-view/components/resource-pane/measure-dialog/measure-dialog.tsx b/web-console/src/views/explore-view/components/resource-pane/measure-dialog/measure-dialog.tsx index 5300c33ad85b..5299a5dca2ec 100644 --- a/web-console/src/views/explore-view/components/resource-pane/measure-dialog/measure-dialog.tsx +++ b/web-console/src/views/explore-view/components/resource-pane/measure-dialog/measure-dialog.tsx @@ -88,7 +88,11 @@ export const MeasureDialog = React.memo(function MeasureDialog(props: MeasureDia />
- +
From bbc0b09e1fbaf2f7580f8b3808ed3ef46de847e5 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 27 Mar 2025 08:30:15 -0700 Subject: [PATCH 11/13] add filter icon --- .../views/explore-view/components/module-pane/module-pane.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web-console/src/views/explore-view/components/module-pane/module-pane.tsx b/web-console/src/views/explore-view/components/module-pane/module-pane.tsx index 0db1abefdadb..93def0fe06a3 100644 --- a/web-console/src/views/explore-view/components/module-pane/module-pane.tsx +++ b/web-console/src/views/explore-view/components/module-pane/module-pane.tsx @@ -283,6 +283,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { >
+ + + { + setModuleState( + moduleState.changeParameterValues(getStickyParameterValuesForModule(moduleId)), + ); + }} + /> + + + } + > +