Skip to content

Commit

Permalink
feat(explore): adhoc column expressions [ID-3] (#17379)
Browse files Browse the repository at this point in the history
* add support for adhoc columns to api and sqla model

* fix some types

* fix duplicates in column names

* fix more lint

* fix schema and dedup

* clean up some logic

* first pass at fixing viz.py

* Add frontend support for adhoc columns

* Add title edit

* Fix showing custom title

* Use column name as default value in sql editor

* fix: Adds a loading message when needed in the Select component (#16531)

* fix(tests): make parquet select deterministic with order by (#16570)

* bump emotion to help with cache clobbering (#16559)

* fix: Support Jinja template functions in global async queries (#16412)

* Support Jinja template functions in async queries

* Pylint

* Add tests for async tasks

* Remove redundant has_request_context check

* fix: impersonate user label/tooltip (#16573)

* docs: update for small typos (#16568)

* feat: Add Aurora Data API engine spec (#16535)

* feat: Add Aurora Data API engine spec

* Fix lint

* refactor: sql_json view endpoint: encapsulate ctas parameters (#16548)

* refactor sql_json view endpoint: encapsulate ctas parameters

* fix failed tests

* fix failed tests and ci issues

* refactor sql_json view endpoint: separate concern into ad hod method (#16595)

* feat: Experimental cross-filter plugins (#16594)

* fix:fix get permission function

* feat: add cross filter chart in charts gallery under FF

* chore(deps): bump superset-ui to 0.18.2 (#16601)

* update type guard references

* fix imports

* update series_columns schema

* Add changes that got lost in rebase

* Use current columns name or expression as sql editor init value

* add integration test and do minor fixes

* Bump superset-ui

* fix linting issue

* bump superset-ui to 0.18.22

* resolve merge conflict

* lint

* fix select filter infinite loop

* bump superset-ui to 0.18.23

* Fix auto setting column popover title

* Enable adhoc columns only if UX_BETA enabled

* put back removed test

* Move popover height and width to constants

* Refactor big ternary expression

Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
Co-authored-by: Rob DiCiuccio <rob.diciuccio@gmail.com>
Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
Co-authored-by: joeADSP <75027008+joeADSP@users.noreply.github.com>
Co-authored-by: ofekisr <35701650+ofekisr@users.noreply.github.com>
Co-authored-by: simcha90 <56388545+simcha90@users.noreply.github.com>
  • Loading branch information
9 people committed Nov 15, 2021
1 parent 5d3e1b5 commit e2a429b
Show file tree
Hide file tree
Showing 27 changed files with 1,122 additions and 606 deletions.
772 changes: 386 additions & 386 deletions superset-frontend/package-lock.json

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions superset-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,35 +68,35 @@
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@superset-ui/chart-controls": "^0.18.20",
"@superset-ui/core": "^0.18.20",
"@superset-ui/legacy-plugin-chart-calendar": "^0.18.20",
"@superset-ui/legacy-plugin-chart-chord": "^0.18.20",
"@superset-ui/legacy-plugin-chart-country-map": "^0.18.20",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.18.20",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.18.20",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.18.20",
"@superset-ui/legacy-plugin-chart-histogram": "^0.18.20",
"@superset-ui/legacy-plugin-chart-horizon": "^0.18.20",
"@superset-ui/legacy-plugin-chart-map-box": "^0.18.20",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.18.20",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.18.20",
"@superset-ui/legacy-plugin-chart-partition": "^0.18.20",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.18.20",
"@superset-ui/legacy-plugin-chart-rose": "^0.18.20",
"@superset-ui/legacy-plugin-chart-sankey": "^0.18.20",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.18.20",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.20",
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.20",
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.20",
"@superset-ui/legacy-preset-chart-big-number": "^0.18.20",
"@superset-ui/chart-controls": "^0.18.23",
"@superset-ui/core": "^0.18.23",
"@superset-ui/legacy-plugin-chart-calendar": "^0.18.23",
"@superset-ui/legacy-plugin-chart-chord": "^0.18.23",
"@superset-ui/legacy-plugin-chart-country-map": "^0.18.23",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.18.23",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.18.23",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.18.23",
"@superset-ui/legacy-plugin-chart-histogram": "^0.18.23",
"@superset-ui/legacy-plugin-chart-horizon": "^0.18.23",
"@superset-ui/legacy-plugin-chart-map-box": "^0.18.23",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.18.23",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.18.23",
"@superset-ui/legacy-plugin-chart-partition": "^0.18.23",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.18.23",
"@superset-ui/legacy-plugin-chart-rose": "^0.18.23",
"@superset-ui/legacy-plugin-chart-sankey": "^0.18.23",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.18.23",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.23",
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.23",
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.23",
"@superset-ui/legacy-preset-chart-big-number": "^0.18.23",
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.20",
"@superset-ui/plugin-chart-echarts": "^0.18.20",
"@superset-ui/plugin-chart-pivot-table": "^0.18.20",
"@superset-ui/plugin-chart-table": "^0.18.20",
"@superset-ui/plugin-chart-word-cloud": "^0.18.20",
"@superset-ui/preset-chart-xy": "^0.18.20",
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.23",
"@superset-ui/plugin-chart-echarts": "^0.18.23",
"@superset-ui/plugin-chart-pivot-table": "^0.18.23",
"@superset-ui/plugin-chart-table": "^0.18.23",
"@superset-ui/plugin-chart-word-cloud": "^0.18.23",
"@superset-ui/preset-chart-xy": "^0.18.23",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.9.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,27 @@
* under the License.
*/
/* eslint-disable camelcase */
import React, { useCallback, useMemo, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { AdhocColumn, t, styled, css } from '@superset-ui/core';
import {
ColumnMeta,
isAdhocColumn,
isSavedExpression,
} from '@superset-ui/chart-controls';
import Tabs from 'src/components/Tabs';
import Button from 'src/components/Button';
import { Select } from 'src/components';
import { t, styled } from '@superset-ui/core';

import { Form, FormItem } from 'src/components/Form';
import { SQLEditor } from 'src/components/AsyncAceEditor';
import { StyledColumnOption } from 'src/explore/components/optionRenderers';
import { ColumnMeta } from '@superset-ui/chart-controls';
import { POPOVER_INITIAL_HEIGHT } from 'src/explore/constants';

const StyledSelect = styled(Select)`
.metric-option {
Expand All @@ -41,29 +53,58 @@ const StyledSelect = styled(Select)`

interface ColumnSelectPopoverProps {
columns: ColumnMeta[];
editedColumn?: ColumnMeta;
onChange: (column: ColumnMeta) => void;
editedColumn?: ColumnMeta | AdhocColumn;
onChange: (column: ColumnMeta | AdhocColumn) => void;
onClose: () => void;
setLabel: (title: string) => void;
getCurrentTab: (tab: string) => void;
label: string;
isAdhocColumnsEnabled: boolean;
}

const getInitialColumnValues = (
editedColumn?: ColumnMeta | AdhocColumn,
): [AdhocColumn?, ColumnMeta?, ColumnMeta?] => {
if (!editedColumn) {
return [undefined, undefined, undefined];
}
if (isAdhocColumn(editedColumn)) {
return [editedColumn, undefined, undefined];
}
if (isSavedExpression(editedColumn)) {
return [undefined, editedColumn, undefined];
}
return [undefined, undefined, editedColumn];
};

const ColumnSelectPopover = ({
columns,
editedColumn,
onChange,
onClose,
setLabel,
getCurrentTab,
label,
isAdhocColumnsEnabled,
}: ColumnSelectPopoverProps) => {
const [initialLabel] = useState(label);
const [
initialAdhocColumn,
initialCalculatedColumn,
initialSimpleColumn,
] = editedColumn?.expression
? [editedColumn, undefined]
: [undefined, editedColumn];
const [selectedCalculatedColumn, setSelectedCalculatedColumn] = useState(
initialCalculatedColumn,
);
const [selectedSimpleColumn, setSelectedSimpleColumn] = useState(
initialSimpleColumn,
] = getInitialColumnValues(editedColumn);

const [adhocColumn, setAdhocColumn] = useState<AdhocColumn | undefined>(
initialAdhocColumn,
);
const [selectedCalculatedColumn, setSelectedCalculatedColumn] = useState<
ColumnMeta | undefined
>(initialCalculatedColumn);
const [selectedSimpleColumn, setSelectedSimpleColumn] = useState<
ColumnMeta | undefined
>(initialSimpleColumn);

const sqlEditorRef = useRef(null);

const [calculatedColumns, simpleColumns] = useMemo(
() =>
Expand All @@ -81,15 +122,28 @@ const ColumnSelectPopover = ({
[columns],
);

const onSqlExpressionChange = useCallback(
sqlExpression => {
setAdhocColumn({ label, sqlExpression } as AdhocColumn);
setSelectedSimpleColumn(undefined);
setSelectedCalculatedColumn(undefined);
},
[label],
);

const onCalculatedColumnChange = useCallback(
selectedColumnName => {
const selectedColumn = calculatedColumns.find(
col => col.column_name === selectedColumnName,
);
setSelectedCalculatedColumn(selectedColumn);
setSelectedSimpleColumn(undefined);
setAdhocColumn(undefined);
setLabel(
selectedColumn?.verbose_name || selectedColumn?.column_name || '',
);
},
[calculatedColumns],
[calculatedColumns, setLabel],
);

const onSimpleColumnChange = useCallback(
Expand All @@ -99,33 +153,79 @@ const ColumnSelectPopover = ({
);
setSelectedCalculatedColumn(undefined);
setSelectedSimpleColumn(selectedColumn);
setAdhocColumn(undefined);
setLabel(
selectedColumn?.verbose_name || selectedColumn?.column_name || '',
);
},
[simpleColumns],
[setLabel, simpleColumns],
);

const defaultActiveTabKey =
initialSimpleColumn || calculatedColumns.length === 0 ? 'simple' : 'saved';
const defaultActiveTabKey = initialAdhocColumn
? 'sqlExpression'
: initialSimpleColumn || calculatedColumns.length === 0
? 'simple'
: 'saved';

useEffect(() => {
getCurrentTab(defaultActiveTabKey);
}, [defaultActiveTabKey, getCurrentTab]);

const onSave = useCallback(() => {
const selectedColumn = selectedCalculatedColumn || selectedSimpleColumn;
if (adhocColumn && adhocColumn.label !== label) {
adhocColumn.label = label;
}
const selectedColumn =
adhocColumn || selectedCalculatedColumn || selectedSimpleColumn;
if (!selectedColumn) {
return;
}
onChange(selectedColumn);
onClose();
}, [onChange, onClose, selectedCalculatedColumn, selectedSimpleColumn]);
}, [
adhocColumn,
label,
onChange,
onClose,
selectedCalculatedColumn,
selectedSimpleColumn,
]);

const onResetStateAndClose = useCallback(() => {
setSelectedCalculatedColumn(initialCalculatedColumn);
setSelectedSimpleColumn(initialSimpleColumn);
setAdhocColumn(initialAdhocColumn);
onClose();
}, [initialCalculatedColumn, initialSimpleColumn, onClose]);
}, [
initialAdhocColumn,
initialCalculatedColumn,
initialSimpleColumn,
onClose,
]);

const stateIsValid = selectedCalculatedColumn || selectedSimpleColumn;
const onTabChange = useCallback(
tab => {
getCurrentTab(tab);
// @ts-ignore
sqlEditorRef.current?.editor.focus();
},
[getCurrentTab],
);

const onSqlEditorFocus = useCallback(() => {
// @ts-ignore
sqlEditorRef.current?.editor.resize();
}, []);

const stateIsValid =
adhocColumn || selectedCalculatedColumn || selectedSimpleColumn;
const hasUnsavedChanges =
initialLabel !== label ||
selectedCalculatedColumn?.column_name !==
initialCalculatedColumn?.column_name ||
selectedSimpleColumn?.column_name !== initialSimpleColumn?.column_name;
selectedSimpleColumn?.column_name !== initialSimpleColumn?.column_name ||
adhocColumn?.sqlExpression !== initialAdhocColumn?.sqlExpression;

const savedExpressionsLabel = t('Saved expressions');
const simpleColumnsLabel = t('Column');

Expand All @@ -134,8 +234,12 @@ const ColumnSelectPopover = ({
<Tabs
id="adhoc-metric-edit-tabs"
defaultActiveKey={defaultActiveTabKey}
onChange={onTabChange}
className="adhoc-metric-edit-tabs"
allowOverflow
css={css`
height: ${POPOVER_INITIAL_HEIGHT}px;
`}
>
<Tabs.TabPane key="saved" tab={t('Saved')}>
<FormItem label={savedExpressionsLabel}>
Expand Down Expand Up @@ -178,6 +282,28 @@ const ColumnSelectPopover = ({
/>
</FormItem>
</Tabs.TabPane>
{isAdhocColumnsEnabled && (
<Tabs.TabPane key="sqlExpression" tab={t('Custom SQL')}>
<SQLEditor
value={
adhocColumn?.sqlExpression ||
selectedSimpleColumn?.column_name ||
selectedCalculatedColumn?.expression
}
onFocus={onSqlEditorFocus}
showLoadingForImport
onChange={onSqlExpressionChange}
width="100%"
height={`${POPOVER_INITIAL_HEIGHT - 80}px`}
showGutter={false}
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
className="filter-sql-editor"
wrapEnabled
ref={sqlEditorRef}
/>
</Tabs.TabPane>
)}
</Tabs>
<div>
<Button buttonSize="small" onClick={onResetStateAndClose} cta>
Expand Down

0 comments on commit e2a429b

Please sign in to comment.