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/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/column-picker-menu/column-picker-menu.tsx b/web-console/src/views/explore-view/components/column-picker-menu/column-picker-menu.tsx index 7634cbae4ccd..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 @@ -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} - /> + content={ + + 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/contains-filter-control/contains-filter-control.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/contains-filter-control/contains-filter-control.tsx index b3b6892bb4bd..15b9a75cadc4 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/contains-filter-control/contains-filter-control.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/contains-filter-control/contains-filter-control.tsx @@ -30,6 +30,7 @@ import './contains-filter-control.scss'; export interface ContainsFilterControlProps { querySource: QuerySource; + extraFilter: SqlExpression; filter: SqlExpression; filterPattern: ContainsFilterPattern; setFilterPattern(filterPattern: ContainsFilterPattern): void; @@ -39,7 +40,7 @@ export interface ContainsFilterControlProps { export const ContainsFilterControl = React.memo(function ContainsFilterControl( props: ContainsFilterControlProps, ) { - const { querySource, filter, filterPattern, setFilterPattern, runSqlQuery } = props; + const { querySource, extraFilter, filter, filterPattern, setFilterPattern, runSqlQuery } = props; const { column, negated, contains } = filterPattern; const previewQuery = useMemo( @@ -47,6 +48,7 @@ export const ContainsFilterControl = React.memo(function ContainsFilterControl( querySource .getInitQuery( SqlExpression.and( + extraFilter, filter, contains ? filterPatternToExpression(filterPattern) : undefined, ), @@ -56,7 +58,7 @@ export const ContainsFilterControl = React.memo(function ContainsFilterControl( .changeLimitValue(101) .toString(), // eslint-disable-next-line react-hooks/exhaustive-deps -- exclude 'makePattern' from deps - [querySource.query, filter, column, contains, negated], + [querySource.query, extraFilter, filter, column, contains, negated], ); const [previewState] = useQueryManager({ 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..39cff40d2a97 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 @@ -108,6 +108,7 @@ type FilterMenuTab = 'compose' | 'sql'; export interface FilterMenuProps { querySource: QuerySource; + extraFilter: SqlExpression; filter: SqlExpression; initPattern?: FilterPattern; onPatternChange(newPattern: FilterPattern): void; @@ -121,6 +122,7 @@ export interface FilterMenuProps { export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) { const { querySource, + extraFilter, filter, initPattern, onPatternChange, @@ -136,8 +138,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(); @@ -151,6 +163,7 @@ export const FilterMenu = React.memo(function FilterMenu(props: FilterMenuProps) cont = ( ); break; @@ -281,6 +297,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 +433,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/regexp-filter-control/regexp-filter-control.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-menu/regexp-filter-control/regexp-filter-control.tsx index 23d67ff2ec5a..fa0820667e2d 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-menu/regexp-filter-control/regexp-filter-control.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-menu/regexp-filter-control/regexp-filter-control.tsx @@ -39,6 +39,7 @@ function regexpIssue(possibleRegexp: string): string | undefined { export interface RegexpFilterControlProps { querySource: QuerySource; + extraFilter: SqlExpression; filter: SqlExpression; filterPattern: RegexpFilterPattern; setFilterPattern(filterPattern: RegexpFilterPattern): void; @@ -48,21 +49,25 @@ export interface RegexpFilterControlProps { export const RegexpFilterControl = React.memo(function RegexpFilterControl( props: RegexpFilterControlProps, ) { - const { querySource, filter, filterPattern, setFilterPattern, runSqlQuery } = props; + const { querySource, extraFilter, filter, filterPattern, setFilterPattern, runSqlQuery } = props; const { column, negated, regexp } = filterPattern; const previewQuery = useMemo( () => querySource .getInitQuery( - SqlExpression.and(filter, regexp ? filterPatternToExpression(filterPattern) : undefined), + SqlExpression.and( + extraFilter, + filter, + regexp ? filterPatternToExpression(filterPattern) : undefined, + ), ) .addSelect(F.cast(C(column), 'VARCHAR').as('c'), { addToGroupBy: 'end' }) .changeOrderByExpression(F.count().toOrderByExpression('DESC')) .changeLimitValue(101) .toString(), // eslint-disable-next-line react-hooks/exhaustive-deps -- exclude 'makePattern' from deps - [querySource.query, filter, column, regexp, negated], + [querySource.query, extraFilter, filter, column, regexp, negated], ); const [previewState] = useQueryManager({ 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..cf4701fe4563 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'; @@ -33,6 +33,7 @@ import './values-filter-control.scss'; export interface ValuesFilterControlProps { querySource: QuerySource; + extraFilter: SqlExpression; filter: SqlExpression; filterPattern: ValuesFilterPattern; setFilterPattern(filterPattern: ValuesFilterPattern): void; @@ -42,7 +43,7 @@ export interface ValuesFilterControlProps { export const ValuesFilterControl = React.memo(function ValuesFilterControl( props: ValuesFilterControlProps, ) { - const { querySource, filter, filterPattern, setFilterPattern, runSqlQuery } = props; + const { querySource, extraFilter, filter, filterPattern, setFilterPattern, runSqlQuery } = props; const { column, negated, values: selectedValues } = filterPattern; const [initValues] = useState(selectedValues); const [searchString, setSearchString] = useState(''); @@ -52,6 +53,7 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl( querySource .getInitQuery( SqlExpression.and( + extraFilter, filter, searchString ? F('ICONTAINS_STRING', C(column), searchString) : undefined, ), @@ -61,7 +63,7 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl( .changeLimitValue(101) .toString(), // eslint-disable-next-line react-hooks/exhaustive-deps - [querySource.query, filter, column, searchString], + [querySource.query, extraFilter, filter, column, searchString], ); const [valuesState] = useQueryManager({ @@ -90,24 +92,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/filter-pane/filter-pane.tsx b/web-console/src/views/explore-view/components/filter-pane/filter-pane.tsx index cab39295b9fe..fa62e0d65cde 100644 --- a/web-console/src/views/explore-view/components/filter-pane/filter-pane.tsx +++ b/web-console/src/views/explore-view/components/filter-pane/filter-pane.tsx @@ -48,6 +48,7 @@ import './filter-pane.scss'; export interface FilterPaneProps { querySource: QuerySource | undefined; + extraFilter: SqlExpression; timezone: Timezone; filter: SqlExpression; onFilterChange(filter: SqlExpression): void; @@ -59,6 +60,7 @@ export interface FilterPaneProps { export const FilterPane = forwardRef(function FilterPane(props: FilterPaneProps, ref) { const { querySource, + extraFilter, timezone, filter, onFilterChange, @@ -139,6 +141,7 @@ export const FilterPane = forwardRef(function FilterPane(props: FilterPaneProps, content={ { @@ -201,6 +204,7 @@ export const FilterPane = forwardRef(function FilterPane(props: FilterPaneProps, content={ { @@ -220,7 +224,7 @@ export const FilterPane = forwardRef(function FilterPane(props: FilterPaneProps, text={patterns.length ? undefined : 'Add filter'} onClick={() => setMenuNew({})} minimal - data-tooltip="Add filter" + data-tooltip={patterns.length ? 'Add filter' : undefined} /> ) : ( @@ -229,7 +233,7 @@ export const FilterPane = forwardRef(function FilterPane(props: FilterPaneProps, text={patterns.length ? undefined : 'Add filter'} disabled minimal - data-tooltip="Add filter" + data-tooltip="No query source, unable to query" /> )} 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)} /> ); } diff --git a/web-console/src/views/explore-view/components/module-pane/module-pane.scss b/web-console/src/views/explore-view/components/module-pane/module-pane.scss index e74e3fe4bae3..0fa698cfc9b2 100644 --- a/web-console/src/views/explore-view/components/module-pane/module-pane.scss +++ b/web-console/src/views/explore-view/components/module-pane/module-pane.scss @@ -18,47 +18,71 @@ @import '../../../../variables'; -$tob-bar-height: 34px; +$filter-bar-height: 34px; +$control-bar-height: 34px; $control-pane-width: 240px; +$small-border: 1px; .module-pane { position: relative; @include card-like; overflow: hidden; - .module-top-bar { + .filter-pane { + position: absolute; + top: 0; + left: 0; + right: 90px; + height: $filter-bar-height; + border-bottom: $small-border solid $dark-gray2; + padding: 2px 0; + } + + .module-control-bar { position: absolute; top: 0; left: 0; width: 100%; - height: $tob-bar-height; - border-bottom: 1px solid $dark-gray2; + height: $control-bar-height; + border-bottom: $small-border solid $dark-gray2; padding: 2px; display: flex; - - .bar-expander { - flex: 1; - } } .control-pane-container { position: absolute; - top: $tob-bar-height + 1; + top: $control-bar-height + 1; bottom: 0; width: $control-pane-width; right: 0; - border-left: 1px solid $dark-gray2; + border-left: $small-border solid $dark-gray2; padding: 8px; overflow: auto; } &.show-controls .module-inner-container { - right: $control-pane-width + 1; + right: $control-pane-width + $small-border; + } + + &.show-filter { + .module-control-bar { + top: $filter-bar-height + $small-border; + } + + .module-inner-container { + top: $filter-bar-height + $small-border + $control-bar-height + $small-border; + } + } + + .corner-buttons { + position: absolute; + top: 2px; + right: 2px; } .module-inner-container { position: absolute; - top: $tob-bar-height + 1; + top: $control-bar-height + $small-border; bottom: 0; left: 0; right: 0; 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..96ed6e8fe90c 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 @@ -30,6 +30,7 @@ import { IconNames } from '@blueprintjs/icons'; import type { Timezone } from 'chronoshift'; import classNames from 'classnames'; import type { Column, QueryResult, SqlExpression, SqlQuery } from 'druid-query-toolkit'; +import { SqlLiteral } from 'druid-query-toolkit'; import React, { useState } from 'react'; import { useMemoWithPrevious } from '../../../../hooks'; @@ -53,6 +54,7 @@ import { ModuleRepository } from '../../module-repository/module-repository'; import { adjustTransferValue, normalizeType } from '../../utils'; import { ControlPane } from '../control-pane/control-pane'; import { DroppableContainer } from '../droppable-container/droppable-container'; +import { FilterPane } from '../filter-pane/filter-pane'; import { Issue } from '../issue/issue'; import { ModulePicker } from '../module-picker/module-picker'; @@ -115,7 +117,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { onAddToSourceQueryAsColumn, onAddToSourceQueryAsMeasure, } = props; - const { moduleId, parameterValues, showControls } = moduleState; + const { moduleId, moduleWhere, parameterValues, showModuleWhere, showControls } = moduleState; const [stage, setStage] = useState(); const module = ModuleRepository.getModule(moduleId); @@ -174,6 +176,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { timezone, where, setWhere, + moduleWhere, parameterValues: parameterValuesWithDefaults, setParameterValues: updateParameterValues, runSqlQuery, @@ -191,15 +194,27 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { setModuleState(moduleState.applyShowMeasure(measure)); } + const moduleHasFilter = !SqlLiteral.isTrue(moduleWhere); return (
-
+ {showModuleWhere && ( + setModuleState(moduleState.changeModuleWhere(newFilter))} + runSqlQuery={runSqlQuery} + /> + )} +
{ @@ -254,42 +269,49 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { onAddToSourceQueryAsMeasure={onAddToSourceQueryAsMeasure} /> )} -
- - - { - setModuleState( - moduleState.changeParameterValues( - getStickyParameterValuesForModule(moduleId), - ), - ); - }} - /> - - - } - > -
+ + + { + setModuleState( + moduleState.changeParameterValues(getStickyParameterValuesForModule(moduleId)), + ); + }} + /> + + + } + > +