diff --git a/superset-frontend/src/explore/components/ExploreContentPopover.tsx b/superset-frontend/src/explore/components/ExploreContentPopover.tsx index 6a91c6b4ca29f..93ee96a24e977 100644 --- a/superset-frontend/src/explore/components/ExploreContentPopover.tsx +++ b/superset-frontend/src/explore/components/ExploreContentPopover.tsx @@ -29,10 +29,4 @@ export const ExplorePopoverContent = styled.div` .filter-sql-editor { border: ${({ theme }) => theme.colors.grayscale.light2} solid thin; } - .custom-sql-disabled-message { - color: ${({ theme }) => theme.colors.grayscale.light1}; - font-size: ${({ theme }) => theme.typography.sizes.xs}px; - text-align: center; - margin-top: ${({ theme }) => theme.gridUnit * 15}px; - } `; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx index c733ad69b10c2..1936eb8995135 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx @@ -284,7 +284,7 @@ export const DndMetricSelect = (props: any) => { columns={props.columns} savedMetrics={props.savedMetrics} savedMetricsOptions={getSavedMetricOptionsForMetric(index)} - datasourceType={props.datasourceType} + datasource={props.datasource} onMoveLabel={moveLabel} onDropLabel={handleDropLabel} type={`${DndItemType.AdhocMetricOption}_${props.name}_${props.label}`} @@ -299,7 +299,7 @@ export const DndMetricSelect = (props: any) => { onMetricEdit, onRemoveMetric, props.columns, - props.datasourceType, + props.datasource, props.label, props.name, props.savedMetrics, @@ -396,7 +396,7 @@ export const DndMetricSelect = (props: any) => { columns={props.columns} savedMetricsOptions={newSavedMetricOptions} savedMetric={EMPTY_OBJECT as savedMetricType} - datasourceType={props.datasourceType} + datasource={props.datasource} isControlledComponent visible={newMetricPopoverVisible} togglePopover={togglePopover} diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx index afe501ec5e691..097cdb4122db9 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from 'src/components/Button'; +import { Tooltip } from 'src/components/Tooltip'; import { styled, t } from '@superset-ui/core'; import ErrorBoundary from 'src/components/ErrorBoundary'; @@ -56,11 +57,6 @@ const ResizeIcon = styled.i` const startingWidth = 320; const startingHeight = 240; -const SectionWrapper = styled.div` - .ant-select { - margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; - } -`; const FilterPopoverContentContainer = styled.div` .adhoc-filter-edit-tabs > .nav-tabs { @@ -173,51 +169,15 @@ export default class AdhocFilterEditPopover extends React.Component { onResize, datasource, partitionColumn, - sections = ['SIMPLE', 'CUSTOM_SQL'], theme, operators, ...popoverProps } = this.props; const { adhocFilter } = this.state; - - const resultSections = - datasource?.type === 'druid' - ? sections.filter(s => s !== 'CUSTOM_SQL') - : sections; - const stateIsValid = adhocFilter.isValid(); const hasUnsavedChanges = !adhocFilter.equals(propsAdhocFilter); - const sectionRenders = {}; - - sectionRenders.CUSTOM_SQL = ( - <ErrorBoundary> - <AdhocFilterEditPopoverSqlTabContent - adhocFilter={this.state.adhocFilter} - onChange={this.onAdhocFilterChange} - options={this.props.options} - height={this.state.height} - activeKey={this.state.activeKey} - /> - </ErrorBoundary> - ); - - sectionRenders.SIMPLE = ( - <ErrorBoundary> - <AdhocFilterEditPopoverSimpleTabContent - operators={operators} - adhocFilter={this.state.adhocFilter} - onChange={this.onAdhocFilterChange} - options={options} - datasource={datasource} - onHeightChange={this.adjustHeight} - partitionColumn={partitionColumn} - popoverRef={this.popoverContentRef.current} - /> - </ErrorBoundary> - ); - return ( <FilterPopoverContentContainer id="filter-edit-popover" @@ -225,38 +185,62 @@ export default class AdhocFilterEditPopover extends React.Component { data-test="filter-edit-popover" ref={this.popoverContentRef} > - {resultSections.length > 1 ? ( - <Tabs - id="adhoc-filter-edit-tabs" - defaultActiveKey={adhocFilter.expressionType} - className="adhoc-filter-edit-tabs" - data-test="adhoc-filter-edit-tabs" - style={{ minHeight: this.state.height, width: this.state.width }} - allowOverflow - onChange={this.onTabChange} + <Tabs + id="adhoc-filter-edit-tabs" + defaultActiveKey={adhocFilter.expressionType} + className="adhoc-filter-edit-tabs" + data-test="adhoc-filter-edit-tabs" + style={{ minHeight: this.state.height, width: this.state.width }} + allowOverflow + onChange={this.onTabChange} + > + <Tabs.TabPane + className="adhoc-filter-edit-tab" + key={EXPRESSION_TYPES.SIMPLE} + tab={t('Simple')} + > + <ErrorBoundary> + <AdhocFilterEditPopoverSimpleTabContent + operators={operators} + adhocFilter={this.state.adhocFilter} + onChange={this.onAdhocFilterChange} + options={options} + datasource={datasource} + onHeightChange={this.adjustHeight} + partitionColumn={partitionColumn} + popoverRef={this.popoverContentRef.current} + /> + </ErrorBoundary> + </Tabs.TabPane> + <Tabs.TabPane + className="adhoc-filter-edit-tab" + key={EXPRESSION_TYPES.SQL} + tab={ + datasource?.type === 'druid' ? ( + <Tooltip + title={t( + 'Custom SQL ad-hoc filters are not available for the native Druid connector', + )} + > + {t('Custom SQL')} + </Tooltip> + ) : ( + t('Custom SQL') + ) + } + disabled={datasource?.type === 'druid'} > - {resultSections.includes('SIMPLE') && ( - <Tabs.TabPane - className="adhoc-filter-edit-tab" - key={EXPRESSION_TYPES.SIMPLE} - tab={t('Simple')} - > - {sectionRenders.SIMPLE} - </Tabs.TabPane> - )} - {resultSections.includes('CUSTOM_SQL') && ( - <Tabs.TabPane - className="adhoc-filter-edit-tab" - key={EXPRESSION_TYPES.SQL} - tab={t('Custom SQL')} - > - {sectionRenders.CUSTOM_SQL} - </Tabs.TabPane> - )} - </Tabs> - ) : ( - <SectionWrapper>{sectionRenders[resultSections[0]]}</SectionWrapper> - )} + <ErrorBoundary> + <AdhocFilterEditPopoverSqlTabContent + adhocFilter={this.state.adhocFilter} + onChange={this.onAdhocFilterChange} + options={this.props.options} + height={this.state.height} + activeKey={this.state.activeKey} + /> + </ErrorBoundary> + </Tabs.TabPane> + </Tabs> <div> <Button buttonSize="small" onClick={this.props.onClose} cta> {t('Close')} diff --git a/superset-frontend/src/explore/components/controls/FixedOrMetricControl/index.jsx b/superset-frontend/src/explore/components/controls/FixedOrMetricControl/index.jsx index ae9dc9054caa4..545065319b77a 100644 --- a/superset-frontend/src/explore/components/controls/FixedOrMetricControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/FixedOrMetricControl/index.jsx @@ -178,6 +178,7 @@ export default class FixedOrMetricControl extends React.Component { }} onChange={this.setMetric} value={this.state.metricValue} + datasource={this.props.datasource} /> </PopoverSection> </div> diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx index 8d4ef8a624656..d665befcca6ad 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx @@ -41,7 +41,10 @@ const createProps = () => ({ }, ], adhocMetric: new AdhocMetric({ isNew: true }), - datasourceType: 'table', + datasource: { + extra: '{}', + type: 'table', + }, columns: [ { id: 1342, @@ -62,18 +65,61 @@ test('Should render', () => { test('Should render correct elements', () => { const props = createProps(); render(<AdhocMetricEditPopover {...props} />); - expect(screen.getByRole('tablist')).toBeVisible(); + expect(screen.getByRole('button', { name: 'Resize' })).toBeVisible(); + expect(screen.getByRole('button', { name: 'Save' })).toBeVisible(); + expect(screen.getByRole('button', { name: 'Close' })).toBeVisible(); +}); +test('Should render correct elements for SQL', () => { + const props = createProps(); + render(<AdhocMetricEditPopover {...props} />); expect(screen.getByRole('tab', { name: 'Custom SQL' })).toBeVisible(); expect(screen.getByRole('tab', { name: 'Simple' })).toBeVisible(); expect(screen.getByRole('tab', { name: 'Saved' })).toBeVisible(); + expect(screen.getByRole('tabpanel', { name: 'Saved' })).toBeVisible(); +}); + +test('Should render correct elements for native Druid', () => { + const props = { ...createProps(), datasource: { type: 'druid' } }; + render(<AdhocMetricEditPopover {...props} />); + expect(screen.getByRole('tab', { name: 'Custom SQL' })).toHaveAttribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('tab', { name: 'Simple' })).toBeEnabled(); + expect(screen.getByRole('tab', { name: 'Saved' })).toBeEnabled(); + expect(screen.getByRole('tabpanel', { name: 'Saved' })).toBeVisible(); +}); +test('Should render correct elements for allow ad-hoc metrics', () => { + const props = { + ...createProps(), + datasource: { extra: '{"disallow_adhoc_metrics": false}' }, + }; + render(<AdhocMetricEditPopover {...props} />); + expect(screen.getByRole('tab', { name: 'Custom SQL' })).toBeEnabled(); + expect(screen.getByRole('tab', { name: 'Simple' })).toBeEnabled(); + expect(screen.getByRole('tab', { name: 'Saved' })).toBeEnabled(); expect(screen.getByRole('tabpanel', { name: 'Saved' })).toBeVisible(); +}); - expect(screen.getByRole('button', { name: 'Resize' })).toBeVisible(); - expect(screen.getByRole('button', { name: 'Save' })).toBeVisible(); - expect(screen.getByRole('button', { name: 'Close' })).toBeVisible(); +test('Should render correct elements for disallow ad-hoc metrics', () => { + const props = { + ...createProps(), + datasource: { extra: '{"disallow_adhoc_metrics": true}' }, + }; + render(<AdhocMetricEditPopover {...props} />); + expect(screen.getByRole('tab', { name: 'Custom SQL' })).toHaveAttribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('tab', { name: 'Simple' })).toHaveAttribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('tab', { name: 'Saved' })).toBeEnabled(); + expect(screen.getByRole('tabpanel', { name: 'Saved' })).toBeVisible(); }); test('Clicking on "Close" should call onClose', () => { diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.jsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.jsx index 59760e9766ba0..e997e8c7ff04a 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.jsx @@ -22,6 +22,7 @@ import PropTypes from 'prop-types'; import Tabs from 'src/components/Tabs'; import Button from 'src/components/Button'; import { Select } from 'src/components'; +import { Tooltip } from 'src/components/Tooltip'; import { t, styled } from '@superset-ui/core'; import { Form, FormItem } from 'src/components/Form'; @@ -50,7 +51,7 @@ const propTypes = { columns: PropTypes.arrayOf(columnType), savedMetricsOptions: PropTypes.arrayOf(savedMetricType), savedMetric: savedMetricType, - datasourceType: PropTypes.string, + datasource: PropTypes.object, }; const defaultProps = { @@ -277,7 +278,7 @@ export default class AdhocMetricEditPopover extends React.PureComponent { onChange, onClose, onResize, - datasourceType, + datasource, ...popoverProps } = this.props; const { adhocMetric, savedMetric } = this.state; @@ -322,7 +323,10 @@ export default class AdhocMetricEditPopover extends React.PureComponent { autoFocus: true, }; - if (this.props.datasourceType === 'druid' && aggregateSelectProps.options) { + if ( + this.props.datasource?.type === 'druid' && + aggregateSelectProps.options + ) { aggregateSelectProps.options = aggregateSelectProps.options.filter( aggregate => aggregate !== 'AVG', ); @@ -337,6 +341,13 @@ export default class AdhocMetricEditPopover extends React.PureComponent { ) && savedMetric?.metric_name !== propsSavedMetric?.metric_name); + let extra = {}; + if (datasource?.extra) { + try { + extra = JSON.parse(datasource.extra); + } catch {} // eslint-disable-line no-empty + } + return ( <Form layout="vertical" @@ -370,7 +381,23 @@ export default class AdhocMetricEditPopover extends React.PureComponent { /> </FormItem> </Tabs.TabPane> - <Tabs.TabPane key={EXPRESSION_TYPES.SIMPLE} tab={t('Simple')}> + <Tabs.TabPane + key={EXPRESSION_TYPES.SIMPLE} + tab={ + extra.disallow_adhoc_metrics ? ( + <Tooltip + title={t( + 'Simple ad-hoc metrics are not enabled for this dataset', + )} + > + {t('Simple')} + </Tooltip> + ) : ( + t('Simple') + ) + } + disabled={extra.disallow_adhoc_metrics} + > <FormItem label={t('column')}> <Select options={columns.map(column => ({ @@ -395,32 +422,47 @@ export default class AdhocMetricEditPopover extends React.PureComponent { </Tabs.TabPane> <Tabs.TabPane key={EXPRESSION_TYPES.SQL} - tab={t('Custom SQL')} + tab={ + extra.disallow_adhoc_metrics || + this.props.datasource?.type === 'druid' ? ( + <Tooltip + title={ + this.props.datasource?.type === 'druid' + ? t( + 'Custom SQL ad-hoc metrics are not available for the native Druid connector', + ) + : t( + 'Custom SQL ad-hoc metrics are not enabled for this dataset', + ) + } + > + {t('Custom SQL')} + </Tooltip> + ) : ( + t('Custom SQL') + ) + } data-test="adhoc-metric-edit-tab#custom" + disabled={ + extra.disallow_adhoc_metrics || + this.props.datasource?.type === 'druid' + } > - {this.props.datasourceType !== 'druid' ? ( - <SQLEditor - data-test="sql-editor" - showLoadingForImport - ref={this.handleAceEditorRef} - keywords={keywords} - height={`${this.state.height - 80}px`} - onChange={this.onSqlExpressionChange} - width="100%" - showGutter={false} - value={ - adhocMetric.sqlExpression || adhocMetric.translateToSql() - } - editorProps={{ $blockScrolling: true }} - enableLiveAutocompletion - className="filter-sql-editor" - wrapEnabled - /> - ) : ( - <div className="custom-sql-disabled-message"> - Custom SQL Metrics are not available on druid datasources - </div> - )} + <SQLEditor + data-test="sql-editor" + showLoadingForImport + ref={this.handleAceEditorRef} + keywords={keywords} + height={`${this.state.height - 80}px`} + onChange={this.onSqlExpressionChange} + width="100%" + showGutter={false} + value={adhocMetric.sqlExpression || adhocMetric.translateToSql()} + editorProps={{ $blockScrolling: true }} + enableLiveAutocompletion + className="filter-sql-editor" + wrapEnabled + /> </Tabs.TabPane> </Tabs> <div> diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx index bbe544dd8fed6..352480f222205 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx @@ -32,7 +32,7 @@ const propTypes = { columns: PropTypes.arrayOf(columnType), savedMetricsOptions: PropTypes.arrayOf(savedMetricType), savedMetric: savedMetricType, - datasourceType: PropTypes.string, + datasource: PropTypes.object, onMoveLabel: PropTypes.func, onDropLabel: PropTypes.func, index: PropTypes.number, @@ -58,7 +58,7 @@ class AdhocMetricOption extends React.PureComponent { columns, savedMetricsOptions, savedMetric, - datasourceType, + datasource, onMoveLabel, onDropLabel, index, @@ -73,7 +73,7 @@ class AdhocMetricOption extends React.PureComponent { columns={columns} savedMetricsOptions={savedMetricsOptions} savedMetric={savedMetric} - datasourceType={datasourceType} + datasource={datasource} > <OptionControlLabel savedMetric={savedMetric} diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx index 2b3b557793fa0..42c2c806b78ec 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { ReactNode } from 'react'; -import { Metric } from '@superset-ui/core'; +import { Datasource, Metric } from '@superset-ui/core'; import Popover from 'src/components/Popover'; import AdhocMetricEditPopoverTitle from 'src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle'; import { ExplorePopoverContent } from 'src/explore/components/ExploreContentPopover'; @@ -33,7 +33,7 @@ export type AdhocMetricPopoverTriggerProps = { columns: { column_name: string; type: string }[]; savedMetricsOptions: savedMetricType[]; savedMetric: savedMetricType; - datasourceType: string; + datasource?: Datasource; children: ReactNode; isControlledComponent?: boolean; visible?: boolean; @@ -170,7 +170,7 @@ class AdhocMetricPopoverTrigger extends React.PureComponent< savedMetric, columns, savedMetricsOptions, - datasourceType, + datasource, isControlledComponent, } = this.props; const { verbose_name, metric_name } = savedMetric; @@ -204,7 +204,7 @@ class AdhocMetricPopoverTrigger extends React.PureComponent< columns={columns} savedMetricsOptions={savedMetricsOptions} savedMetric={savedMetric} - datasourceType={datasourceType} + datasource={datasource} onResize={this.onPopoverResize} onClose={closePopover} onChange={this.onChange} diff --git a/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx b/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx index 7115f529437c8..0b5f5de0acca1 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx @@ -34,7 +34,7 @@ const propTypes = { savedMetrics: PropTypes.arrayOf(savedMetricType), savedMetricsOptions: PropTypes.arrayOf(savedMetricType), multi: PropTypes.bool, - datasourceType: PropTypes.string, + datasource: PropTypes.object, }; export default function MetricDefinitionValue({ @@ -44,7 +44,7 @@ export default function MetricDefinitionValue({ columns, savedMetrics, savedMetricsOptions, - datasourceType, + datasource, onMoveLabel, onDropLabel, index, @@ -70,7 +70,7 @@ export default function MetricDefinitionValue({ onRemoveMetric, columns, savedMetricsOptions, - datasourceType, + datasource, adhocMetric, onMoveLabel, onDropLabel, diff --git a/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx b/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx index 5df7d755b9e3b..cafebd602add9 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx @@ -48,7 +48,7 @@ const propTypes = { isLoading: PropTypes.bool, multi: PropTypes.bool, clearable: PropTypes.bool, - datasourceType: PropTypes.string, + datasource: PropTypes.object, }; const defaultProps = { @@ -122,7 +122,6 @@ const MetricsControl = ({ columns, savedMetrics, datasource, - datasourceType, ...props }) => { const [value, setValue] = useState(coerceAdhocMetrics(propsValue)); @@ -232,9 +231,8 @@ const MetricsControl = ({ onMetricEdit={onNewMetric} columns={columns} savedMetricsOptions={savedMetricOptions} - datasource={datasource} savedMetric={emptySavedMetric} - datasourceType={datasourceType} + datasource={datasource} > {trigger} </AdhocMetricPopoverTrigger> @@ -243,7 +241,6 @@ const MetricsControl = ({ [ columns, datasource, - datasourceType, isAddNewMetricDisabled, newAdhocMetric, onNewMetric, @@ -295,7 +292,6 @@ const MetricsControl = ({ value, value?.[index], )} - datasourceType={datasourceType} onMoveLabel={moveLabel} onDropLabel={onDropLabel} multi={multi} @@ -304,7 +300,6 @@ const MetricsControl = ({ [ columns, datasource, - datasourceType, moveLabel, multi, onDropLabel, diff --git a/superset-frontend/src/explore/controls.jsx b/superset-frontend/src/explore/controls.jsx index f28a1974ad947..7754d5d56c963 100644 --- a/superset-frontend/src/explore/controls.jsx +++ b/superset-frontend/src/explore/controls.jsx @@ -153,7 +153,7 @@ const metrics = { return { columns: datasource ? datasource.columns : [], savedMetrics: datasource ? datasource.metrics : [], - datasourceType: datasource && datasource.type, + datasource, }; }, description: t('One or many metrics to display'), @@ -383,7 +383,7 @@ export const controls = { mapStateToProps: state => ({ columns: state.datasource ? state.datasource.columns : [], savedMetrics: state.datasource ? state.datasource.metrics : [], - datasourceType: state.datasource && state.datasource.type, + datasource: state.datasource, }), },