This is an automated email from the ASF dual-hosted git repository. vogievetsky pushed a commit to branch explore_source in repository https://gitbox.apache.org/repos/asf/druid.git
commit bd72d0f1fe9343091fdabbfa1c7c2606071a268e Author: Vadim Ogievetsky <[email protected]> AuthorDate: Mon Aug 12 22:18:30 2024 -0700 bar chart better --- .../_save/tiles/filter-tile/filter-tile.tsx | 80 ------- .../tiles/hello-world-tile/hello-world-tile.scss | 24 -- .../tiles/hello-world-tile/hello-world-tile.tsx | 45 ---- web-console/_save/tiles/index.ts | 23 -- .../_save/tiles/picker-tile/picker-tile.scss | 24 -- .../_save/tiles/picker-tile/picker-tile.tsx | 70 ------ web-console/_save/tiles/table-tile/table-tile.scss | 23 -- web-console/_save/tiles/table-tile/table-tile.tsx | 94 -------- .../src/components/header-bar/header-bar.tsx | 9 - web-console/src/console-application.tsx | 6 - web-console/src/modules/bar-chart-module.tsx | 206 ++++++++--------- web-console/src/modules/module-pane.tsx | 8 +- web-console/src/utils/local-storage-keys.tsx | 2 - .../src/views/explore-view/explore-view.tsx | 7 +- web-console/src/views/index.ts | 1 - web-console/src/views/tiles-view/common.ts | 23 -- .../edit-tile-dialog/edit-tile-dialog.tsx | 118 ---------- web-console/src/views/tiles-view/tiles-view.scss | 36 --- web-console/src/views/tiles-view/tiles-view.tsx | 257 --------------------- .../src/views/tiles-view/utils/inline-state.ts | 41 ---- 20 files changed, 110 insertions(+), 987 deletions(-) diff --git a/web-console/_save/tiles/filter-tile/filter-tile.tsx b/web-console/_save/tiles/filter-tile/filter-tile.tsx deleted file mode 100644 index 92220f9cd98..00000000000 --- a/web-console/_save/tiles/filter-tile/filter-tile.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 { SqlExpression, SqlLiteral, SqlQuery } from '@druid-toolkit/query'; -import React from 'react'; - -import { Loader } from '../../../../components'; -import { useQueryManager } from '../../../../hooks'; -import { FilterPane } from '../../../explore-view/filter-pane/filter-pane'; -import type { QuerySource } from '../../../explore-view/utils'; -import { TileRepository } from '../../tile-repository'; - -// import './overall-tile.scss'; - -interface FilterTileConfig { - source: string; -} - -TileRepository.registerTile<FilterTileConfig>({ - type: 'filter', - title: 'Filter', - description: 'Shows the count', - parameterDefinitions: { - source: { - type: 'string', - // required: true, - }, - }, - component: function FilterTile(props) { - const { config, myPublicState, setPublicState, runSqlQuery } = props; - const { source } = config; - - const [querySourceState] = useQueryManager<string, QuerySource>({ - query: source, - processQuery: async source => { - const r = await runSqlQuery(`SELECT * FROM (${source}) LIMIT 0`); - - return { - query: SqlQuery.parse(source), - columns: r.header.map(c => { - return { - expression: SqlLiteral.NULL, - name: c.name, - sqlType: c.sqlType, - }; - }), - }; - }, - }); - - if (querySourceState.loading) return <Loader />; - - const filter = SqlExpression.maybeParse(myPublicState['filter']) || SqlLiteral.TRUE; - return ( - <div className="filter-tile" style={{ marginTop: '22px' }}> - <FilterPane - querySource={querySourceState.data} - filter={filter} - onFilterChange={f => setPublicState('filter', String(f))} - runSqlQuery={runSqlQuery} - /> - </div> - ); - }, -}); diff --git a/web-console/_save/tiles/hello-world-tile/hello-world-tile.scss b/web-console/_save/tiles/hello-world-tile/hello-world-tile.scss deleted file mode 100644 index 10a4fd21457..00000000000 --- a/web-console/_save/tiles/hello-world-tile/hello-world-tile.scss +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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. - */ - -.hello-world-tile { - display: flex; - justify-content: center; - align-items: center; - font-size: 26px; -} diff --git a/web-console/_save/tiles/hello-world-tile/hello-world-tile.tsx b/web-console/_save/tiles/hello-world-tile/hello-world-tile.tsx deleted file mode 100644 index 37b7ee08124..00000000000 --- a/web-console/_save/tiles/hello-world-tile/hello-world-tile.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 React from 'react'; - -import { TileRepository } from '../../tile-repository'; - -import './hello-world-tile.scss'; - -interface HelloWorldTileConfig { - alt?: string; -} - -TileRepository.registerTile<HelloWorldTileConfig>({ - type: 'hello-world', - title: 'Hello World', - description: 'Just a simple hello world', - configFields: [ - { - name: 'alt', - type: 'string', - defaultValue: 'World', - }, - ], - component: function HelloWorldModule(props) { - const { config, setPublicState } = props; - setPublicState('x', 'Hi there'); - return <div className="hello-world-tile">{`Hello ${config.alt ?? 'World'}`}</div>; - }, -}); diff --git a/web-console/_save/tiles/index.ts b/web-console/_save/tiles/index.ts deleted file mode 100644 index 2ab33beb5bf..00000000000 --- a/web-console/_save/tiles/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 './hello-world-tile/hello-world-tile'; -import './overall-tile/overall-tile'; -import './picker-tile/picker-tile'; -import './filter-tile/filter-tile'; -import './table-tile/table-tile'; diff --git a/web-console/_save/tiles/picker-tile/picker-tile.scss b/web-console/_save/tiles/picker-tile/picker-tile.scss deleted file mode 100644 index cb35dea9222..00000000000 --- a/web-console/_save/tiles/picker-tile/picker-tile.scss +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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. - */ - -.picker-tile { - display: flex; - justify-content: center; - align-items: center; - font-size: 26px; -} diff --git a/web-console/_save/tiles/picker-tile/picker-tile.tsx b/web-console/_save/tiles/picker-tile/picker-tile.tsx deleted file mode 100644 index c7a43373678..00000000000 --- a/web-console/_save/tiles/picker-tile/picker-tile.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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, Menu, MenuItem } from '@blueprintjs/core'; -import { IconNames } from '@blueprintjs/icons'; -import { Popover2 } from '@blueprintjs/popover2'; -import React from 'react'; - -import { TileRepository } from '../../tile-repository'; - -import './picker-tile.scss'; - -interface OverallTileConfig { - options: string[]; -} - -TileRepository.registerTile<OverallTileConfig>({ - type: 'picker', - title: 'Picker', - description: 'Shows the count', - parameterDefinitions: { - options: { - type: 'json', - defaultValue: [], - required: true, - }, - }, - component: function PickerModule(props) { - const { config, myPublicState, setPublicState } = props; - const { options } = config; - - return ( - <div className="picker-tile"> - <Popover2 - content={ - <Menu> - {options.map((option, i) => ( - <MenuItem - key={i} - text={option} - onClick={() => setPublicState('selected', option)} - /> - ))} - </Menu> - } - > - <Button - text={`Pick: ${myPublicState['selected'] ?? '?'}`} - rightIcon={IconNames.CARET_DOWN} - /> - </Popover2> - </div> - ); - }, -}); diff --git a/web-console/_save/tiles/table-tile/table-tile.scss b/web-console/_save/tiles/table-tile/table-tile.scss deleted file mode 100644 index 8b0240fed27..00000000000 --- a/web-console/_save/tiles/table-tile/table-tile.scss +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -.table-tile { - .generic-output-table { - height: 100%; - } -} diff --git a/web-console/_save/tiles/table-tile/table-tile.tsx b/web-console/_save/tiles/table-tile/table-tile.tsx deleted file mode 100644 index dc3db33f7dd..00000000000 --- a/web-console/_save/tiles/table-tile/table-tile.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { sql } from '@druid-toolkit/query'; -import React, { useMemo } from 'react'; - -import { Issue, Loader } from '../../../../components'; -import { useQueryManager } from '../../../../hooks'; -import { GenericOutputTable } from '../../../explore-view/modules/components'; -import { TileRepository } from '../../tile-repository'; -import { inlineState } from '../../utils/inline-state'; - -import './table-tile.scss'; - -interface TableTileConfig { - source: string; - splitColumn: string; -} - -TileRepository.registerTile<TableTileConfig>({ - type: 'table', - title: 'Table', - description: 'A universal table', - configFields: [ - { - name: 'source', - type: 'string', - required: true, - }, - { - name: 'splitColumn', - type: 'string', - required: true, - }, - ], - component: function TableTile(props) { - const { config, publicState, setPublicState, runSqlQuery } = props; - const { source, splitColumn } = config; - - const query = useMemo(() => { - return String(sql` - SELECT - "${splitColumn}", - COUNT(*) AS "cnt" - FROM (${inlineState(source, publicState)}) - GROUP BY 1 - ORDER BY 2 DESC - `); - }, [source, publicState]); - - const [resultState] = useQueryManager<string, any>({ - query: query, - processQuery: async query => { - setPublicState('query', query); - return await runSqlQuery(query); - }, - }); - - if (resultState.loading) return <Loader />; - - const errorMessage = resultState.getErrorMessage(); - if (errorMessage) return <Issue issue={errorMessage} />; - - return ( - <div className="table-tile"> - {resultState.data && ( - <GenericOutputTable - runeMode={false} - queryResult={resultState.data} - showTypeIcons={false} - onQueryAction={action => { - console.log(action); - }} - /> - )} - </div> - ); - }, -}); diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index f9142d749ac..aed66798299 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -70,7 +70,6 @@ export type HeaderActiveTab = | 'workbench' | 'sql-data-loader' | 'explore' - | 'tiles' | 'lookups'; const DruidLogo = React.memo(function DruidLogo() { @@ -159,14 +158,6 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { disabled={!capabilities.hasSql()} selected={active === 'explore'} /> - <MenuItem - icon={IconNames.APPLICATIONS} - text="Tiles" - label="(experimental)" - href="#tiles" - disabled={!capabilities.hasSql()} - selected={active === 'tiles'} - /> </Menu> ); diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx index 9aa477d920d..36a0b8aa392 100644 --- a/web-console/src/console-application.tsx +++ b/web-console/src/console-application.tsx @@ -44,7 +44,6 @@ import { SqlDataLoaderView, SupervisorsView, TasksView, - TilesView, WorkbenchView, } from './views'; @@ -440,10 +439,6 @@ export class ConsoleApplication extends React.PureComponent< return this.wrapInViewContainer('explore', <ExploreView />, 'thinner'); }; - private readonly wrappedTilesView = () => { - return this.wrapInViewContainer('tiles', <TilesView />, 'thinner'); - }; - render() { const { capabilities, capabilitiesLoading } = this.state; @@ -504,7 +499,6 @@ export class ConsoleApplication extends React.PureComponent< {capabilities.hasSql() && ( <Route path="/explore" component={this.wrappedExploreView} /> )} - {capabilities.hasSql() && <Route path="/tiles" component={this.wrappedTilesView} />} <Route component={this.wrappedHomeView} /> </Switch> diff --git a/web-console/src/modules/bar-chart-module.tsx b/web-console/src/modules/bar-chart-module.tsx index ab72c78e391..a1c2b0dc1b8 100644 --- a/web-console/src/modules/bar-chart-module.tsx +++ b/web-console/src/modules/bar-chart-module.tsx @@ -17,110 +17,18 @@ */ import { L, SqlExpression, SqlQuery } from '@druid-toolkit/query'; +import type { ECharts } from 'echarts'; import * as echarts from 'echarts'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; + +import { useQueryManager } from '../hooks'; +import { highlightStore } from '../views/explore-view/highlight-store/highlight-store'; import type { ExpressionMeta } from './models'; import { ModuleRepository } from './module-repository/module-repository'; import './record-table-module.scss'; -const barChartFn = ({ container, runSqlQuery }: any) => { - const myChart = echarts.init(container, 'dark'); - - myChart.setOption({ - tooltip: {}, - dataset: { - sourceHeader: false, - dimensions: ['dim', 'met'], - source: [], - }, - xAxis: { - type: 'category', - axisLabel: { interval: 0, rotate: -30 }, - }, - yAxis: {}, - series: [ - { - type: 'bar', - encode: { - x: 'dim', - y: 'met', - }, - }, - ], - }); - - return { - async update({ source, parameterValues }: any) { - const { splitColumn, metric, metricToSort, limit } = parameterValues; - - myChart.off('click'); - - const splitExpression = splitColumn ? splitColumn.expression : L('Overall'); - - const v = await runSqlQuery( - SqlQuery.from(source) - .addSelect(splitExpression.as('dim'), { addToGroupBy: 'end' }) - .addSelect(metric.expression.as('met'), { - addToOrderBy: metricToSort ? undefined : 'end', - direction: 'DESC', - }) - .applyIf(metricToSort, q => - q.addOrderBy(metricToSort.expression.toOrderByExpression('DESC')), - ) - .changeLimitValue(limit), - ); - myChart.setOption({ - dataset: { - source: v.toObjectArray(), - }, - }); - - myChart.on('click', 'series', p => { - const { dim, met } = p.data as any; - - const [x, y] = myChart.convertToPixel({ seriesIndex: 0 }, [dim, met]); - - console.log(x, y); - // highlightStore.getState().setHighlight({ - // label: p.name, - // x, - // y: y - 20, - // data: [dim, met], - // onDrop: () => { - // highlightStore.getState().dropHighlight(); - // }, - // onSave: () => { - // updateWhere(where.toggleClauseInWhere(C(splitColumn.name).equal(p.name))); - // highlightStore.getState().dropHighlight(); - // }, - // }); - }); - }, - - resize() { - myChart.resize(); - - // // if there is a highlight, update its x position - // // by calculating new pixel position from the highlight's data - // const highlight = highlightStore.getState().highlight; - // if (highlight) { - // const [x, y] = myChart.convertToPixel({ seriesIndex: 0 }, highlight.data as number[]); - // - // highlightStore.getState().updateHighlight({ - // x, - // y: y - 20, - // }); - // } - }, - - destroy() { - myChart.dispose(); - }, - }; -}; - interface RecordTableParameterValues { splitColumn: ExpressionMeta; metric: ExpressionMeta; @@ -166,20 +74,108 @@ ModuleRepository.registerModule<RecordTableParameterValues>({ }, }, component: function BarChartModule(props) { - const updateFn = useRef<any>(null); - const { querySource, parameterValues, runSqlQuery } = props; + const { querySource, parameterValues, stage, runSqlQuery } = props; + const chartRef = useRef<ECharts>(); + + const dataQuery = useMemo(() => { + const source = querySource.query; + const { splitColumn, metric, metricToSort, limit } = parameterValues; + + const splitExpression = splitColumn ? splitColumn.expression : L('Overall'); + + return SqlQuery.from(source) + .addSelect(splitExpression.as('dim'), { addToGroupBy: 'end' }) + .addSelect(metric.expression.as('met'), { + addToOrderBy: metricToSort ? undefined : 'end', + direction: 'DESC', + }) + .applyIf(metricToSort, q => + q.addOrderBy(metricToSort.expression.toOrderByExpression('DESC')), + ) + .changeLimitValue(limit); + }, [querySource, parameterValues]); + + const [dataState] = useQueryManager({ + query: dataQuery, + processQuery: async (query: SqlQuery) => { + return (await runSqlQuery(query)).toObjectArray(); + }, + }); + + useEffect(() => { + const myChart = chartRef.current; + const data = dataState.data; + if (!myChart || !data) return; + + myChart.off('click'); + + myChart.setOption({ + dataset: { + source: data, + }, + }); + + myChart.on('click', 'series', p => { + const { dim, met } = p.data as any; + + const [x, y] = myChart.convertToPixel({ seriesIndex: 0 }, [dim, met]); + + console.log(x, y); + highlightStore.getState().setHighlight({ + label: p.name, + x, + y: y - 20, + data: [dim, met], + onDrop: () => { + highlightStore.getState().dropHighlight(); + }, + onSave: () => { + // updateWhere(where.toggleClauseInWhere(C(splitColumn.name).equal(p.name))); + console.log('onSave'); + highlightStore.getState().dropHighlight(); + }, + }); + }); + }, [chartRef.current, dataState.data]); useEffect(() => { - if (!updateFn.current) return; - updateFn.current.update({ source: querySource.query, parameterValues }); - }, [updateFn.current, querySource, parameterValues]); + const myChart = chartRef.current; + if (!myChart) return; + myChart.resize(); + }, [stage]); return ( <div className="bar-chart-module" - ref={x => { - if (updateFn.current) return; - updateFn.current = barChartFn({ container: x, runSqlQuery }); + ref={container => { + if (chartRef.current) return; + + const myChart = echarts.init(container, 'dark'); + + myChart.setOption({ + tooltip: {}, + dataset: { + sourceHeader: false, + dimensions: ['dim', 'met'], + source: [], + }, + xAxis: { + type: 'category', + axisLabel: { interval: 0, rotate: -30 }, + }, + yAxis: {}, + series: [ + { + type: 'bar', + encode: { + x: 'dim', + y: 'met', + }, + }, + ], + }); + + chartRef.current = myChart; }} style={{ height: '100%' }} /> diff --git a/web-console/src/modules/module-pane.tsx b/web-console/src/modules/module-pane.tsx index e1ec33ab74f..a7cc32f94d3 100644 --- a/web-console/src/modules/module-pane.tsx +++ b/web-console/src/modules/module-pane.tsx @@ -19,7 +19,7 @@ import { Button, Menu, MenuItem, Popover, ResizeSensor } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import type { QueryResult, SqlExpression, SqlQuery } from '@druid-toolkit/query'; -import React, { useState } from 'react'; +import React, { forwardRef, useState } from 'react'; import { Issue } from '../components'; @@ -55,7 +55,7 @@ export interface ModulePaneProps { onEdit?: () => void; } -export const ModulePane = function ModulePane(props: ModulePaneProps) { +export const ModulePane = forwardRef<any, any>(function ModulePane(props: ModulePaneProps, ref) { const { moduleId, moduleName, @@ -101,7 +101,7 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { setStage(newStage); }} > - <div className="module-pane"> + <div className="module-pane" ref={ref}> {content} {onEdit && ( <Button className="edit-button" icon={IconNames.EDIT} onClick={onEdit} minimal small /> @@ -119,4 +119,4 @@ export const ModulePane = function ModulePane(props: ModulePaneProps) { </div> </ResizeSensor> ); -}; +}); diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx index 7211d77cb34..d4efec06e22 100644 --- a/web-console/src/utils/local-storage-keys.tsx +++ b/web-console/src/utils/local-storage-keys.tsx @@ -57,8 +57,6 @@ export const LocalStorageKeys = { SQL_DATA_LOADER_CONTENT: 'sql-data-loader-content' as const, EXPLORE_STATE: 'explore-state' as const, - - TILES: 'tiles' as const, }; export type LocalStorageKeys = (typeof LocalStorageKeys)[keyof typeof LocalStorageKeys]; diff --git a/web-console/src/views/explore-view/explore-view.tsx b/web-console/src/views/explore-view/explore-view.tsx index 4246f4e16cd..15637657b7f 100644 --- a/web-console/src/views/explore-view/explore-view.tsx +++ b/web-console/src/views/explore-view/explore-view.tsx @@ -163,6 +163,7 @@ async function introspectSource(source: string): Promise<QuerySource> { export const ExploreView = React.memo(function ExploreView() { const [shownText, setShownText] = useState<string | undefined>(); const filterPane = useRef<{ filterOn(column: Column): void }>(); + const containerRef = useRef<HTMLDivElement | null>(null); const [exploreState, setExploreState] = useLocalStorageState<ExploreState>( LocalStorageKeys.EXPLORE_STATE, @@ -237,6 +238,8 @@ export const ExploreView = React.memo(function ExploreView() { ); } + console.log('containerRef.current', containerRef.current); + const querySource = querySourceState.getSomeData(); const effectiveShowFullSource = showFullSource || parsedError; @@ -251,7 +254,7 @@ export const ExploreView = React.memo(function ExploreView() { )} {parsedError && <div className="source-error">{`Source error: ${parsedError}`}</div>} {parsedSource && ( - <div className="explore-container"> + <div className="explore-container" ref={containerRef}> <SourcePane selectedSource={parsedSource} onSelectedSourceChange={setSource} @@ -426,7 +429,7 @@ export const ExploreView = React.memo(function ExploreView() { )} </div> )} - <HighlightBubble referenceContainer={null} /> + <HighlightBubble referenceContainer={containerRef.current} /> </div> ); }); diff --git a/web-console/src/views/index.ts b/web-console/src/views/index.ts index bc413874dd2..51113dd91df 100644 --- a/web-console/src/views/index.ts +++ b/web-console/src/views/index.ts @@ -26,5 +26,4 @@ export * from './services-view/services-view'; export * from './sql-data-loader-view/sql-data-loader-view'; export * from './supervisors-view/supervisors-view'; export * from './tasks-view/tasks-view'; -export * from './tiles-view/tiles-view'; export * from './workbench-view/workbench-view'; diff --git a/web-console/src/views/tiles-view/common.ts b/web-console/src/views/tiles-view/common.ts deleted file mode 100644 index 30b3e2f17a2..00000000000 --- a/web-console/src/views/tiles-view/common.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -export interface ModuleTileConfig { - moduleName: string; - moduleId: string; - parameterValues: Record<string, any>; -} diff --git a/web-console/src/views/tiles-view/edit-tile-dialog/edit-tile-dialog.tsx b/web-console/src/views/tiles-view/edit-tile-dialog/edit-tile-dialog.tsx deleted file mode 100644 index 405b50fc5d9..00000000000 --- a/web-console/src/views/tiles-view/edit-tile-dialog/edit-tile-dialog.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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, Intent } from '@blueprintjs/core'; -import React, { useState } from 'react'; - -import type { FormJsonTabs } from '../../../components'; -import { AutoForm, FormJsonSelector, JsonInput } from '../../../components'; -import { ModuleRepository } from '../../../modules/module-repository/module-repository'; -import type { ModuleTileConfig } from '../common'; - -export interface EditTileDialogProps { - initTileConfig: ModuleTileConfig | undefined; - onSave(tileConfig: ModuleTileConfig): void; - onClose(): void; -} - -export const EditTileDialog = React.memo(function EditTileDialog(props: EditTileDialogProps) { - const { initTileConfig, onSave, onClose } = props; - - const [currentTab, setCurrentTab] = useState<FormJsonTabs>('form'); - const [currentModuleTileConfig, setCurrentModuleTileConfig] = useState<Partial<ModuleTileConfig>>( - initTileConfig || { moduleName: `t${String(Math.random()).slice(2, 3)}}` }, - ); - const [jsonError, setJsonError] = useState<Error | undefined>(); - - const moduleDefinition = - typeof currentModuleTileConfig.moduleId === 'string' - ? ModuleRepository.getModule(currentModuleTileConfig.moduleId) - : undefined; - - // const issueWithCurrentTileConfig = moduleDefinition - // ? AutoForm.issueWithModel(currentModuleTileConfig.config, moduleDefinition.configFields) - // : 'no tile'; - - const issueWithCurrentTileConfig = moduleDefinition ? undefined : 'no tile'; - - console.log(jsonError, issueWithCurrentTileConfig); - - return ( - <Dialog - className="edit-tile-dialog" - isOpen - onClose={onClose} - canOutsideClickClose={false} - title="Edit tile" - > - <div className={Classes.DIALOG_BODY}> - <FormJsonSelector - tab={currentTab} - onChange={t => { - setJsonError(undefined); - setCurrentTab(t); - }} - /> - <div className="content"> - {currentTab === 'form' ? ( - <> - <AutoForm - fields={[ - { - name: 'moduleName', - type: 'string', - }, - { - name: 'moduleId', - type: 'string', - suggestions: ModuleRepository.getAllModuleIds(), - required: true, - }, - ]} - model={currentModuleTileConfig} - onChange={setCurrentModuleTileConfig} - /> - ToDo: fill this in - </> - ) : ( - <JsonInput - value={currentModuleTileConfig} - onChange={setCurrentModuleTileConfig} - setError={setJsonError} - height="100%" - /> - )} - </div> - </div> - <div className={Classes.DIALOG_FOOTER}> - <div className={Classes.DIALOG_FOOTER_ACTIONS}> - <Button text="Close" onClick={onClose} /> - <Button - text="Save" - intent={Intent.PRIMARY} - disabled={Boolean(jsonError || issueWithCurrentTileConfig)} - onClick={() => { - onSave(currentModuleTileConfig as any); - onClose(); - }} - /> - </div> - </div> - </Dialog> - ); -}); diff --git a/web-console/src/views/tiles-view/tiles-view.scss b/web-console/src/views/tiles-view/tiles-view.scss deleted file mode 100644 index aea0286ad18..00000000000 --- a/web-console/src/views/tiles-view/tiles-view.scss +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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. - */ - -.tiles-view { - position: relative; - height: 100%; - width: 100%; - - .control-button { - position: absolute; - bottom: 5px; - right: 5px; - } - - .tiles-container { - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 400px 400px 400px; - gap: 10px; - } -} diff --git a/web-console/src/views/tiles-view/tiles-view.tsx b/web-console/src/views/tiles-view/tiles-view.tsx deleted file mode 100644 index d67dc942b69..00000000000 --- a/web-console/src/views/tiles-view/tiles-view.tsx +++ /dev/null @@ -1,257 +0,0 @@ -/* - * 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, Intent, Menu, MenuItem, Popover, Position } from '@blueprintjs/core'; -import { IconNames } from '@blueprintjs/icons'; -import type { QueryResult } from '@druid-toolkit/query'; -import { L, QueryRunner, sql, SqlLiteral, SqlQuery, SqlTable, T } from '@druid-toolkit/query'; -import React from 'react'; - -import { SpecDialog } from '../../dialogs'; -import { QuerySource } from '../../modules'; -import { ModulePane } from '../../modules/module-pane'; -import { Api } from '../../singletons'; -import { - deepGet, - localStorageGetJson, - LocalStorageKeys, - localStorageSetJson, - queryDruidSql, - removeObjectKey, -} from '../../utils'; - -import type { ModuleTileConfig } from './common'; -import { EditTileDialog } from './edit-tile-dialog/edit-tile-dialog'; - -import './tiles-view.scss'; - -// micro-cache -const MAX_TIME_TTL = 60000; -let lastMaxTimeTable: string | undefined; -let lastMaxTimeValue: Date | undefined; -let lastMaxTimeTimestamp = 0; - -async function getMaxTimeForTable(tableName: string): Promise<Date | undefined> { - // micro-cache get - if ( - lastMaxTimeTable === tableName && - lastMaxTimeValue && - Date.now() < lastMaxTimeTimestamp + MAX_TIME_TTL - ) { - return lastMaxTimeValue; - } - - const d = await queryDruidSql({ - query: sql`SELECT MAX(__time) AS "maxTime" FROM ${T(tableName)}`, - }); - - const maxTime = new Date(deepGet(d, '0.maxTime')); - if (isNaN(maxTime.valueOf())) return; - - // micro-cache set - lastMaxTimeTable = tableName; - lastMaxTimeValue = maxTime; - lastMaxTimeTimestamp = Date.now(); - - return maxTime; -} - -function getFirstTableName(q: SqlQuery): string | undefined { - let tableName: string | undefined; - q.walk(ex => { - if (ex instanceof SqlTable) { - tableName = ex.getName(); - return; - } - return ex; - }); - return tableName; -} - -const queryRunner = new QueryRunner({ - inflateDateStrategy: 'none', - executor: async (payload, isSql, cancelToken) => { - if (isSql && payload.query.includes('MAX_DATA_TIME()')) { - const parsed = SqlQuery.parse(payload.query); - const tableName = getFirstTableName(parsed); - if (tableName) { - const maxTime = await getMaxTimeForTable(tableName); - if (maxTime) { - payload = { - ...payload, - query: payload.query.replace(/MAX_DATA_TIME\(\)/g, L(maxTime)), - }; - } - } - } - - console.debug('Running query:'); - console.debug(payload.query ?? payload); - return Api.instance.post(`/druid/v2${isSql ? '/sql' : ''}`, payload, { cancelToken }); - }, -}); - -function runSqlQuery(query: string | SqlQuery): Promise<QueryResult> { - return queryRunner.runQuery({ - query, - }); -} - -export interface TilesViewProps {} - -export interface TilesViewState { - tileConfigs: ModuleTileConfig[]; - tilePublicState: Record<string, Record<string, any>>; - editTile?: { - tileConfig: ModuleTileConfig; - index: number; - }; - showConfigs: boolean; -} - -export class TilesView extends React.PureComponent<TilesViewProps, TilesViewState> { - constructor(props: TilesViewProps) { - super(props); - - this.state = { - tileConfigs: localStorageGetJson(LocalStorageKeys.TILES) || [ - { type: 'filter', moduleName: 'MainFilter', config: { source: 'SELECT * FROM wikipedia' } }, - { - type: 'table', - moduleName: 'Table', - config: { - source: `SELECT * FROM wikipedia WHERE STATE('MainFilter', 'filter', TRUE)`, - splitColumn: 'channel', - }, - }, - { - type: 'overall', - moduleName: 'Tile 3', - config: { source: `SELECT * FROM wikipedia WHERE STATE('MainFilter', 'filter', TRUE)` }, - }, - { type: 'picker', moduleName: 'Tile 4', config: { options: ['A', 'B', 'C'] } }, - { type: 'hello-world', moduleName: 'Tile 2', config: { alt: 'Moon' } }, - ], - tilePublicState: {}, - showConfigs: false, - }; - } - - private handleTileConfigsChange(tileConfigs: ModuleTileConfig[]) { - localStorageSetJson(LocalStorageKeys.TILES, tileConfigs); - this.setState({ tileConfigs }); - } - - render() { - const { tileConfigs, tilePublicState, editTile, showConfigs } = this.state; - - return ( - <div className="tiles-view app-view"> - <Popover - className="control-button" - position={Position.BOTTOM_LEFT} - content={ - <Menu> - <MenuItem text="Show configs" onClick={() => this.setState({ showConfigs: true })} /> - <MenuItem - text="Clear view local storage" - intent={Intent.DANGER} - onClick={() => { - localStorageSetJson(LocalStorageKeys.TILES, null); - window.location.reload(); - }} - /> - </Menu> - } - > - <Button icon={IconNames.COG} /> - </Popover> - <div className="tiles-container"> - {tileConfigs.map((moduleTileConfig, i) => ( - <ModulePane - key={i} - moduleId={moduleTileConfig.moduleId} - moduleName={moduleTileConfig.moduleName} - querySource={new QuerySource(SqlQuery.parse('ToDo: ???'), [], [])} - where={SqlLiteral.TRUE} - parameterValues={moduleTileConfig.parameterValues} - publicState={tilePublicState} - setPublicState={(key, value) => { - setTimeout(() => { - let { tilePublicState } = this.state; - const myPublicState = tilePublicState[moduleTileConfig.moduleName] ?? {}; - const currentValue = myPublicState[key]; - if (value === currentValue) return; - if (typeof value === 'undefined') { - tilePublicState = { - ...tilePublicState, - [moduleTileConfig.moduleName]: removeObjectKey(myPublicState, key), - }; - } else { - tilePublicState = { - ...tilePublicState, - [moduleTileConfig.moduleName]: { - ...myPublicState, - [key]: value, - }, - }; - } - this.setState({ tilePublicState }); - }, 1); - }} - runSqlQuery={runSqlQuery} - onEdit={() => { - this.setState({ - editTile: { - tileConfig: moduleTileConfig, - index: i, - }, - }); - }} - /> - ))} - </div> - {editTile && ( - <EditTileDialog - initTileConfig={editTile.tileConfig} - onSave={newTileConfig => { - if (!editTile) return; - this.handleTileConfigsChange( - tileConfigs.map((t, i) => (i === editTile?.index ? newTileConfig : t)), - ); - }} - onClose={() => this.setState({ editTile: undefined })} - /> - )} - {showConfigs && ( - <SpecDialog - title="Tile configs" - initSpec={tileConfigs} - onSubmit={v => { - if (!Array.isArray(v)) return; - this.setState({ tileConfigs: v }); - }} - onClose={() => { - this.setState({ showConfigs: false }); - }} - /> - )} - </div> - ); - } -} diff --git a/web-console/src/views/tiles-view/utils/inline-state.ts b/web-console/src/views/tiles-view/utils/inline-state.ts deleted file mode 100644 index 467c152f2e5..00000000000 --- a/web-console/src/views/tiles-view/utils/inline-state.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { SqlExpression, SqlFunction, SqlLiteral } from '@druid-toolkit/query'; - -export function inlineState( - expression: SqlExpression | string, - publicState: Readonly<Record<string, Readonly<Record<string, any>>>>, -): SqlExpression { - return SqlExpression.parse(expression).walk(ex => { - if (ex instanceof SqlFunction && ex.getEffectiveFunctionName() === 'STATE') { - const tileName = ex.getArgAsString(0); - if (!tileName) throw new Error('needs tile name'); - - const stateName = ex.getArgAsString(1); - if (!stateName) throw new Error('needs state name'); - - return ( - SqlExpression.maybeParse(publicState[tileName]?.[stateName]) || - ex.getArg(2) || - SqlLiteral.NULL - ).ensureParens(); - } - return ex; - }) as SqlExpression; -} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
