This is an automated email from the ASF dual-hosted git repository. vogievetsky pushed a commit to branch power-tiles in repository https://gitbox.apache.org/repos/asf/druid.git
commit 70099ea4a8920d53be1ac95a9d185812e104c0c9 Author: Vadim Ogievetsky <[email protected]> AuthorDate: Fri Jun 21 14:20:15 2024 -0700 moves --- .../generic-output-table/generic-output-table.tsx | 11 +-- web-console/src/modules/models/query-source.ts | 20 +++- web-console/src/utils/cast-breakdown.ts | 92 +++++++++++++++++++ web-console/src/utils/index.tsx | 1 + .../edit-column-dialog/edit-column-dialog.scss} | 46 +++++----- .../edit-column-dialog/edit-column-dialog.tsx | 102 +++++++++++++++++++++ .../explore-view/resource-pane/resource-pane.tsx | 18 +++- .../column-editor/column-editor.tsx | 85 ++--------------- 8 files changed, 266 insertions(+), 109 deletions(-) diff --git a/web-console/src/modules/components/generic-output-table/generic-output-table.tsx b/web-console/src/modules/components/generic-output-table/generic-output-table.tsx index b41e3842248..22f9c21eb2f 100644 --- a/web-console/src/modules/components/generic-output-table/generic-output-table.tsx +++ b/web-console/src/modules/components/generic-output-table/generic-output-table.tsx @@ -227,16 +227,7 @@ export const GenericOutputTable = React.memo(function GenericOutputTable( if (column.isTimeColumn()) { // ToDo: clean } else if (column.sqlType === 'TIMESTAMP') { - menuItems.push( - <MenuItem - key="declare_time" - icon={IconNames.TIME} - text="Use as the primary time column" - onClick={() => { - onQueryAction(q => q.changeSelect(headerIndex, selectExpression.as(TIME_COLUMN))); - }} - />, - ); + // ToDo: clean } else { // Not a time column ------------------------------------------- const values = queryResult.rows.map(row => row[headerIndex]); diff --git a/web-console/src/modules/models/query-source.ts b/web-console/src/modules/models/query-source.ts index 6baac7cd02c..ef5cc65b7ee 100644 --- a/web-console/src/modules/models/query-source.ts +++ b/web-console/src/modules/models/query-source.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import type { SqlQuery } from '@druid-toolkit/query'; +import type { SqlExpression, SqlQuery } from '@druid-toolkit/query'; import { C, SqlStar } from '@druid-toolkit/query'; import type { ExpressionMeta } from '../models'; @@ -60,10 +60,28 @@ export class QuerySource { this.columns = columns; } + public getSourceExpressionForColumn(outputName: string): SqlExpression { + const sourceExpression = this.query + .getSelectExpressionsArray() + .find(ex => ex.getOutputName() === outputName); + if (sourceExpression) return sourceExpression; + return C(outputName); + } + public deleteColumn(outputName: string): SqlQuery { const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); return noStarQuery.changeSelectExpressions( noStarQuery.getSelectExpressionsArray().filter(ex => ex.getOutputName() !== outputName), ); } + + public changeExpression(newExpression: SqlExpression): SqlQuery { + const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); + const outputName = newExpression.getOutputName(); + return noStarQuery.changeSelectExpressions( + noStarQuery + .getSelectExpressionsArray() + .map(ex => (ex.getOutputName() === outputName ? newExpression : ex)), + ); + } } diff --git a/web-console/src/utils/cast-breakdown.ts b/web-console/src/utils/cast-breakdown.ts new file mode 100644 index 00000000000..5aee18054df --- /dev/null +++ b/web-console/src/utils/cast-breakdown.ts @@ -0,0 +1,92 @@ +/* + * 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 type { SqlType } from '@druid-toolkit/query'; +import { F, SqlExpression, SqlFunction } from '@druid-toolkit/query'; + +export interface CastBreakdown { + formula: string; + castType?: SqlType; + forceMultiValue: boolean; + outputName: string; +} + +export function expressionToCastBreakdown(expression: SqlExpression): CastBreakdown { + const outputName = expression.getOutputName() || ''; + expression = expression.getUnderlyingExpression(); + + if (expression instanceof SqlFunction) { + const asType = expression.getCastType(); + const formula = String(expression.getArg(0)); + if (asType) { + return { + formula, + castType: asType, + forceMultiValue: false, + outputName, + }; + } else if (expression.getEffectiveFunctionName() === 'ARRAY_TO_MV') { + return { + formula, + forceMultiValue: true, + outputName, + }; + } + } + + return { + formula: String(expression), + forceMultiValue: false, + outputName, + }; +} + +export function castBreakdownToExpression({ + formula, + castType, + forceMultiValue, + outputName, +}: CastBreakdown): SqlExpression { + let newExpression = SqlExpression.parse(formula); + const defaultOutputName = newExpression.getOutputName(); + + if (castType) { + newExpression = newExpression.cast(castType); + } else if (forceMultiValue) { + newExpression = F('ARRAY_TO_MV', newExpression); + } + + if (!defaultOutputName && !outputName) { + throw new Error('Must explicitly define an output name'); + } + + if (newExpression.getOutputName() !== outputName) { + newExpression = newExpression.as(outputName); + } + + return newExpression; +} + +export function castBreakdownsEqual(a: CastBreakdown, b: CastBreakdown): boolean { + return ( + a.formula === b.formula && + String(a.castType) === String(b.castType) && + a.forceMultiValue === b.forceMultiValue && + a.outputName === b.outputName + ); +} diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx index 4daeefe61c1..21da7b7faeb 100644 --- a/web-console/src/utils/index.tsx +++ b/web-console/src/utils/index.tsx @@ -16,6 +16,7 @@ * limitations under the License. */ +export * from './cast-breakdown'; export * from './column-metadata'; export * from './date'; export * from './download'; diff --git a/web-console/src/utils/index.tsx b/web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.scss similarity index 53% copy from web-console/src/utils/index.tsx copy to web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.scss index 4daeefe61c1..744ccf6bf8e 100644 --- a/web-console/src/utils/index.tsx +++ b/web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.scss @@ -16,24 +16,28 @@ * limitations under the License. */ -export * from './column-metadata'; -export * from './date'; -export * from './download'; -export * from './download-query-detail-archive'; -export * from './druid-lookup'; -export * from './druid-query'; -export * from './formatter'; -export * from './general'; -export * from './intermediate-query-state'; -export * from './local-storage-backed-visibility'; -export * from './local-storage-keys'; -export * from './null-mode-detection'; -export * from './object-change'; -export * from './query-action'; -export * from './query-manager'; -export * from './query-state'; -export * from './sanitizers'; -export * from './sql'; -export * from './table-helpers'; -export * from './types'; -export * from './values-query'; +@import '../../../variables'; + +.edit-column-dialog { + &.#{$bp-ns}-dialog { + width: 80vw; + height: 80vh; + } + + .#{$bp-ns}-dialog-body { + display: flex; + gap: 8px; + + .controls { + flex: 1; + } + + .preview { + width: 200px; + } + } + + .#{$bp-ns}-dialog-footer { + margin-top: 0; + } +} diff --git a/web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.tsx b/web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.tsx new file mode 100644 index 00000000000..b56cdbc1b6b --- /dev/null +++ b/web-console/src/views/explore-view/edit-column-dialog/edit-column-dialog.tsx @@ -0,0 +1,102 @@ +/* + * 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 { Button, Classes, Dialog, FormGroup, InputGroup, Intent } from '@blueprintjs/core'; +import type { SqlExpression } from '@druid-toolkit/query'; +import React, { useState } from 'react'; + +import { AppToaster } from '../../../singletons'; +import { castBreakdownToExpression, expressionToCastBreakdown } from '../../../utils'; +import { FlexibleQueryInput } from '../../workbench-view/flexible-query-input/flexible-query-input'; + +import './edit-column-dialog.scss'; + +export interface EditColumnDialogProps { + initExpression?: SqlExpression; + onApply(expression: SqlExpression): void; + onClose(): void; +} + +export const EditColumnDialog = React.memo(function EditColumnDialog(props: EditColumnDialogProps) { + const { initExpression, onApply, onClose } = props; + + const [initBreakdown] = useState( + initExpression ? expressionToCastBreakdown(initExpression) : undefined, + ); + const [currentBreakdown, setCurrentBreakdown] = useState( + initBreakdown || { formula: '', forceMultiValue: false, outputName: '' }, + ); + + return ( + <Dialog className="edit-column-dialog" isOpen onClose={onClose} title="Edit column"> + <div className={Classes.DIALOG_BODY}> + <div className="controls"> + <FormGroup label="Name"> + <InputGroup + value={currentBreakdown.outputName} + onChange={e => { + setCurrentBreakdown({ ...currentBreakdown, outputName: e.target.value }); + }} + /> + </FormGroup> + <FormGroup label="SQL expression"> + <FlexibleQueryInput + showGutter={false} + placeholder="expression" + queryString={currentBreakdown.formula} + onQueryStringChange={formula => { + setCurrentBreakdown({ ...currentBreakdown, formula }); + }} + columnMetadata={undefined} + /> + </FormGroup> + </div> + <div className="preview"> + <div className="label">Preview</div> + <div className="preview-values">Xyz</div> + </div> + </div> + <div className={Classes.DIALOG_FOOTER}> + <div className={Classes.DIALOG_FOOTER_ACTIONS}> + <div className="edit-column-dialog-buttons"> + <Button text="Close" onClick={onClose} /> + <Button + text="Save" + intent={Intent.PRIMARY} + onClick={() => { + let newExpression: SqlExpression; + try { + newExpression = castBreakdownToExpression(currentBreakdown); + } catch (e) { + AppToaster.show({ + message: e.message, + intent: Intent.DANGER, + }); + return; + } + + onApply(newExpression); + onClose(); + }} + /> + </div> + </div> + </div> + </Dialog> + ); +}); diff --git a/web-console/src/views/explore-view/resource-pane/resource-pane.tsx b/web-console/src/views/explore-view/resource-pane/resource-pane.tsx index 5694af50b1d..877b96cb3c5 100644 --- a/web-console/src/views/explore-view/resource-pane/resource-pane.tsx +++ b/web-console/src/views/explore-view/resource-pane/resource-pane.tsx @@ -19,13 +19,14 @@ import { Icon, Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { Popover2 } from '@blueprintjs/popover2'; -import type { SqlQuery } from '@druid-toolkit/query'; +import type { SqlExpression, SqlQuery } from '@druid-toolkit/query'; import React, { useState } from 'react'; import { ClearableInput } from '../../../components'; import type { ExpressionMeta, QuerySource } from '../../../modules'; import { caseInsensitiveContains, dataTypeToIcon, filterMap } from '../../../utils'; import { DragHelper } from '../drag-helper'; +import { EditColumnDialog } from '../edit-column-dialog/edit-column-dialog'; import './resource-pane.scss'; @@ -40,7 +41,7 @@ export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { const { querySource, onQueryChange, onFilter, onShow } = props; const [columnSearch, setColumnSearch] = useState(''); - // const { query, columns } = querySource; + const [editedExpression, setEditedExpression] = useState<SqlExpression | undefined>(); return ( <div className="resource-pane"> @@ -68,6 +69,13 @@ export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { <MenuItem icon={IconNames.EYE_OPEN} text="Show" onClick={() => onShow(c)} /> )} <MenuDivider /> + <MenuItem + icon={IconNames.EDIT} + text="Edit" + onClick={() => + setEditedExpression(querySource.getSourceExpressionForColumn(columnName)) + } + /> <MenuItem icon={IconNames.TRASH} text="Delete" @@ -96,6 +104,12 @@ export const ResourcePane = function ResourcePane(props: ResourcePaneProps) { ); })} </div> + {editedExpression && ( + <EditColumnDialog + onApply={newExpression => onQueryChange(querySource.changeExpression(newExpression))} + onClose={() => setEditedExpression(undefined)} + /> + )} </div> ); }; diff --git a/web-console/src/views/sql-data-loader-view/column-editor/column-editor.tsx b/web-console/src/views/sql-data-loader-view/column-editor/column-editor.tsx index a981ce4140b..074b07dc671 100644 --- a/web-console/src/views/sql-data-loader-view/column-editor/column-editor.tsx +++ b/web-console/src/views/sql-data-loader-view/column-editor/column-editor.tsx @@ -28,13 +28,20 @@ import { } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { Popover2 } from '@blueprintjs/popover2'; -import type { QueryResult } from '@druid-toolkit/query'; -import { F, SqlExpression, SqlFunction, SqlType } from '@druid-toolkit/query'; +import type { QueryResult, SqlExpression } from '@druid-toolkit/query'; +import { SqlType } from '@druid-toolkit/query'; import type { JSX } from 'react'; import React, { useState } from 'react'; import { AppToaster } from '../../../singletons'; -import { deepDelete, deleteKeys, tickIcon } from '../../../utils'; +import { + castBreakdownsEqual, + castBreakdownToExpression, + deepDelete, + deleteKeys, + expressionToCastBreakdown, + tickIcon, +} from '../../../utils'; import { FlexibleQueryInput } from '../../workbench-view/flexible-query-input/flexible-query-input'; import './column-editor.scss'; @@ -45,78 +52,6 @@ function getTargetTypes(isArray: boolean): SqlType[] { : [SqlType.VARCHAR, SqlType.BIGINT, SqlType.DOUBLE]; } -interface CastBreakdown { - formula: string; - castType?: SqlType; - forceMultiValue: boolean; - outputName: string; -} - -function expressionToCastBreakdown(expression: SqlExpression): CastBreakdown { - const outputName = expression.getOutputName() || ''; - expression = expression.getUnderlyingExpression(); - - if (expression instanceof SqlFunction) { - const asType = expression.getCastType(); - const formula = String(expression.getArg(0)); - if (asType) { - return { - formula, - castType: asType, - forceMultiValue: false, - outputName, - }; - } else if (expression.getEffectiveFunctionName() === 'ARRAY_TO_MV') { - return { - formula, - forceMultiValue: true, - outputName, - }; - } - } - - return { - formula: String(expression), - forceMultiValue: false, - outputName, - }; -} - -function castBreakdownToExpression({ - formula, - castType, - forceMultiValue, - outputName, -}: CastBreakdown): SqlExpression { - let newExpression = SqlExpression.parse(formula); - const defaultOutputName = newExpression.getOutputName(); - - if (castType) { - newExpression = newExpression.cast(castType); - } else if (forceMultiValue) { - newExpression = F('ARRAY_TO_MV', newExpression); - } - - if (!defaultOutputName && !outputName) { - throw new Error('Must explicitly define an output name'); - } - - if (newExpression.getOutputName() !== outputName) { - newExpression = newExpression.as(outputName); - } - - return newExpression; -} - -function castBreakdownsEqual(a: CastBreakdown, b: CastBreakdown): boolean { - return ( - a.formula === b.formula && - String(a.castType) === String(b.castType) && - a.forceMultiValue === b.forceMultiValue && - a.outputName === b.outputName - ); -} - interface ColumnEditorProps { initExpression?: SqlExpression; onApply(expression: SqlExpression | undefined): void; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
