Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web console: add suggestions for table status filtering. #17765

Merged
merged 5 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions web-console/src/components/menu-boolean/menu-boolean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ function toKey(value: boolean | undefined) {

const DEFAULT_OPTIONS_TEXT: Partial<Record<TrueFalseUndefined, string>> = { undefined: 'Auto' };

export const ENABLE_DISABLE_OPTIONS_TEXT: Partial<Record<TrueFalseUndefined, string>> = {
true: 'Enable',
false: 'Disable',
export const ENABLED_DISABLED_OPTIONS_TEXT: Partial<Record<TrueFalseUndefined, string>> = {
true: 'Enabled',
false: 'Disabled',
undefined: 'Auto',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,8 @@ export const SegmentBarChartRender = function SegmentBarChartRender(
className={classNames('load-rule', loadRuleToBaseType(loadRule))}
data-tooltip={title}
style={{
left: xWidth.x,
width: xWidth.width,
left: clamp(xWidth.x, 0, innerStage.width),
width: clamp(xWidth.width, 0, innerStage.width),
}}
>
{title}
Expand Down
53 changes: 53 additions & 0 deletions web-console/src/react-table/react-table-inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import classNames from 'classnames';
import { useEffect, useState } from 'react';
import type { Column, ReactTableFunction } from 'react-table';

import { filterMap, toggle } from '../utils';

import {
combineModeAndNeedle,
FILTER_MODES,
Expand Down Expand Up @@ -113,6 +115,57 @@ export function GenericFilterInput({ column, filter, onChange, key }: FilterRend
);
}

export function suggestibleFilterInput(suggestions: string[]) {
return function SuggestibleFilterInput({ filter, onChange, key, ...rest }: FilterRendererProps) {
let valuesFilteredOn: string[] | undefined;
if (filter) {
const modeAndNeedle = parseFilterModeAndNeedle(filter, true);
if (modeAndNeedle && modeAndNeedle.mode === '=') {
valuesFilteredOn = modeAndNeedle.needleParts;
}
}
return (
<Popover
key={key}
placement="bottom-start"
minimal
content={
<Menu>
{filterMap(suggestions, (suggestion, i) => {
return (
<MenuItem
key={i}
icon={
valuesFilteredOn
? valuesFilteredOn.includes(suggestion)
? IconNames.MINUS
: IconNames.PLUS
: IconNames.EQUALS
}
text={suggestion}
onClick={() =>
onChange(
combineModeAndNeedle(
'=',
valuesFilteredOn
? toggle(valuesFilteredOn, suggestion).join('|')
: suggestion,
true,
),
)
}
/>
);
})}
</Menu>
}
>
<GenericFilterInput filter={filter} onChange={onChange} {...rest} />
</Popover>
);
};
}

export function BooleanFilterInput({ filter, onChange, key }: FilterRendererProps) {
return (
<HTMLSelect
Expand Down
56 changes: 32 additions & 24 deletions web-console/src/react-table/react-table-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function filterModeToTitle(mode: FilterMode): string {
interface FilterModeAndNeedle {
mode: FilterMode;
needle: string;
needleParts: string[];
}

export function addFilter(
Expand All @@ -100,13 +101,16 @@ export function parseFilterModeAndNeedle(
if (!m) return;
if (!loose && !m[2]) return;
const mode = (m[1] as FilterMode) || '~';
const needle = m[2] || '';
return {
mode,
needle: m[2] || '',
needle,
needleParts: needle.split('|'),
};
}

export function combineModeAndNeedle(mode: FilterMode, needle: string): string {
export function combineModeAndNeedle(mode: FilterMode, needle: string, cleanup = false): string {
if (cleanup && needle === '') return '';
return `${mode}${needle}`;
}

Expand All @@ -116,60 +120,64 @@ export function addOrUpdateFilter(filters: readonly Filter[], filter: Filter): F

export function booleanCustomTableFilter(filter: Filter, value: unknown): boolean {
if (value == null) return false;
const modeAndNeedle = parseFilterModeAndNeedle(filter);
if (!modeAndNeedle) return true;
const { mode, needle } = modeAndNeedle;
const modeAndNeedles = parseFilterModeAndNeedle(filter);
if (!modeAndNeedles) return true;
const { mode, needleParts } = modeAndNeedles;
const strValue = String(value);
switch (mode) {
case '=':
return strValue === needle;
return needleParts.some(needle => strValue === needle);

case '!=':
return strValue !== needle;
return needleParts.every(needle => strValue !== needle);

case '<':
return strValue < needle;
return needleParts.some(needle => strValue < needle);

case '<=':
return strValue <= needle;
return needleParts.some(needle => strValue <= needle);

case '>':
return strValue > needle;
return needleParts.some(needle => strValue > needle);

case '>=':
return strValue >= needle;
return needleParts.some(needle => strValue >= needle);

default:
return caseInsensitiveContains(strValue, needle);
return needleParts.some(needle => caseInsensitiveContains(strValue, needle));
}
}

export function sqlQueryCustomTableFilter(filter: Filter): SqlExpression | undefined {
const modeAndNeedle = parseFilterModeAndNeedle(filter);
if (!modeAndNeedle) return;
const { mode, needle } = modeAndNeedle;
const modeAndNeedles = parseFilterModeAndNeedle(filter);
if (!modeAndNeedles) return;
const { mode, needleParts } = modeAndNeedles;
const column = C(filter.id);
switch (mode) {
case '=':
return column.equal(needle);
case '=': {
return SqlExpression.or(...needleParts.map(needle => column.equal(needle)));
}

case '!=':
return column.unequal(needle);
case '!=': {
return SqlExpression.and(...needleParts.map(needle => column.unequal(needle)));
}

case '<':
return column.lessThan(needle);
return SqlExpression.or(...needleParts.map(needle => column.lessThan(needle)));

case '<=':
return column.lessThanOrEqual(needle);
return SqlExpression.or(...needleParts.map(needle => column.lessThanOrEqual(needle)));

case '>':
return column.greaterThan(needle);
return SqlExpression.or(...needleParts.map(needle => column.greaterThan(needle)));

case '>=':
return column.greaterThanOrEqual(needle);
return SqlExpression.or(...needleParts.map(needle => column.greaterThanOrEqual(needle)));

default:
return F('LOWER', column).like(`%${needle.toLowerCase()}%`);
return SqlExpression.or(
...needleParts.map(needle => F('LOWER', column).like(`%${needle.toLowerCase()}%`)),
);
}
}

Expand Down
5 changes: 5 additions & 0 deletions web-console/src/utils/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,3 +686,8 @@ export function offsetToRowColumn(str: string, offset: number): RowColumn | unde
export function xor(a: unknown, b: unknown): boolean {
return Boolean(a ? !b : b);
}

export function toggle<T>(xs: readonly T[], x: T, eq?: (a: T, b: T) => boolean): T[] {
const e = eq || ((a, b) => a === b);
return xs.find(_ => e(_, x)) ? xs.filter(d => !e(d, x)) : xs.concat([x]);
}
14 changes: 12 additions & 2 deletions web-console/src/views/datasources-view/datasources-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
compact,
countBy,
deepGet,
findMap,
formatBytes,
formatInteger,
formatMillions,
Expand Down Expand Up @@ -1709,7 +1710,7 @@ GROUP BY 1, 2`;
}

render() {
const { capabilities, goToSegments } = this.props;
const { capabilities, filters, goToSegments } = this.props;
const {
showUnused,
visibleColumns,
Expand Down Expand Up @@ -1737,7 +1738,16 @@ GROUP BY 1, 2`;
label="Show segment timeline"
onChange={() =>
this.setState({
showSegmentTimeline: showSegmentTimeline ? undefined : { capabilities },
showSegmentTimeline: showSegmentTimeline
? undefined
: {
capabilities,
datasource: findMap(filters, filter =>
filter.id === 'datasource' && /^=[^=|]+$/.exec(String(filter.value))
? filter.value.slice(1)
: undefined,
),
},
})
}
disabled={!capabilities.hasSqlOrCoordinatorAccess()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import React, { useMemo, useState } from 'react';

import { ClearableInput } from '../../../../../../components';
import { useQueryManager } from '../../../../../../hooks';
import { caseInsensitiveContains, filterMap } from '../../../../../../utils';
import { caseInsensitiveContains, filterMap, toggle } from '../../../../../../utils';
import type { QuerySource } from '../../../../models';
import { toggle } from '../../../../utils';
import { ColumnValue } from '../../../column-value/column-value';

import './values-filter-control.scss';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ import {
checkedCircleIcon,
filterMap,
formatNumber,
toggle,
without,
xor,
} from '../../../../utils';
import type { ExpressionMeta, QuerySource } from '../../models';
import { addOrUpdatePattern, toggle } from '../../utils';
import { addOrUpdatePattern } from '../../utils';
import { ColumnValue } from '../column-value/column-value';

import './helper-table.scss';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ import React, { useState } from 'react';

import { ClearableInput, Loader, MenuCheckbox } from '../../../../../components';
import { useQueryManager } from '../../../../../hooks';
import { caseInsensitiveContains, filterMap, pluralIfNeeded, wait } from '../../../../../utils';
import {
caseInsensitiveContains,
filterMap,
pluralIfNeeded,
toggle,
wait,
} from '../../../../../utils';
import type { QuerySource } from '../../../models';
import { ExpressionMeta } from '../../../models';
import { toggle } from '../../../utils';

import './nested-column-dialog.scss';

Expand Down
5 changes: 0 additions & 5 deletions web-console/src/views/explore-view/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@
import { nonEmptyArray } from '../../../utils';
import type { ParameterDefinition } from '../models';

export function toggle<T>(xs: readonly T[], x: T, eq?: (a: T, b: T) => boolean): T[] {
const e = eq || ((a, b) => a === b);
return xs.find(_ => e(_, x)) ? xs.filter(d => !e(d, x)) : xs.concat([x]);
}

export function normalizeType(paramType: ParameterDefinition['type']): ParameterDefinition['type'] {
switch (paramType) {
case 'expressions':
Expand Down
14 changes: 12 additions & 2 deletions web-console/src/views/segments-view/segments-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
compact,
countBy,
filterMap,
findMap,
formatBytes,
formatInteger,
getApiArray,
Expand Down Expand Up @@ -1004,7 +1005,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
}

render() {
const { capabilities, onFiltersChange } = this.props;
const { capabilities, filters, onFiltersChange } = this.props;
const {
segmentTableActionDialogId,
datasourceTableActionDialogId,
Expand Down Expand Up @@ -1047,7 +1048,16 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
label="Show segment timeline"
onChange={() =>
this.setState({
showSegmentTimeline: showSegmentTimeline ? undefined : { capabilities },
showSegmentTimeline: showSegmentTimeline
? undefined
: {
capabilities,
datasource: findMap(filters, filter =>
filter.id === 'datasource' && /^=[^=|]+$/.exec(String(filter.value))
? filter.value.slice(1)
: undefined,
),
},
})
}
disabled={!capabilities.hasSqlOrCoordinatorAccess()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ exports[`ServicesView renders data 1`] = `
},
{
"Cell": [Function],
"Filter": [Function],
"Header": "Type",
"accessor": "service_type",
"show": true,
Expand Down
16 changes: 15 additions & 1 deletion web-console/src/views/services-view/services-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import {
import { AsyncActionDialog } from '../../dialogs';
import type { QueryWithContext } from '../../druid-models';
import type { Capabilities, CapabilitiesMode } from '../../helpers';
import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import {
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
suggestibleFilterInput,
} from '../../react-table';
import { Api, AppToaster } from '../../singletons';
import type { AuxiliaryQueryFn, NumberLike } from '../../utils';
import {
Expand Down Expand Up @@ -417,6 +421,16 @@ ORDER BY
{
Header: 'Type',
show: visibleColumns.shown('Type'),
Filter: suggestibleFilterInput([
'coordinator',
'overlord',
'router',
'broker',
'historical',
'indexer',
'middle_manager',
'peon',
]),
accessor: 'service_type',
width: 150,
Cell: this.renderFilterableCell('service_type'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr

import {
ClearableInput,
ENABLE_DISABLE_OPTIONS_TEXT,
ENABLED_DISABLED_OPTIONS_TEXT,
LearnMore,
Loader,
MenuBoolean,
Expand Down Expand Up @@ -694,7 +694,7 @@ export const SchemaStep = function SchemaStep(props: SchemaStepProps) {
text="Force segment sort by time"
value={forceSegmentSortByTime}
onValueChange={v => changeForceSegmentSortByTime(Boolean(v))}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
optionsText={ENABLED_DISABLED_OPTIONS_TEXT}
optionsLabelElement={{ false: EXPERIMENTAL_ICON }}
/>
</Menu>
Expand Down
Loading