fix(explore): don't discard controls on deprecated (#30447)

This commit is contained in:
JUST.in DO IT 2024-10-03 17:12:23 -07:00 committed by GitHub
parent 2aa9348759
commit b627011463
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 141 additions and 17 deletions

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { useState, ReactNode, useLayoutEffect, RefObject } from 'react'; import { useState, ReactNode, useLayoutEffect, RefObject } from 'react';
import { css, styled, SupersetTheme } from '@superset-ui/core'; import { css, SafeMarkdown, styled, SupersetTheme } from '@superset-ui/core';
import { Tooltip } from './Tooltip'; import { Tooltip } from './Tooltip';
import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel'; import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel';
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip'; import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
@ -28,6 +28,7 @@ import {
getColumnTypeTooltipNode, getColumnTypeTooltipNode,
} from './labelUtils'; } from './labelUtils';
import { SQLPopover } from './SQLPopover'; import { SQLPopover } from './SQLPopover';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
export type ColumnOptionProps = { export type ColumnOptionProps = {
column: ColumnMeta; column: ColumnMeta;
@ -50,6 +51,8 @@ export function ColumnOption({
}: ColumnOptionProps) { }: ColumnOptionProps) {
const { expression, column_name, type_generic } = column; const { expression, column_name, type_generic } = column;
const hasExpression = expression && expression !== column_name; const hasExpression = expression && expression !== column_name;
const warningMarkdown =
column.warning_markdown || column.warning_text || column.error_text;
const type = hasExpression ? 'expression' : type_generic; const type = hasExpression ? 'expression' : type_generic;
const [tooltipText, setTooltipText] = useState<ReactNode>(column.column_name); const [tooltipText, setTooltipText] = useState<ReactNode>(column.column_name);
const [columnTypeTooltipText, setcolumnTypeTooltipText] = useState<ReactNode>( const [columnTypeTooltipText, setcolumnTypeTooltipText] = useState<ReactNode>(
@ -94,6 +97,19 @@ export function ColumnOption({
details={column.certification_details} details={column.certification_details}
/> />
)} )}
{warningMarkdown && (
<InfoTooltipWithTrigger
className="text-warning"
icon="warning"
tooltip={<SafeMarkdown source={warningMarkdown} />}
label={`warn-${column.column_name}`}
iconsStyle={{ marginLeft: 0 }}
{...(column.error_text && {
className: 'text-danger',
icon: 'exclamation-circle',
})}
/>
)}
</StyleOverrides> </StyleOverrides>
); );
} }

View File

@ -81,7 +81,8 @@ export function MetricOption({
</span> </span>
); );
const warningMarkdown = metric.warning_markdown || metric.warning_text; const warningMarkdown =
metric.warning_markdown || metric.warning_text || metric.error_text;
const [tooltipText, setTooltipText] = useState<ReactNode>(metric.metric_name); const [tooltipText, setTooltipText] = useState<ReactNode>(metric.metric_name);
@ -116,6 +117,10 @@ export function MetricOption({
tooltip={<SafeMarkdown source={warningMarkdown} />} tooltip={<SafeMarkdown source={warningMarkdown} />}
label={`warn-${metric.metric_name}`} label={`warn-${metric.metric_name}`}
iconsStyle={{ marginLeft: 0 }} iconsStyle={{ marginLeft: 0 }}
{...(metric.error_text && {
className: 'text-danger',
icon: 'exclamation-circle',
})}
/> />
)} )}
</FlexRowContainer> </FlexRowContainer>

View File

@ -34,6 +34,9 @@ jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
<div data-test="mock-column-type-label">{type}</div> <div data-test="mock-column-type-label">{type}</div>
), ),
})); }));
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => (
<div data-test="mock-info-tooltip-with-trigger" />
));
const defaultProps: ColumnOptionProps = { const defaultProps: ColumnOptionProps = {
column: { column: {
@ -111,3 +114,17 @@ test('dttm column has correct column label if showType is true', () => {
String(GenericDataType.Temporal), String(GenericDataType.Temporal),
); );
}); });
test('doesnt show InfoTooltipWithTrigger when no warning', () => {
const { queryByText } = setup();
expect(queryByText('mock-info-tooltip-with-trigger')).not.toBeInTheDocument();
});
test('shows a warning with InfoTooltipWithTrigger when it contains warning', () => {
const { getByTestId } = setup({
...defaultProps,
column: {
...defaultProps.column,
warning_text: 'This is a warning',
},
});
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument();
});

View File

@ -71,6 +71,7 @@ export interface Metric {
verbose_name?: string; verbose_name?: string;
warning_markdown?: Maybe<string>; warning_markdown?: Maybe<string>;
warning_text?: Maybe<string>; warning_text?: Maybe<string>;
error_text?: string;
} }
export function isSavedMetric(metric: any): metric is SavedMetric { export function isSavedMetric(metric: any): metric is SavedMetric {

View File

@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event';
import { render, screen, within } from 'spec/helpers/testing-library';
import { import {
DndColumnSelect, DndColumnSelect,
DndColumnSelectProps, DndColumnSelectProps,
@ -63,3 +64,52 @@ test('renders adhoc column', async () => {
expect(await screen.findByText('adhoc column')).toBeVisible(); expect(await screen.findByText('adhoc column')).toBeVisible();
expect(screen.getByLabelText('calculator')).toBeVisible(); expect(screen.getByLabelText('calculator')).toBeVisible();
}); });
test('warn selected custom metric when metric gets removed from dataset', async () => {
const columnValues = ['column1', 'column2'];
const { rerender, container } = render(
<DndColumnSelect
{...defaultProps}
options={[
{
column_name: 'column1',
},
{
column_name: 'column2',
},
]}
value={columnValues}
/>,
{
useDnd: true,
useRedux: true,
},
);
rerender(
<DndColumnSelect
{...defaultProps}
options={[
{
column_name: 'column3',
},
{
column_name: 'column2',
},
]}
value={columnValues}
/>,
);
expect(screen.getByText('column2')).toBeVisible();
expect(screen.queryByText('column1')).toBeInTheDocument();
const warningIcon = within(
screen.getByText('column1').parentElement ?? container,
).getByRole('button');
expect(warningIcon).toBeInTheDocument();
userEvent.hover(warningIcon);
const warningTooltip = await screen.findByText(
'This column might be incompatible with current dataset',
);
expect(warningTooltip).toBeInTheDocument();
});

View File

@ -109,6 +109,8 @@ function DndColumnSelect(props: DndColumnSelectProps) {
isAdhocColumn(column) && column.datasourceWarning isAdhocColumn(column) && column.datasourceWarning
? t('This column might be incompatible with current dataset') ? t('This column might be incompatible with current dataset')
: undefined; : undefined;
const withCaret = isAdhocColumn(column) || !column.error_text;
return ( return (
<ColumnSelectPopoverTrigger <ColumnSelectPopoverTrigger
key={idx} key={idx}
@ -134,7 +136,7 @@ function DndColumnSelect(props: DndColumnSelectProps) {
canDelete={canDelete} canDelete={canDelete}
column={column} column={column}
datasourceWarningMessage={datasourceWarningMessage} datasourceWarningMessage={datasourceWarningMessage}
withCaret withCaret={withCaret}
/> />
</ColumnSelectPopoverTrigger> </ColumnSelectPopoverTrigger>
); );

View File

@ -92,13 +92,13 @@ test('render selected metrics correctly', () => {
expect(screen.getByText('SUM(Column B)')).toBeVisible(); expect(screen.getByText('SUM(Column B)')).toBeVisible();
}); });
test('remove selected custom metric when metric gets removed from dataset', () => { test('warn selected custom metric when metric gets removed from dataset', async () => {
let metricValues = ['metric_a', 'metric_b', adhocMetricA, adhocMetricB]; let metricValues = ['metric_a', 'metric_b', adhocMetricA, adhocMetricB];
const onChange = (val: any[]) => { const onChange = (val: any[]) => {
metricValues = val; metricValues = val;
}; };
const { rerender } = render( const { rerender, container } = render(
<DndMetricSelect <DndMetricSelect
{...defaultProps} {...defaultProps}
value={metricValues} value={metricValues}
@ -129,19 +129,28 @@ test('remove selected custom metric when metric gets removed from dataset', () =
); );
expect(screen.getByText('metric_a')).toBeVisible(); expect(screen.getByText('metric_a')).toBeVisible();
expect(screen.queryByText('Metric B')).not.toBeInTheDocument(); expect(screen.queryByText('Metric B')).not.toBeInTheDocument();
expect(screen.queryByText('metric_b')).not.toBeInTheDocument(); expect(screen.queryByText('metric_b')).toBeInTheDocument();
const warningIcon = within(
screen.getByText('metric_b').parentElement ?? container,
).getByRole('button');
expect(warningIcon).toBeInTheDocument();
userEvent.hover(warningIcon);
const warningTooltip = await screen.findByText(
'This metric might be incompatible with current dataset',
);
expect(warningTooltip).toBeInTheDocument();
expect(screen.getByText('SUM(column_a)')).toBeVisible(); expect(screen.getByText('SUM(column_a)')).toBeVisible();
expect(screen.getByText('SUM(Column B)')).toBeVisible(); expect(screen.getByText('SUM(Column B)')).toBeVisible();
}); });
test('remove selected custom metric when metric gets removed from dataset for single-select metric control', () => { test('warn selected custom metric when metric gets removed from dataset for single-select metric control', async () => {
let metricValue = 'metric_b'; let metricValue = 'metric_b';
const onChange = (val: any) => { const onChange = (val: any) => {
metricValue = val; metricValue = val;
}; };
const { rerender } = render( const { rerender, container } = render(
<DndMetricSelect <DndMetricSelect
{...defaultProps} {...defaultProps}
value={metricValue} value={metricValue}
@ -178,7 +187,19 @@ test('remove selected custom metric when metric gets removed from dataset for si
); );
expect(screen.queryByText('Metric B')).not.toBeInTheDocument(); expect(screen.queryByText('Metric B')).not.toBeInTheDocument();
expect(screen.getByText('Drop a column/metric here or click')).toBeVisible(); expect(
screen.queryByText('Drop a column/metric here or click'),
).not.toBeInTheDocument();
expect(screen.queryByText('metric_b')).toBeInTheDocument();
const warningIcon = within(
screen.getByText('metric_b').parentElement ?? container,
).getByRole('button');
expect(warningIcon).toBeInTheDocument();
userEvent.hover(warningIcon);
const warningTooltip = await screen.findByText(
'This metric might be incompatible with current dataset',
);
expect(warningTooltip).toBeInTheDocument();
}); });
test('remove selected adhoc metric when column gets removed from dataset', async () => { test('remove selected adhoc metric when column gets removed from dataset', async () => {

View File

@ -60,11 +60,6 @@ const coerceMetrics = (
} }
const metricsCompatibleWithDataset = ensureIsArray(addedMetrics).filter( const metricsCompatibleWithDataset = ensureIsArray(addedMetrics).filter(
metric => { metric => {
if (isSavedMetric(metric)) {
return savedMetrics.some(
savedMetric => savedMetric.metric_name === metric,
);
}
if (isAdhocMetricSimple(metric)) { if (isAdhocMetricSimple(metric)) {
return columns.some( return columns.some(
column => column.column_name === metric.column.column_name, column => column.column_name === metric.column.column_name,
@ -75,6 +70,15 @@ const coerceMetrics = (
); );
return metricsCompatibleWithDataset.map(metric => { return metricsCompatibleWithDataset.map(metric => {
if (
isSavedMetric(metric) &&
!savedMetrics.some(savedMetric => savedMetric.metric_name === metric)
) {
return {
metric_name: metric,
error_text: t('This metric might be incompatible with current dataset'),
};
}
if (!isDictionaryForAdhocMetric(metric)) { if (!isDictionaryForAdhocMetric(metric)) {
return metric; return metric;
} }

View File

@ -22,6 +22,7 @@ import {
ensureIsArray, ensureIsArray,
QueryFormColumn, QueryFormColumn,
isPhysicalColumn, isPhysicalColumn,
t,
} from '@superset-ui/core'; } from '@superset-ui/core';
const getColumnNameOrAdhocColumn = ( const getColumnNameOrAdhocColumn = (
@ -55,7 +56,13 @@ export class OptionSelector {
if (!isPhysicalColumn(value)) { if (!isPhysicalColumn(value)) {
return value; return value;
} }
return null; return {
type_generic: 'UNKNOWN',
column_name: value,
error_text: t(
'This column might be incompatible with current dataset',
),
};
}) })
.filter(Boolean) as ColumnMeta[]; .filter(Boolean) as ColumnMeta[];
} }

View File

@ -67,6 +67,7 @@ class AdhocMetricOption extends PureComponent {
multi, multi,
datasourceWarningMessage, datasourceWarningMessage,
} = this.props; } = this.props;
const withCaret = !savedMetric.error_text;
return ( return (
<AdhocMetricPopoverTrigger <AdhocMetricPopoverTrigger
@ -86,7 +87,7 @@ class AdhocMetricOption extends PureComponent {
onDropLabel={onDropLabel} onDropLabel={onDropLabel}
index={index} index={index}
type={type ?? DndItemType.AdhocMetricOption} type={type ?? DndItemType.AdhocMetricOption}
withCaret withCaret={withCaret}
isFunction isFunction
multi={multi} multi={multi}
datasourceWarningMessage={datasourceWarningMessage} datasourceWarningMessage={datasourceWarningMessage}