diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
index 676c3b6d535ac..019bad7096ab5 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
@@ -71,7 +71,7 @@ describe('Visualization > Line', () => {
.focus()
.type('bnbColors{enter}');
cy.get(
- '.Control[data-test="color_scheme"] .ant-select-selection-item > ul[data-test="bnbColors"]',
+ '.Control[data-test="color_scheme"] .ant-select-selection-item ul[data-test="bnbColors"]',
).should('exist');
});
diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
index 18af6f5a809de..4a7839fdbb2c5 100644
--- a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
+++ b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
@@ -27,9 +27,11 @@ const propTypes = {
onChange: PropTypes.func,
labelMargin: PropTypes.number,
colorScheme: PropTypes.string,
+ hasCustomLabelColors: PropTypes.bool,
};
const defaultProps = {
+ hasCustomLabelColors: false,
colorScheme: undefined,
onChange: () => {},
};
@@ -48,13 +50,12 @@ class ColorSchemeControlWrapper extends React.PureComponent {
}
render() {
- const { colorScheme, labelMargin = 0 } = this.props;
+ const { colorScheme, labelMargin = 0, hasCustomLabelColors } = this.props;
return (
);
}
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx
index 9597ccfccd3c6..8acff9867dd2c 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx
@@ -132,6 +132,7 @@ class PropertiesModal extends React.PureComponent {
this.onColorSchemeChange = this.onColorSchemeChange.bind(this);
this.getRowsWithRoles = this.getRowsWithRoles.bind(this);
this.getRowsWithoutRoles = this.getRowsWithoutRoles.bind(this);
+ this.getJsonMetadata = this.getJsonMetadata.bind(this);
}
componentDidMount() {
@@ -139,13 +140,22 @@ class PropertiesModal extends React.PureComponent {
JsonEditor.preload();
}
+ getJsonMetadata() {
+ const { json_metadata: jsonMetadata } = this.state.values;
+ try {
+ const jsonMetadataObj = jsonMetadata?.length
+ ? JSON.parse(jsonMetadata)
+ : {};
+ return jsonMetadataObj;
+ } catch (_) {
+ return {};
+ }
+ }
+
onColorSchemeChange(colorScheme, { updateMetadata = true } = {}) {
// check that color_scheme is valid
const colorChoices = getCategoricalSchemeRegistry().keys();
- const { json_metadata: jsonMetadata } = this.state.values;
- const jsonMetadataObj = jsonMetadata?.length
- ? JSON.parse(jsonMetadata)
- : {};
+ const jsonMetadataObj = this.getJsonMetadata();
// only fire if the color_scheme is present and invalid
if (colorScheme && !colorChoices.includes(colorScheme)) {
@@ -318,6 +328,11 @@ class PropertiesModal extends React.PureComponent {
getRowsWithoutRoles() {
const { values, isDashboardLoaded } = this.state;
+ const jsonMetadataObj = this.getJsonMetadata();
+ const hasCustomLabelColors = !!Object.keys(
+ jsonMetadataObj?.label_colors || {},
+ ).length;
+
return (
@@ -343,6 +358,7 @@ class PropertiesModal extends React.PureComponent {
{t('Colors')}
@@ -404,6 +425,7 @@ class PropertiesModal extends React.PureComponent {
null,
+ isLinear: false,
+};
+
+const setup = (overrides?: Record) =>
+ render();
+
+test('should render', async () => {
+ const { container } = setup();
+ await waitFor(() => expect(container).toBeVisible());
+});
+
+test('should display a label', async () => {
+ setup();
+ expect(await screen.findByText('Color scheme')).toBeTruthy();
+});
+
+test('should not display an alert icon if hasCustomLabelColors=false', async () => {
+ setup();
+ await waitFor(() => {
+ expect(
+ screen.queryByRole('img', { name: 'alert-solid' }),
+ ).not.toBeInTheDocument();
+ });
+});
+
+test('should display an alert icon if hasCustomLabelColors=true', async () => {
+ const hasCustomLabelColorsProps = {
+ ...defaultProps,
+ hasCustomLabelColors: true,
+ };
+ setup(hasCustomLabelColorsProps);
+ await waitFor(() => {
+ expect(
+ screen.getByRole('img', { name: 'alert-solid' }),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.jsx
similarity index 53%
rename from superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx
rename to superset-frontend/src/explore/components/controls/ColorSchemeControl/index.jsx
index 20be45db038b0..f0ccc1239814f 100644
--- a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx
+++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.jsx
@@ -21,12 +21,15 @@ import PropTypes from 'prop-types';
import { isFunction } from 'lodash';
import { Select } from 'src/components';
import { Tooltip } from 'src/components/Tooltip';
-import { t } from '@superset-ui/core';
-import ControlHeader from '../ControlHeader';
+import { styled, t } from '@superset-ui/core';
+import Icons from 'src/components/Icons';
+import ControlHeader from 'src/explore/components/ControlHeader';
const propTypes = {
+ hasCustomLabelColors: PropTypes.bool,
+ dashboardId: PropTypes.number,
description: PropTypes.string,
- label: PropTypes.string.isRequired,
+ label: PropTypes.string,
labelMargin: PropTypes.number,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
@@ -43,16 +46,27 @@ const propTypes = {
const defaultProps = {
choices: [],
+ hasCustomLabelColors: false,
+ label: t('Color scheme'),
schemes: {},
clearable: false,
onChange: () => {},
};
+const StyledAlert = styled(Icons.AlertSolid)`
+ color: ${({ theme }) => theme.colors.alert.base};
+`;
+
export default class ColorSchemeControl extends React.PureComponent {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.renderOption = this.renderOption.bind(this);
+ this.renderLabel = this.renderLabel.bind(this);
+ this.dashboardColorSchemeAlert = t(
+ `The color scheme is determined by the related dashboard.
+ Edit the color scheme in the dashboard properties.`,
+ );
}
onChange(value) {
@@ -72,7 +86,7 @@ export default class ColorSchemeControl extends React.PureComponent {
}
return (
-
+
-
+
);
}
+ renderLabel() {
+ const { dashboardId, hasCustomLabelColors, label } = this.props;
+
+ if (hasCustomLabelColors || dashboardId) {
+ const alertTitle = hasCustomLabelColors
+ ? t(
+ `This color scheme is being overriden by custom label colors.
+ Check the JSON metadata in the Advanced settings`,
+ )
+ : this.dashboardColorSchemeAlert;
+ return (
+ <>
+ {label}{' '}
+
+
+
+ >
+ );
+ }
+ return label;
+ }
+
render() {
- const { schemes, choices } = this.props;
- // save parsed schemes for later
- this.schemes = isFunction(schemes) ? schemes() : schemes;
+ const { choices, dashboardId, schemes } = this.props;
+ let options = dashboardId
+ ? [
+ {
+ value: 'dashboard',
+ label: 'dashboard',
+ customLabel: (
+
+ {t('Dashboard scheme')}
+
+ ),
+ },
+ ]
+ : [];
+ let currentScheme = dashboardId ? 'dashboard' : undefined;
- const allColorOptions = (isFunction(choices) ? choices() : choices).filter(
- o => o[0] !== 'SUPERSET_DEFAULT',
- );
- const options = allColorOptions.map(([value]) => ({
- value,
- label: this.schemes?.[value]?.label || value,
- customLabel: this.renderOption(value),
- }));
-
- let currentScheme =
- this.props.value ||
- (this.props.default !== undefined ? this.props.default : undefined);
-
- if (currentScheme === 'SUPERSET_DEFAULT') {
- currentScheme = this.schemes?.SUPERSET_DEFAULT?.id;
+ // if related to a dashboard the scheme is dictated by the dashboard
+ if (!dashboardId) {
+ this.schemes = isFunction(schemes) ? schemes() : schemes;
+ const controlChoices = isFunction(choices) ? choices() : choices;
+ const allColorOptions = [];
+ const filteredColorOptions = controlChoices.filter(o => {
+ const option = o[0];
+ const isValidColorOption =
+ option !== 'SUPERSET_DEFAULT' && !allColorOptions.includes(option);
+ allColorOptions.push(option);
+ return isValidColorOption;
+ });
+
+ options = filteredColorOptions.map(([value]) => ({
+ customLabel: this.renderOption(value),
+ label: this.schemes?.[value]?.label || value,
+ value,
+ }));
+
+ currentScheme = this.props.value || this.props.default;
+
+ if (currentScheme === 'SUPERSET_DEFAULT') {
+ currentScheme = this.schemes?.SUPERSET_DEFAULT?.id;
+ }
}
const selectProps = {
ariaLabel: t('Select color scheme'),
allowClear: this.props.clearable,
+ disabled: !!dashboardId,
name: `select-${this.props.name}`,
onChange: this.onChange,
options,
- placeholder: `Select (${options.length})`,
+ placeholder: t('Select scheme'),
value: currentScheme,
};
+
return (
- } {...selectProps} />
+ }
+ {...selectProps}
+ />
);
}
}