Copilot commented on code in PR #36275:
URL: https://github.com/apache/superset/pull/36275#discussion_r2561796827
##########
superset-frontend/src/explore/components/ControlPanelsContainer.tsx:
##########
@@ -657,7 +658,29 @@ export const ControlPanelsContainer = (props:
ControlPanelsContainerProps) => {
}
if (isValidElement(controlItem)) {
// When the item is a React element
- return controlItem;
+ // return controlItem;
+
+ const controlName = (controlItem.props as { name: string })
+ .name;
+ const controlState = controlName
+ ? controls[controlName]
+ : undefined;
Review Comment:
Add a safety check for `controlName` before accessing
`controls[controlName]`. If `controlItem.props` doesn't have a `name` property,
this could lead to undefined behavior. Consider adding: `if (!controlName) {
return controlItem; }` after line 664.
```suggestion
if (!controlName) {
return controlItem;
}
const controlState = controls[controlName];
```
##########
superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controls/RotationControl.tsx:
##########
@@ -0,0 +1,74 @@
+/**
+ * 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 { t } from '@superset-ui/core';
+import { Select, SelectValue } from '@superset-ui/core/components';
+import { ControlHeader } from '@superset-ui/chart-controls';
+import { ControlComponentProps } from '@superset-ui/chart-controls';
+
+type RotationControlProps = ControlComponentProps<string> & {
+ choices?: [string, string][];
+ clearable?: boolean;
+};
+
+export default function RotationControl({
+ name = 'rotation',
+ value,
+ onChange,
+ choices = [
+ ['random', t('random')],
+ ['flat', t('flat')],
+ ['square', t('square')],
+ ],
+ label = t('Word Rotation'),
+ description = t('Rotation to apply to words in the cloud'),
+ renderTrigger = true,
+ clearable = false,
+}: RotationControlProps) {
+ return (
+ <div className="Control" data-test={name}>
+ <ControlHeader
+ name={name}
+ label={label}
+ description={description}
+ renderTrigger={renderTrigger}
+ />
+ <Select
+ value={value ?? 'square'}
+ options={choices.map(([key, text]) => ({ label: text, value: key }))}
+ onChange={(val: SelectValue) => {
+ if (val === null || val === undefined) {
+ return;
+ }
+ // Handle LabeledValue object
+ if (
+ typeof val === 'object' &&
+ 'value' in val &&
+ val.value !== undefined
+ ) {
+ onChange?.(val.value as string);
+ } else if (typeof val === 'string' || typeof val === 'number') {
+ // Handle raw value
+ onChange?.(String(val));
+ }
+ }}
+ allowClear={clearable}
+ />
+ </div>
+ );
+}
Review Comment:
Add a `displayName` property to the `RotationControl` component for
consistency with `ColorSchemeControlWrapper` (line 62 in
ColorSchemeControl.tsx) and to improve debugging. Add
`RotationControl.displayName = 'RotationControl';` after the function
definition.
```suggestion
}
RotationControl.displayName = 'RotationControl';
```
##########
superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controls/ColorSchemeControl/index.tsx:
##########
@@ -0,0 +1,333 @@
+/**
+ * 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 { useMemo, ReactNode } from 'react';
+
+import {
+ ColorScheme,
+ ColorSchemeGroup,
+ SequentialScheme,
+ t,
+ getLabelsColorMap,
+ CategoricalColorNamespace,
+} from '@superset-ui/core';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { sortBy } from 'lodash';
+import { ControlHeader } from '@superset-ui/chart-controls';
+import {
+ Tooltip,
+ Select,
+ type SelectOptionsType,
+} from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import ColorSchemeLabel from './ColorSchemeLabel';
+
+const getColorNamespace = (namespace?: string) => namespace || undefined;
+
+export type OptionData = SelectOptionsType[number]['options'][number] & {
+ searchText?: string;
+};
+
+export interface ColorSchemes {
+ [key: string]: ColorScheme;
+}
+
+export interface ColorSchemeControlProps {
+ hasCustomLabelsColor: boolean;
+ hasDashboardColorScheme?: boolean;
+ hasSharedLabelsColor?: boolean;
+ sharedLabelsColors?: string[];
+ mapLabelsColors?: Record<string, any>;
+ colorNamespace?: string;
+ chartId?: number;
+ dashboardId?: number;
+ label?: string;
+ name: string;
+ onChange?: (value: string) => void;
+ value: string;
+ clearable: boolean;
+ defaultScheme?: string;
+ choices: string[][] | (() => string[][]);
+ schemes: ColorSchemes | (() => ColorSchemes);
+ isLinear?: boolean;
+ description?: string;
+ hovered?: boolean;
+}
+
+const CUSTOM_LABEL_ALERT = t(
+ `The colors of this chart might be overridden by custom label colors of the
related dashboard.
+ 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 DASHBOARD_CONTEXT_ALERT = t(
+ `You are viewing this chart in a dashboard context with labels shared across
multiple charts.
+ The color scheme selection is disabled.`,
+);
+
+const DASHBOARD_CONTEXT_TOOLTIP = t(
+ `You are viewing this chart in the context of a dashboard that is directly
affecting its colors.
+ To edit the color scheme, open this chart outside of the dashboard.`,
+);
+
+const Label = ({
+ label,
+ dashboardId,
+ hasSharedLabelsColor,
+ hasCustomLabelsColor,
+ hasDashboardColorScheme,
+}: Pick<
+ ColorSchemeControlProps,
+ | 'label'
+ | 'dashboardId'
+ | 'hasCustomLabelsColor'
+ | 'hasSharedLabelsColor'
+ | 'hasDashboardColorScheme'
+>) => {
+ const theme = useTheme();
+ if (hasSharedLabelsColor || hasCustomLabelsColor || hasDashboardColorScheme)
{
+ const alertTitle =
+ hasCustomLabelsColor && !hasSharedLabelsColor
+ ? CUSTOM_LABEL_ALERT
+ : dashboardId && hasDashboardColorScheme
+ ? DASHBOARD_ALERT
+ : DASHBOARD_CONTEXT_ALERT;
+ return (
+ <>
+ {label}{' '}
+ <Tooltip title={alertTitle}>
+ <Icons.WarningOutlined
+ iconColor={theme.colorWarning}
+ css={css`
+ vertical-align: baseline;
+ `}
+ iconSize="s"
+ />
+ </Tooltip>
+ </>
+ );
+ }
+ return <>{label}</>;
+};
+
+const ColorSchemeControl = ({
+ hasCustomLabelsColor = false,
+ hasDashboardColorScheme = false,
+ mapLabelsColors = {},
+ sharedLabelsColors = [],
+ dashboardId,
+ colorNamespace,
+ chartId,
+ label = t('Color scheme'),
+ onChange = () => {},
+ value,
+ clearable = false,
+ defaultScheme,
+ choices = [],
+ schemes = {},
+ isLinear,
+ ...rest
+}: ColorSchemeControlProps) => {
+ const countSharedLabelsColor = sharedLabelsColors.length;
+ const colorMapInstance = getLabelsColorMap();
+ const chartLabels = chartId
+ ? colorMapInstance.chartsLabelsMap.get(chartId)?.labels || []
+ : [];
+ const hasSharedLabelsColor = !!(
+ dashboardId &&
+ countSharedLabelsColor > 0 &&
+ chartLabels.some(label => sharedLabelsColors.includes(label))
+ );
+ const hasDashboardScheme = dashboardId && hasDashboardColorScheme;
+ const showDashboardLockedOption = hasDashboardScheme || hasSharedLabelsColor;
+ const theme = useTheme();
+ const currentScheme = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return 'dashboard';
+ }
+ let result = value || defaultScheme;
+ if (result === 'SUPERSET_DEFAULT') {
+ const schemesObject = typeof schemes === 'function' ? schemes() :
schemes;
+ result = schemesObject?.SUPERSET_DEFAULT?.id;
+ }
+ return result;
+ }, [defaultScheme, schemes, showDashboardLockedOption, value]);
+
+ const options = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return [
+ {
+ value: 'dashboard',
+ label: (
+ <Tooltip title={DASHBOARD_CONTEXT_TOOLTIP}>
+ {t('Dashboard scheme')}
+ </Tooltip>
+ ),
+ },
+ ];
+ }
+ const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
+ const controlChoices = typeof choices === 'function' ? 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;
+ });
+
+ const groups = filteredColorOptions.reduce(
+ (acc, [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;
+ }
+ const option = {
+ label: (
+ <ColorSchemeLabel
+ id={currentScheme.id}
+ label={currentScheme.label}
+ colors={colors}
+ />
+ ) as ReactNode,
+ value,
+ searchText: currentScheme.label,
+ };
+ acc[currentScheme.group ??
ColorSchemeGroup.Other].options.push(option);
+ return acc;
+ },
+ {
+ [ColorSchemeGroup.Custom]: {
+ title: ColorSchemeGroup.Custom,
+ label: t('Custom color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Featured]: {
+ title: ColorSchemeGroup.Featured,
+ label: t('Featured color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Other]: {
+ title: ColorSchemeGroup.Other,
+ label: t('Other color palettes'),
+ options: [] as OptionData,
Review Comment:
Type mismatch: `options` is defined as `[] as OptionData` but `OptionData`
is a single option type, not an array type. This should be `[] as OptionData[]`
to correctly represent an array of options.
```suggestion
options: [] as OptionData[],
},
[ColorSchemeGroup.Featured]: {
title: ColorSchemeGroup.Featured,
label: t('Featured color palettes'),
options: [] as OptionData[],
},
[ColorSchemeGroup.Other]: {
title: ColorSchemeGroup.Other,
label: t('Other color palettes'),
options: [] as OptionData[],
```
##########
superset-frontend/src/explore/components/ControlPanelsContainer.tsx:
##########
@@ -657,7 +658,29 @@ export const ControlPanelsContainer = (props:
ControlPanelsContainerProps) => {
}
if (isValidElement(controlItem)) {
// When the item is a React element
- return controlItem;
+ // return controlItem;
Review Comment:
The comment on line 661 should be removed instead of being commented out. If
the code is being replaced, remove the old implementation entirely rather than
leaving it commented.
```suggestion
```
##########
superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controls/ColorSchemeControl/index.tsx:
##########
@@ -0,0 +1,333 @@
+/**
+ * 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 { useMemo, ReactNode } from 'react';
+
+import {
+ ColorScheme,
+ ColorSchemeGroup,
+ SequentialScheme,
+ t,
+ getLabelsColorMap,
+ CategoricalColorNamespace,
+} from '@superset-ui/core';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { sortBy } from 'lodash';
+import { ControlHeader } from '@superset-ui/chart-controls';
+import {
+ Tooltip,
+ Select,
+ type SelectOptionsType,
+} from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import ColorSchemeLabel from './ColorSchemeLabel';
+
+const getColorNamespace = (namespace?: string) => namespace || undefined;
+
+export type OptionData = SelectOptionsType[number]['options'][number] & {
+ searchText?: string;
+};
+
+export interface ColorSchemes {
+ [key: string]: ColorScheme;
+}
+
+export interface ColorSchemeControlProps {
+ hasCustomLabelsColor: boolean;
+ hasDashboardColorScheme?: boolean;
+ hasSharedLabelsColor?: boolean;
+ sharedLabelsColors?: string[];
+ mapLabelsColors?: Record<string, any>;
+ colorNamespace?: string;
+ chartId?: number;
+ dashboardId?: number;
+ label?: string;
+ name: string;
+ onChange?: (value: string) => void;
+ value: string;
+ clearable: boolean;
+ defaultScheme?: string;
+ choices: string[][] | (() => string[][]);
+ schemes: ColorSchemes | (() => ColorSchemes);
+ isLinear?: boolean;
+ description?: string;
+ hovered?: boolean;
+}
+
+const CUSTOM_LABEL_ALERT = t(
+ `The colors of this chart might be overridden by custom label colors of the
related dashboard.
+ 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 DASHBOARD_CONTEXT_ALERT = t(
+ `You are viewing this chart in a dashboard context with labels shared across
multiple charts.
+ The color scheme selection is disabled.`,
+);
+
+const DASHBOARD_CONTEXT_TOOLTIP = t(
+ `You are viewing this chart in the context of a dashboard that is directly
affecting its colors.
+ To edit the color scheme, open this chart outside of the dashboard.`,
+);
+
+const Label = ({
+ label,
+ dashboardId,
+ hasSharedLabelsColor,
+ hasCustomLabelsColor,
+ hasDashboardColorScheme,
+}: Pick<
+ ColorSchemeControlProps,
+ | 'label'
+ | 'dashboardId'
+ | 'hasCustomLabelsColor'
+ | 'hasSharedLabelsColor'
+ | 'hasDashboardColorScheme'
+>) => {
+ const theme = useTheme();
+ if (hasSharedLabelsColor || hasCustomLabelsColor || hasDashboardColorScheme)
{
+ const alertTitle =
+ hasCustomLabelsColor && !hasSharedLabelsColor
+ ? CUSTOM_LABEL_ALERT
+ : dashboardId && hasDashboardColorScheme
+ ? DASHBOARD_ALERT
+ : DASHBOARD_CONTEXT_ALERT;
+ return (
+ <>
+ {label}{' '}
+ <Tooltip title={alertTitle}>
+ <Icons.WarningOutlined
+ iconColor={theme.colorWarning}
+ css={css`
+ vertical-align: baseline;
+ `}
+ iconSize="s"
+ />
+ </Tooltip>
+ </>
+ );
+ }
+ return <>{label}</>;
+};
+
+const ColorSchemeControl = ({
+ hasCustomLabelsColor = false,
+ hasDashboardColorScheme = false,
+ mapLabelsColors = {},
+ sharedLabelsColors = [],
+ dashboardId,
+ colorNamespace,
+ chartId,
+ label = t('Color scheme'),
+ onChange = () => {},
+ value,
+ clearable = false,
+ defaultScheme,
+ choices = [],
+ schemes = {},
+ isLinear,
+ ...rest
+}: ColorSchemeControlProps) => {
+ const countSharedLabelsColor = sharedLabelsColors.length;
+ const colorMapInstance = getLabelsColorMap();
+ const chartLabels = chartId
+ ? colorMapInstance.chartsLabelsMap.get(chartId)?.labels || []
+ : [];
+ const hasSharedLabelsColor = !!(
+ dashboardId &&
+ countSharedLabelsColor > 0 &&
+ chartLabels.some(label => sharedLabelsColors.includes(label))
+ );
+ const hasDashboardScheme = dashboardId && hasDashboardColorScheme;
+ const showDashboardLockedOption = hasDashboardScheme || hasSharedLabelsColor;
+ const theme = useTheme();
+ const currentScheme = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return 'dashboard';
+ }
+ let result = value || defaultScheme;
+ if (result === 'SUPERSET_DEFAULT') {
+ const schemesObject = typeof schemes === 'function' ? schemes() :
schemes;
+ result = schemesObject?.SUPERSET_DEFAULT?.id;
+ }
+ return result;
+ }, [defaultScheme, schemes, showDashboardLockedOption, value]);
+
+ const options = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return [
+ {
+ value: 'dashboard',
+ label: (
+ <Tooltip title={DASHBOARD_CONTEXT_TOOLTIP}>
+ {t('Dashboard scheme')}
+ </Tooltip>
+ ),
+ },
+ ];
+ }
+ const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
+ const controlChoices = typeof choices === 'function' ? 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;
+ });
+
+ const groups = filteredColorOptions.reduce(
+ (acc, [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;
+ }
+ const option = {
+ label: (
+ <ColorSchemeLabel
+ id={currentScheme.id}
+ label={currentScheme.label}
+ colors={colors}
+ />
+ ) as ReactNode,
+ value,
+ searchText: currentScheme.label,
+ };
+ acc[currentScheme.group ??
ColorSchemeGroup.Other].options.push(option);
+ return acc;
+ },
+ {
+ [ColorSchemeGroup.Custom]: {
+ title: ColorSchemeGroup.Custom,
+ label: t('Custom color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Featured]: {
+ title: ColorSchemeGroup.Featured,
+ label: t('Featured color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Other]: {
+ title: ColorSchemeGroup.Other,
+ label: t('Other color palettes'),
+ options: [] as OptionData,
Review Comment:
Type mismatch: `options` is defined as `[] as OptionData` but `OptionData`
is a single option type, not an array type. This should be `[] as OptionData[]`
to correctly represent an array of options.
```suggestion
options: [] as OptionData[],
},
[ColorSchemeGroup.Featured]: {
title: ColorSchemeGroup.Featured,
label: t('Featured color palettes'),
options: [] as OptionData[],
},
[ColorSchemeGroup.Other]: {
title: ColorSchemeGroup.Other,
label: t('Other color palettes'),
options: [] as OptionData[],
```
##########
superset-frontend/src/utils/getControlsForVizType.ts:
##########
@@ -56,6 +57,18 @@ const memoizedControls = memoizeOne(
config: JsonObject;
};
controlsMap[controlObj.name] = controlObj.config;
+ } else if (React.isValidElement(control)) {
+ const { name } = control.props as { name: string };
+ if (name) {
+ const ComponentType = control.type as React.ComponentType;
+ console.log('ComponentType', ComponentType);
Review Comment:
Remove debug console.log statement. This should not be committed to
production code.
```suggestion
```
##########
superset-frontend/plugins/plugin-chart-word-cloud/test/RotationControl.test.tsx:
##########
@@ -0,0 +1,47 @@
+/**
+ * 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 { RotationControl } from '../src/plugin/controls';
+import { render, screen } from 'spec/helpers/testing-library';
+
+const setup = (props = {}) => {
+ const defaultProps = {
+ name: 'rotation',
+ value: 'square',
+ onChange: jest.fn(),
+ };
+ return render(<RotationControl {...defaultProps} {...props} />);
+};
+
+test('renders rotation control with label', () => {
+ setup();
+ expect(screen.getByText('Word Rotation')).toBeInTheDocument();
+});
+
+test('renders select with default value', () => {
+ setup({ value: 'flat' });
+ // Check that the select is rendered (implementation depends on Select
component)
+ expect(screen.getByTestId('rotation')).toBeInTheDocument();
+});
+
+test('calls onChange when value changes', () => {
+ const onChange = jest.fn();
+ setup({ onChange });
+ // Test onChange is called when select value changes
+});
Review Comment:
This test is incomplete. The comment on line 46 says "Test onChange is
called when select value changes" but there's no actual test implementation.
Either implement the test or remove this empty test case.
##########
superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controls/ColorSchemeControl/index.tsx:
##########
@@ -0,0 +1,333 @@
+/**
+ * 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 { useMemo, ReactNode } from 'react';
+
+import {
+ ColorScheme,
+ ColorSchemeGroup,
+ SequentialScheme,
+ t,
+ getLabelsColorMap,
+ CategoricalColorNamespace,
+} from '@superset-ui/core';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { sortBy } from 'lodash';
+import { ControlHeader } from '@superset-ui/chart-controls';
+import {
+ Tooltip,
+ Select,
+ type SelectOptionsType,
+} from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import ColorSchemeLabel from './ColorSchemeLabel';
+
+const getColorNamespace = (namespace?: string) => namespace || undefined;
+
+export type OptionData = SelectOptionsType[number]['options'][number] & {
+ searchText?: string;
+};
+
+export interface ColorSchemes {
+ [key: string]: ColorScheme;
+}
+
+export interface ColorSchemeControlProps {
+ hasCustomLabelsColor: boolean;
+ hasDashboardColorScheme?: boolean;
+ hasSharedLabelsColor?: boolean;
+ sharedLabelsColors?: string[];
+ mapLabelsColors?: Record<string, any>;
+ colorNamespace?: string;
+ chartId?: number;
+ dashboardId?: number;
+ label?: string;
+ name: string;
+ onChange?: (value: string) => void;
+ value: string;
+ clearable: boolean;
+ defaultScheme?: string;
+ choices: string[][] | (() => string[][]);
+ schemes: ColorSchemes | (() => ColorSchemes);
+ isLinear?: boolean;
+ description?: string;
+ hovered?: boolean;
+}
+
+const CUSTOM_LABEL_ALERT = t(
+ `The colors of this chart might be overridden by custom label colors of the
related dashboard.
+ 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 DASHBOARD_CONTEXT_ALERT = t(
+ `You are viewing this chart in a dashboard context with labels shared across
multiple charts.
+ The color scheme selection is disabled.`,
+);
+
+const DASHBOARD_CONTEXT_TOOLTIP = t(
+ `You are viewing this chart in the context of a dashboard that is directly
affecting its colors.
+ To edit the color scheme, open this chart outside of the dashboard.`,
+);
+
+const Label = ({
+ label,
+ dashboardId,
+ hasSharedLabelsColor,
+ hasCustomLabelsColor,
+ hasDashboardColorScheme,
+}: Pick<
+ ColorSchemeControlProps,
+ | 'label'
+ | 'dashboardId'
+ | 'hasCustomLabelsColor'
+ | 'hasSharedLabelsColor'
+ | 'hasDashboardColorScheme'
+>) => {
+ const theme = useTheme();
+ if (hasSharedLabelsColor || hasCustomLabelsColor || hasDashboardColorScheme)
{
+ const alertTitle =
+ hasCustomLabelsColor && !hasSharedLabelsColor
+ ? CUSTOM_LABEL_ALERT
+ : dashboardId && hasDashboardColorScheme
+ ? DASHBOARD_ALERT
+ : DASHBOARD_CONTEXT_ALERT;
+ return (
+ <>
+ {label}{' '}
+ <Tooltip title={alertTitle}>
+ <Icons.WarningOutlined
+ iconColor={theme.colorWarning}
+ css={css`
+ vertical-align: baseline;
+ `}
+ iconSize="s"
+ />
+ </Tooltip>
+ </>
+ );
+ }
+ return <>{label}</>;
+};
+
+const ColorSchemeControl = ({
+ hasCustomLabelsColor = false,
+ hasDashboardColorScheme = false,
+ mapLabelsColors = {},
+ sharedLabelsColors = [],
+ dashboardId,
+ colorNamespace,
+ chartId,
+ label = t('Color scheme'),
+ onChange = () => {},
+ value,
+ clearable = false,
+ defaultScheme,
+ choices = [],
+ schemes = {},
+ isLinear,
+ ...rest
+}: ColorSchemeControlProps) => {
+ const countSharedLabelsColor = sharedLabelsColors.length;
+ const colorMapInstance = getLabelsColorMap();
+ const chartLabels = chartId
+ ? colorMapInstance.chartsLabelsMap.get(chartId)?.labels || []
+ : [];
+ const hasSharedLabelsColor = !!(
+ dashboardId &&
+ countSharedLabelsColor > 0 &&
+ chartLabels.some(label => sharedLabelsColors.includes(label))
+ );
+ const hasDashboardScheme = dashboardId && hasDashboardColorScheme;
+ const showDashboardLockedOption = hasDashboardScheme || hasSharedLabelsColor;
+ const theme = useTheme();
+ const currentScheme = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return 'dashboard';
+ }
+ let result = value || defaultScheme;
+ if (result === 'SUPERSET_DEFAULT') {
+ const schemesObject = typeof schemes === 'function' ? schemes() :
schemes;
+ result = schemesObject?.SUPERSET_DEFAULT?.id;
+ }
+ return result;
+ }, [defaultScheme, schemes, showDashboardLockedOption, value]);
+
+ const options = useMemo(() => {
+ if (showDashboardLockedOption) {
+ return [
+ {
+ value: 'dashboard',
+ label: (
+ <Tooltip title={DASHBOARD_CONTEXT_TOOLTIP}>
+ {t('Dashboard scheme')}
+ </Tooltip>
+ ),
+ },
+ ];
+ }
+ const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
+ const controlChoices = typeof choices === 'function' ? 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;
+ });
+
+ const groups = filteredColorOptions.reduce(
+ (acc, [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;
+ }
+ const option = {
+ label: (
+ <ColorSchemeLabel
+ id={currentScheme.id}
+ label={currentScheme.label}
+ colors={colors}
+ />
+ ) as ReactNode,
+ value,
+ searchText: currentScheme.label,
+ };
+ acc[currentScheme.group ??
ColorSchemeGroup.Other].options.push(option);
+ return acc;
+ },
+ {
+ [ColorSchemeGroup.Custom]: {
+ title: ColorSchemeGroup.Custom,
+ label: t('Custom color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Featured]: {
+ title: ColorSchemeGroup.Featured,
+ label: t('Featured color palettes'),
+ options: [] as OptionData,
+ },
+ [ColorSchemeGroup.Other]: {
+ title: ColorSchemeGroup.Other,
+ label: t('Other color palettes'),
+ options: [] as OptionData,
Review Comment:
Type mismatch: `options` is defined as `[] as OptionData` but `OptionData`
is a single option type, not an array type. This should be `[] as OptionData[]`
to correctly represent an array of options.
```suggestion
options: [] as OptionData[],
},
[ColorSchemeGroup.Featured]: {
title: ColorSchemeGroup.Featured,
label: t('Featured color palettes'),
options: [] as OptionData[],
},
[ColorSchemeGroup.Other]: {
title: ColorSchemeGroup.Other,
label: t('Other color palettes'),
options: [] as OptionData[],
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]