Skip to content
Merged
1 change: 1 addition & 0 deletions web-console/src/utils/local-storage-keys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const LocalStorageKeys = {
SQL_DATA_LOADER_CONTENT: 'sql-data-loader-content' as const,

EXPLORE_STATE: 'explore-state' as const,
EXPLORE_STICKY: 'explore-sticky' as const,
};
export type LocalStorageKeys = (typeof LocalStorageKeys)[keyof typeof LocalStorageKeys];

Expand Down
1 change: 1 addition & 0 deletions web-console/src/utils/table-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function changePage(pagination: Pagination, page: number): Pagination {
export interface ColumnHint {
displayName?: string;
group?: string;
hidden?: boolean;
expressionForWhere?: SqlExpression;
formatter?: (x: any) => string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const ControlPane = function ControlPane(props: ControlPaneProps) {
};
return {
element: (
<NamedExpressionsInput
<NamedExpressionsInput<ExpressionMeta>
allowReordering
values={effectiveValue ? [effectiveValue] : []}
onValuesChange={vs => onValueChange(vs[0])}
Expand Down Expand Up @@ -223,7 +223,7 @@ export const ControlPane = function ControlPane(props: ControlPaneProps) {
);
return {
element: (
<NamedExpressionsInput
<NamedExpressionsInput<ExpressionMeta>
allowReordering
values={effectiveValue as ExpressionMeta[]}
onValuesChange={onValueChange}
Expand Down Expand Up @@ -266,7 +266,7 @@ export const ControlPane = function ControlPane(props: ControlPaneProps) {
case 'measure': {
return {
element: (
<NamedExpressionsInput
<NamedExpressionsInput<Measure>
values={effectiveValue ? [effectiveValue] : []}
onValuesChange={vs => onValueChange(vs[0])}
singleton
Expand All @@ -284,9 +284,11 @@ export const ControlPane = function ControlPane(props: ControlPaneProps) {
/>
),
onDropColumn: column => {
const measures = Measure.getPossibleMeasuresForColumn(column);
if (!measures.length) return;
onValueChange(measures[0]);
const candidateMeasures = Measure.getPossibleMeasuresForColumn(column).filter(
p => !effectiveValue || effectiveValue.name !== p.name,
);
if (!candidateMeasures.length) return;
onValueChange(candidateMeasures[0]);
},
onDropMeasure: onValueChange,
};
Expand All @@ -313,11 +315,11 @@ export const ControlPane = function ControlPane(props: ControlPaneProps) {
/>
),
onDropColumn: column => {
const measures = Measure.getPossibleMeasuresForColumn(column).filter(
p => !effectiveValue.some((v: ExpressionMeta) => v.name === p.name),
const candidateMeasures = Measure.getPossibleMeasuresForColumn(column).filter(
p => !effectiveValue.some((v: Measure) => v.name === p.name),
);
if (!measures.length) return;
onValueChange(effectiveValue.concat(measures[0]));
if (!candidateMeasures.length) return;
onValueChange(effectiveValue.concat(candidateMeasures[0]));
},
onDropMeasure: measure => {
onValueChange(effectiveValue.concat(measure));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const NamedExpressionsInput = function NamedExpressionsInput<

const onDragOver = useCallback(
(e: React.DragEvent, i: number) => {
if (dragIndex === -1) return;
const targetRect = e.currentTarget.getBoundingClientRect();
const before = e.clientX - targetRect.left <= targetRect.width / 2;
setDropBefore(before);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const ContainsFilterControl = React.memo(function ContainsFilterControl(
),
)
.changeOrderByExpression(F.count().toOrderByExpression('DESC'))
.changeLimitValue(101)
.toString(),
// eslint-disable-next-line react-hooks/exhaustive-deps -- exclude 'makePattern' from deps
[querySource.query, filter, column, contains, negated],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const RegexpFilterControl = React.memo(function RegexpFilterControl(
SqlExpression.and(filter, regexp ? filterPatternToExpression(filterPattern) : undefined),
)
.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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
* limitations under the License.
*/

import { FormGroup, InputGroup, Menu, MenuItem } from '@blueprintjs/core';
import { FormGroup, Menu, MenuItem } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import type { QueryResult, SqlQuery, ValuesFilterPattern } from '@druid-toolkit/query';
import { C, F, L, SqlExpression, SqlLiteral } from '@druid-toolkit/query';
import type { QueryResult, ValuesFilterPattern } from '@druid-toolkit/query';
import { C, F, SqlExpression, SqlQuery } from '@druid-toolkit/query';
import React, { useMemo, useState } from 'react';

import { ClearableInput } from '../../../../../../components';
import { useQueryManager } from '../../../../../../hooks';
import { caseInsensitiveContains } from '../../../../../../utils';
import { caseInsensitiveContains, filterMap } from '../../../../../../utils';
import type { QuerySource } from '../../../../models';
import { toggle } from '../../../../utils';
import { ColumnValue } from '../../column-value/column-value';
Expand All @@ -46,21 +47,21 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl(
const [initValues] = useState(selectedValues);
const [searchString, setSearchString] = useState('');

const valuesQuery = useMemo(() => {
const columnRef = C(column);
const queryParts: string[] = [`SELECT ${columnRef.as('c')}`, `FROM (${querySource.query})`];

const filterEx = SqlExpression.and(
filter,
searchString ? F('ICONTAINS_STRING', columnRef, L(searchString)) : undefined,
);
if (!(filterEx instanceof SqlLiteral)) {
queryParts.push(`WHERE ${filterEx}`);
}

queryParts.push(`GROUP BY 1 ORDER BY COUNT(*) DESC LIMIT 101`);
return queryParts.join('\n');
}, [querySource.query, filter, column, searchString]);
const valuesQuery = useMemo(
() =>
SqlQuery.from(querySource.query)
.addSelect(C(column).as('c'), { addToGroupBy: 'end' })
.changeWhereExpression(
SqlExpression.and(
filter,
searchString ? F('ICONTAINS_STRING', C(column), searchString) : undefined,
),
)
.changeOrderByExpression(F.count().toOrderByExpression('DESC'))
.changeLimitValue(101)
.toString(),
[querySource.query, filter, column, searchString],
);

const [valuesState] = useQueryManager<string, any[]>({
query: valuesQuery,
Expand All @@ -77,42 +78,37 @@ export const ValuesFilterControl = React.memo(function ValuesFilterControl(
if (values) {
valuesToShow = valuesToShow.concat(values.filter(v => !initValues.includes(v)));
}
if (searchString) {
valuesToShow = valuesToShow.filter(v => caseInsensitiveContains(v, searchString));
}

const showSearch = querySource.columns.find(c => c.name === column)?.sqlType !== 'BOOLEAN';

return (
<FormGroup className="values-filter-control">
{showSearch && (
<InputGroup
value={searchString}
onChange={e => setSearchString(e.target.value)}
placeholder="Search"
/>
<ClearableInput value={searchString} onChange={setSearchString} placeholder="Search" />
)}
<Menu className="value-list">
{valuesToShow.map((v, i) => (
<MenuItem
key={i}
icon={
selectedValues.includes(v)
? negated
? IconNames.DELETE
: IconNames.TICK_CIRCLE
: IconNames.CIRCLE
}
text={<ColumnValue value={v} />}
shouldDismissPopover={false}
onClick={e => {
setFilterPattern({
...filterPattern,
values: e.altKey ? [v] : toggle(selectedValues, v),
});
}}
/>
))}
{filterMap(valuesToShow, (v, i) => {
if (!caseInsensitiveContains(v, searchString)) return;
return (
<MenuItem
key={i}
icon={
selectedValues.includes(v)
? negated
? IconNames.DELETE
: IconNames.TICK_CIRCLE
: IconNames.CIRCLE
}
text={<ColumnValue value={v} />}
shouldDismissPopover={false}
onClick={e => {
setFilterPattern({
...filterPattern,
values: e.altKey ? [v] : toggle(selectedValues, v),
});
}}
/>
);
})}
{valuesState.loading && <MenuItem icon={IconNames.BLANK} text="Loading..." disabled />}
</Menu>
</FormGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ export const GenericOutputTable = React.memo(function GenericOutputTable(
columns={columnNester(
queryResult.header.map((column, i) => {
const h = column.name;
const hint = columnHints?.get(h);
const icon = showTypeIcons ? columnToIcon(column) : undefined;

return {
Expand All @@ -446,9 +447,10 @@ export const GenericOutputTable = React.memo(function GenericOutputTable(
},
headerClassName: getHeaderClassName(h),
accessor: String(i),
show: !hint?.hidden,
Cell(row) {
const value = row.value;
const formatter = columnHints?.get(h)?.formatter || formatNumber;
const formatter = hint?.formatter || formatNumber;
return (
<div>
<Popover content={<Deferred content={() => getCellMenu(column, i, value)} />}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ export const ColumnDialog = React.memo(function ColumnDialog(props: ColumnDialog
if (!expression) return;
return SqlQuery.from(QuerySource.stripToBaseSource(querySource.query))
.addSelect(F.cast(expression, 'VARCHAR').as('v'), { addToGroupBy: 'end' })
.applyIf(
querySource.baseColumns.find(column => column.isTimeColumn()),
q => q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`),
.applyIf(querySource.hasBaseTimeColumn(), q =>
q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`),
)
.changeLimitValue(100)
.toString();
Expand Down Expand Up @@ -151,7 +150,7 @@ export const ColumnDialog = React.memo(function ColumnDialog(props: ColumnDialog
} else {
onApply(
querySource.changeColumn(initExpressionName, newExpression),
new Map([[initExpression.getOutputName()!, newExpression.getOutputName()!]]),
new Map([[initExpressionName, newExpression.getOutputName()!]]),
);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ 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.baseColumns.find(column => column.isTimeColumn()),
q => q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`),
.applyIf(querySource.hasBaseTimeColumn(), q =>
q.addWhere(sql`MAX_DATA_TIME() - INTERVAL '14' DAY <= __time`),
)
.toString();
}, [querySource.query, formula]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@import '../../../../../variables';

.nested-column-dialog {
&.#{$bp-ns}-dialog {
width: 50vw;
min-height: 540px;
}

.#{$bp-ns}-dialog-body {
display: flex;
flex-direction: column;

.path-selector {
flex: 1;
padding: 5px 0;
height: 400px;
overflow: auto;
border-left: 1px solid rgba(15, 19, 32, 0.4);
border-right: 1px solid rgba(15, 19, 32, 0.4);
}
}

.#{$bp-ns}-dialog-footer {
margin-top: 0;
}
}
Loading