refactor: Rewrites ColorSchemeControl with Typescript (#21496)

This commit is contained in:
Michael S. Molina 2022-09-22 08:29:02 -03:00 committed by GitHub
parent fb835d1437
commit bbac67a2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 191 additions and 229 deletions

View File

@ -1,43 +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.
*/
/* eslint-disable no-unused-expressions */
import React from 'react';
import { Select } from 'src/components';
import { getCategoricalSchemeRegistry } from '@superset-ui/core';
import { styledMount as mount } from 'spec/helpers/theming';
import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl';
const defaultProps = {
name: 'color_scheme',
label: 'Color Scheme',
options: getCategoricalSchemeRegistry()
.keys()
.map(s => [s, s]),
};
describe('ColorSchemeControl', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<ColorSchemeControl {...defaultProps} />);
});
it('renders a Select', () => {
expect(wrapper.find(Select)).toExist();
});
});

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import ColorSchemeControl from '.';
import ColorSchemeControl, { ColorSchemes } from '.';
const defaultProps = {
hasCustomLabelColors: false,
@ -28,7 +28,7 @@ const defaultProps = {
value: 'supersetDefault',
clearable: true,
choices: [],
schemes: () => null,
schemes: () => ({} as ColorSchemes),
isLinear: false,
};

View File

@ -1,184 +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 PropTypes from 'prop-types';
import { isFunction } from 'lodash';
import { Select } from 'src/components';
import { Tooltip } from 'src/components/Tooltip';
import { styled, t } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import ControlHeader from 'src/explore/components/ControlHeader';
import ColorSchemeLabel from './ColorSchemeLabel';
const propTypes = {
hasCustomLabelColors: PropTypes.bool,
dashboardId: PropTypes.number,
description: PropTypes.string,
label: PropTypes.string,
labelMargin: PropTypes.number,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string,
clearable: PropTypes.bool,
default: PropTypes.string,
choices: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.array),
PropTypes.func,
]),
schemes: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
isLinear: PropTypes.bool,
};
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) {
this.props.onChange(value);
}
renderOption(value) {
const { isLinear } = this.props;
const currentScheme = this.schemes[value];
// For categorical scheme, display all the colors
// For sequential scheme, show 10 or interpolate to 10.
// Sequential schemes usually have at most 10 colors.
let colors = [];
if (currentScheme) {
colors = isLinear ? currentScheme.getColors(10) : currentScheme.colors;
}
return (
<ColorSchemeLabel
id={currentScheme.id}
label={currentScheme.label}
colors={colors}
/>
);
}
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}{' '}
<Tooltip title={alertTitle}>
<StyledAlert iconSize="s" />
</Tooltip>
</>
);
}
return label;
}
render() {
const { choices, dashboardId, schemes } = this.props;
let options = dashboardId
? [
{
value: 'dashboard',
label: 'dashboard',
customLabel: (
<Tooltip title={this.dashboardColorSchemeAlert}>
{t('Dashboard scheme')}
</Tooltip>
),
},
]
: [];
let currentScheme = dashboardId ? 'dashboard' : undefined;
// 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: t('Select scheme'),
value: currentScheme,
};
return (
<Select
header={<ControlHeader {...this.props} label={this.renderLabel()} />}
{...selectProps}
/>
);
}
}
ColorSchemeControl.propTypes = propTypes;
ColorSchemeControl.defaultProps = defaultProps;

View File

@ -0,0 +1,189 @@
/**
* 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, { useMemo } from 'react';
import { ColorScheme, SequentialScheme, styled, t } from '@superset-ui/core';
import { isFunction } from 'lodash';
import { Select } from 'src/components';
import ControlHeader from 'src/explore/components/ControlHeader';
import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import ColorSchemeLabel from './ColorSchemeLabel';
export interface ColorSchemes {
[key: string]: ColorScheme;
}
export interface ColorSchemeControlProps {
hasCustomLabelColors: boolean;
dashboardId?: number;
label: string;
name: string;
onChange?: (value: string) => void;
value: string;
clearable: boolean;
defaultScheme?: string;
choices: string[][] | (() => string[][]);
schemes: ColorSchemes | (() => ColorSchemes);
isLinear: boolean;
}
const StyledAlert = styled(Icons.AlertSolid)`
color: ${({ theme }) => theme.colors.alert.base};
`;
const CUSTOM_LABEL_ALERT = t(
`This color scheme is being overriden by custom label colors.
Check the JSON metadata in the Advanced settings`,
);
const DASHBOARD_ALERT = t(
`The color scheme is determined by the related dashboard.
Edit the color scheme in the dashboard properties.`,
);
const Label = ({
label,
hasCustomLabelColors,
dashboardId,
}: Pick<
ColorSchemeControlProps,
'label' | 'hasCustomLabelColors' | 'dashboardId'
>) => {
if (hasCustomLabelColors || dashboardId) {
const alertTitle = hasCustomLabelColors
? CUSTOM_LABEL_ALERT
: DASHBOARD_ALERT;
return (
<>
{label}{' '}
<Tooltip title={alertTitle}>
<StyledAlert iconSize="s" />
</Tooltip>
</>
);
}
return <>{label}</>;
};
const ColorSchemeControl = ({
hasCustomLabelColors = false,
dashboardId,
label = t('Color scheme'),
name,
onChange = () => {},
value,
clearable = false,
defaultScheme,
choices = [],
schemes = {},
isLinear,
...rest
}: ColorSchemeControlProps) => {
const currentScheme = useMemo(() => {
if (dashboardId) {
return 'dashboard';
}
let result = value || defaultScheme;
if (result === 'SUPERSET_DEFAULT') {
const schemesObject = isFunction(schemes) ? schemes() : schemes;
result = schemesObject?.SUPERSET_DEFAULT?.id;
}
return result;
}, [dashboardId, defaultScheme, schemes, value]);
const options = useMemo(() => {
if (dashboardId) {
return [
{
value: 'dashboard',
label: 'dashboard',
customLabel: (
<Tooltip title={DASHBOARD_ALERT}>{t('Dashboard scheme')}</Tooltip>
),
},
];
}
const schemesObject = isFunction(schemes) ? schemes() : schemes;
const controlChoices = isFunction(choices) ? choices() : choices;
const allColorOptions: string[] = [];
const filteredColorOptions = controlChoices.filter(o => {
const option = o[0];
const isValidColorOption =
option !== 'SUPERSET_DEFAULT' && !allColorOptions.includes(option);
allColorOptions.push(option);
return isValidColorOption;
});
return filteredColorOptions.map(([value]) => {
const currentScheme = schemesObject[value];
// For categorical scheme, display all the colors
// For sequential scheme, show 10 or interpolate to 10.
// Sequential schemes usually have at most 10 colors.
let colors: string[] = [];
if (currentScheme) {
colors = isLinear
? (currentScheme as SequentialScheme).getColors(10)
: currentScheme.colors;
}
return {
customLabel: (
<ColorSchemeLabel
id={currentScheme.id}
label={currentScheme.label}
colors={colors}
/>
),
label: schemesObject?.[value]?.label || value,
value,
};
});
}, [choices, dashboardId, isLinear, schemes]);
// We can't pass on change directly because it receives a second
// parameter and it would be interpreted as the error parameter
const handleOnChange = (value: string) => onChange(value);
return (
<Select
header={
<ControlHeader
{...rest}
label={
<Label
label={label}
hasCustomLabelColors={hasCustomLabelColors}
dashboardId={dashboardId}
/>
}
/>
}
ariaLabel={t('Select color scheme')}
allowClear={clearable}
disabled={!!dashboardId}
name={`select-${name}`}
onChange={handleOnChange}
options={options}
placeholder={t('Select scheme')}
value={currentScheme}
/>
);
};
export default ColorSchemeControl;