You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "rusackas (via GitHub)" <gi...@apache.org> on 2026/08/08 07:27:58 UTC

[PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

rusackas opened a new pull request, #42910:
URL: https://github.com/apache/superset/pull/42910

   <!---
   Please write the PR title following the conventions at https://www.conventionalcommits.org/en/v1.0.0/
   Example:
   fix(dashboard): load charts correctly
   -->
   
   ### SUMMARY
   
   Follow-up to #42053, which upgraded `ColorPickerControl` (custom presets, `resolveThemeTokens`, `outputFormat`) and used it to fix `ConditionalFormattingControl`. This PR finds and fixes the four remaining places in the app that still used a legacy/degraded color-selection UI instead of the shared picker, one commit per item.
   
   #### 1. Gauge chart interval colors
   
   `Gauge/controlPanel.tsx`'s "Interval colors" control asked users to type comma-separated 1-indexed positions into the chosen color scheme (e.g. `1,2,4`), with zero visual feedback and silent discarding of malformed input.
   
   Replaced with `IntervalColorsControl`: one `ColorPickerControl` per interval bound (parsed from the existing `intervals` control), storing real hex colors in a new `interval_colors` field, positionally matched to those bounds.
   
   **Design decision:** bounds stay owned by the existing `intervals` text control rather than being folded into the new control's own row list (which the row-list add/remove pattern in the task brief technically implied). This keeps a single source of truth for bounds and avoids needing two-way sync between two independent controls — the new control's row *count* simply tracks whatever `intervals` currently contains.
   
   **Backward compatibility:** charts saved before this control existed only have `interval_color_indices` (the old index strings). `getIntervalBoundsAndColors` in `transformProps.ts` still resolves those indices against the categorical scheme at render time whenever `interval_colors` is empty, so existing dashboards render identically with no migration. The control also resolves legacy indices to real colors for display the first time such a chart's panel is reopened (editor convenience only, not required for correct rendering).
   
   ![Gauge: new Interval colors control + rendered chart](https://gist.githubusercontent.com/rusackas/b1accdefe51ca5c3f14bbec364dddc92/raw/976039b415ea5b32b3b67f6326d8eac5bab01df4/1-gauge-controls.png)
   
   #### 2. Bullet chart band colors
   
   Bullet chart background bands were hardcoded to a 4-step theme-token ramp with **no** color control at all — a genuinely new feature, not a swap.
   
   Added an optional `range_colors` control (`BulletRangeColorsControl`): one `ColorPickerControl` per threshold parsed from the existing `ranges` control, each starting unset ("use default") with a "Use default" link to clear a customization. `transformProps.ts` captures each range's chosen color by its *original* (pre-sort) position in `ranges` before the existing largest-first band sort reorders them for nested drawing, so colors stay pinned to the correct threshold regardless of draw order.
   
   **Backward compatible by construction:** `range_colors` is optional and defaults to empty, so charts saved before this control existed have no such field and keep rendering with the exact default ramp.
   
   ![Bullet: new Range colors control + rendered chart with custom band colors](https://gist.githubusercontent.com/rusackas/b1accdefe51ca5c3f14bbec364dddc92/raw/39973cccdc468af8ff3ba8e1515a217649585554/2-bullet-controls.png)
   
   #### 3. Big Number Period-over-Period comparison colors
   
   The comparison-color control was a 2-choice `SelectControl` ("Green for increase, red for decrease" / reverse) bound directly to `theme.colorSuccess`/`theme.colorError`.
   
   Replaced with two `ColorPickerControl`s, `increase_color` / `decrease_color`, using the exact `resolveThemeTokens` + `outputFormat="hex"` pattern #42053 introduced: picking the Green/Red preset swatch stores the token *name* (so it still reads the same as before for users who just want the classic behavior), while any other pick stores a literal hex color. Exported `SPECIAL_COLORS`/`SpecialColorKey` from `ColorPickerControl.tsx` so this call site doesn't redefine the Green/Red mapping.
   
   The color→style resolution moved into two small, independently unit-tested pure functions in `utils.ts` (`resolveComparisonColorKeys`, `getComparisonColorTokens`) rather than living inline in `PopKPI`'s render body — jsdom doesn't reliably expose emotion's injected styles to `toHaveStyle` for direct component assertions, so the logic needed to be testable on its own.
   
   **Backward compatibility:** `resolveComparisonColorKeys` falls back to the legacy `comparisonColorScheme` field (kept, `@deprecated` in `types.ts`) whenever the new fields are absent — including correctly *reversing* increase/decrease for charts saved with the old "Red for increase, green for decrease" choice, the case a naive default-to-Green migration would have silently broken.
   
   ![Big Number PoP: new Color for increase/decrease pickers + rendered comparison](https://gist.githubusercontent.com/rusackas/b1accdefe51ca5c3f14bbec364dddc92/raw/635e9480ae84aa08607f82349b59ca33b49e71bd/3-pop-controls.png)
   
   #### 4. Admin Theme editor curated colors
   
   `ThemeModal.tsx` only exposed antd theming as a single JSON textarea, requiring admins to paste in a whole token object from an external tool to change even one color.
   
   Added a "Colors" section (`ThemeColorPickers`) above the JSON textarea with one `ColorPickerControl` per curated antd token — the 5 SEED colors (`colorPrimary`, `colorSuccess`, `colorWarning`, `colorError`, `colorInfo`) plus 6 load-bearing map/alias tokens (`colorLink`, `colorText`, `colorTextSecondary`, `colorBgBase`, `colorBgContainer`, `colorBorder`). This intentionally does **not** attempt the full 100+ token surface — everything else stays fully editable via the JSON textarea, which remains the source of truth. Names are taken directly from antd's own `SeedToken`/`MapToken` types, not invented.
   
   Sync is two-way, via two small pure functions (`tryParseThemeJson`, `patchThemeJsonToken`):
   - **Picker → JSON:** patches just that key into the JSON's `token` object and re-serializes with the modal's existing 2-space indent, preserving every other key (curated or not).
   - **JSON → pickers:** each render re-parses the textarea's current value and re-derives picker values from it.
   - **Invalid/mid-edit JSON:** `tryParseThemeJson` returns `null` instead of throwing (matching the file's existing `isValidJson` convention); the section shows a small notice and pickers stop persisting edits until the JSON is valid again.
   
   The section is hidden for read-only system themes, matching the existing Format/Apply button visibility.
   
   **No backward-compat concern** — additive UI over the same JSON, nothing about existing saved themes changes.
   
   ![Theme editor: curated Colors section synced from JSON](https://gist.githubusercontent.com/rusackas/b1accdefe51ca5c3f14bbec364dddc92/raw/069dad8a867c9ce33580f50551f1111a7ab95cc2/4-theme-modal.png)
   
   ### TESTING INSTRUCTIONS
   
   - `npm run test` in `superset-frontend/` — new/updated suites:
     - `plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts`
     - `src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx`
     - `plugins/plugin-chart-echarts/test/Bullet/transformProps.test.ts`
     - `src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx`
     - `plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/{utils,PopKPI}.test.tsx`
     - `src/features/themes/{ThemeModal,ThemeColorPickers}.test.tsx`
   - Manually: create/edit a Gauge chart, expand Customize → Intervals; create/edit a Bullet chart, expand Customize → Range colors; create/edit a Big Number w/ Time Comparison chart with "Add color for positive/negative change" enabled; open Settings → Themes → + Theme.
   - Backward compatibility: open an existing Gauge chart saved with `interval_color_indices` only, an existing Bullet chart with no `range_colors`, and a Big Number PoP chart saved with only `comparison_color_scheme` (including the `Red` value) — all three should render identically to before this PR.
   
   ### ADDITIONAL INFORMATION
   <!--- Check any relevant boxes with "x" -->
   <!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [x] Changes UI
   - [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [x] Introduces new feature or API
   - [ ] Removes existing feature or API
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sha174n (via GitHub)" <gi...@apache.org>.
sha174n commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5281443710

   Nice consolidation, and the one-commit-per-item structure keeps it bisectable with real backward-compat handling on each swap (legacy interval_color_indices / comparison_color_scheme fallbacks, additive optional fields). One process question: this bundles four unrelated areas (Gauge, Bullet, Big Number PoP, Theme editor), each with its own new control dir and backward-compat surface — might be worth confirming maintainers are fine reviewing it as a single PR vs splitting per area. Minor: ThemeColorPickers re-parses the JSON textarea on each render (the useMemo keyed on jsonData mitigates it).
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4163008240


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +104,45 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed = formData.comparison_color_scheme === 'Red';
+    formData.increase_color = legacyReversed ? 'Red' : 'Green';
+    formData.decrease_color = legacyReversed ? 'Green' : 'Red';

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicated legacy color scheme rule</b></div>
   <div id="fix">
   
   This block re-implements the legacy-reversal rule already encoded in `resolveComparisonColorKeys` (BigNumberPeriodOverPeriod/utils.ts) using hardcoded 'Red'/'Green' literals instead of the shared `ColorSchemeEnum` from '@superset-ui/chart-controls'. If the legacy scheme semantics ever change, the two copies can diverge and migrated charts would disagree with the render-time fallback. Reusing `ColorSchemeEnum` keeps migration and render fallback in lockstep.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #ec4479</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,124 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Unsound double type assertion</b></div>
   <div id="fix">
   
   The double assertion `theme as unknown as Record<string, string>` is unsound: `SupersetTheme` also holds non-string fields (e.g. `sizeUnit`), so `themeColors[colorValue]` can yield a non-string typed as `string`, discarding type checks contrary to dev-standard.mdc's type-safety rule. Cast to `Record<string, unknown>` and guard with `typeof === 'string'` so the `colorValue` fallback stays honest.
   </div>
   
   
   </div>
   
   <details>
   <summary><b>Citations</b></summary>
   <ul>
   
   <li>
   Rule Violated: <a href="https://github.com/apache/superset/blob/3b4bb77/.cursor/rules/dev-standard.mdc#L16">dev-standard.mdc:16</a>
   </li>
   
   </ul>
   </details>
   
   
   
   
   <small><i>Code Review Run #ec4479</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { Button } from '@superset-ui/core/components';
+import {
+  tokenizeToNumericArray,
+  isRangesInputComplete,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow,
+  RangeLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { BulletRangeColorsControlProps } from './types';
+
+/**
+ * Per-range color editor for the Bullet chart. Row *count* is driven by the
+ * sibling `ranges` control (one row per parsed threshold) so range values
+ * keep a single source of truth; this control only owns colors, stored as
+ * an array of hex strings positionally matched to those thresholds. An
+ * empty string entry (or a missing entry past the end of the array) means
+ * "no custom color" -- `Bullet/transformProps.ts` falls back to the default
+ * theme-token band ramp for that range, so existing saved Bullet charts
+ * (which have no `range_colors` at all) render identically to before this
+ * control existed.
+ */
+export default function BulletRangeColorsControl({
+  value,
+  onChange,
+  ranges,
+  ...headerProps
+}: BulletRangeColorsControlProps) {
+  const rangeValues = tokenizeToNumericArray(ranges) ?? [];
+  // While `ranges` is mid-edit (e.g. "20,,60"), tokenizeToNumericArray's
+  // leniency silently compacts it to a shorter list, which would otherwise
+  // shift colors to the wrong positions. Block commits until it's complete
+  // again so no edit can be positionally misaligned once it is.
+  const rangesComplete = isRangesInputComplete(ranges);
+
+  const colorAt = (index: number): string => value?.[index] || '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, color, colorAt));
+  };
+
+  const handleReset = (index: number) => {
+    if (!rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, '', colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {rangeValues.length === 0 ? (
+        <RangeLabel>
+          {t('Add ranges above to configure their colors here.')}
+        </RangeLabel>
+      ) : (
+        rangeValues.map((range, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <RangeRow key={index}>

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Fragmented i18n label</b></div>
   <div id="fix">
   
   Line 83 fragments the translatable string: `t('Up to')` is a standalone message concatenated with `{range}` as a separate JSX node, so translators cannot reorder the number inside the phrase. The `ariaLabel` on line 86 already uses the interpolated form `t('Color for range up to %s', range)`; use `t('Up to %s', range)` here too for consistent, locallyizable output.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #ec4479</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx:
##########
@@ -0,0 +1,144 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import BulletRangeColorsControl from 'src/explore/components/controls/BulletRangeColorsControl';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no ranges are configured', () => {
+  render(<BulletRangeColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add ranges above to configure their colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed range, unset by default', () => {
+  render(
+    <BulletRangeColorsControl onChange={jest.fn()} ranges="100,200,300" />,
+  );
+
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 200/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 300/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+  // no row has a custom color yet, so no "Use default" reset link shows
+  expect(screen.queryByText('Use default')).not.toBeInTheDocument();
+});
+
+test('shows a "Use default" reset link only for rows with a custom color', () => {
+  render(
+    <BulletRangeColorsControl
+      onChange={jest.fn()}
+      ranges="100,200"
+      value={['#ff0000', '']}
+    />,
+  );
+
+  expect(screen.getAllByText('Use default')).toHaveLength(1);
+});
+
+test('calls onChange with the full color array when a row color is picked', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['', '']}
+    />,
+  );
+
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+
+  await userEvent.click(triggers[0]);
+  await waitFor(() => {
+    expect(document.querySelector('.ant-color-picker')).toBeInTheDocument();
+  });
+
+  const hexInput = document.querySelector<HTMLInputElement>(
+    '.ant-color-picker-input input',
+  );
+  expect(hexInput).toBeInTheDocument();
+  await userEvent.clear(hexInput!);
+  await userEvent.type(hexInput!, '00ff00{enter}');
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalled();
+  });
+  const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
+  expect(lastCall[0].toLowerCase()).toBe('#00ff00');
+  expect(lastCall[1]).toBe('');
+});
+
+test('resets a single row back to "no custom color" without touching the others', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['#ff0000', '#00ff00']}
+    />,
+  );
+
+  const resetButtons = screen.getAllByText('Use default');
+  expect(resetButtons).toHaveLength(2);
+  await userEvent.click(resetButtons[0]);
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalledWith(['', '#00ff00']);
+  });
+});
+
+test('disables editing and resets while ranges has a blank token between numbers', () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="20,,60"
+      value={['#ff0000', '#00ff00', '#0000ff']}
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // collapse row count or allow commits that would misalign colors once
+  // the blank is filled back in.
+  document.querySelectorAll('.ant-color-picker-trigger').forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+  // AntD's Button always wraps its text child in an inner <span>, so
+  // querying by text here would match that span -- which `toBeDisabled`
+  // never recognizes (it only supports actual form-control elements).
+  // Query by role to get the real <button>, where `disabled` lives.
+  screen.getAllByRole('button', { name: 'Use default' }).forEach(button => {
+    expect(button).toBeDisabled();
+  });
+});

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing negative onChange assertion</b></div>
   <div id="fix">
   
   This test pins the disabled UI state but never asserts `onChange` stays silent, though it creates and passes the `onChange` mock (line 125). `BulletRangeColorsControl`'s `handleColorChange`/`handleReset` guard on `rangesComplete`, so `expect(onChange).not.toHaveBeenCalled()` would pass today and would catch a regression that drops that guard. Per BITO.md rule 6262, assert the behavior the test name claims.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #ec4479</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4153174510


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,60 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .map(part => (part === '' ? NaN : Number(part)));
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
+          schemeColors.length
+      ];
+    }
+    return schemeColors[index % schemeColors.length];

Review Comment:
   Good catch, fixed. The fallback was using the bound's absolute position instead of counting just the entries missing a legacy index.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,94 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string') return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };

Review Comment:
   Good catch, added the same `isRangesInputComplete` guard here and disabled the picker until the bounds list is complete.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4165187742


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,124 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;

Review Comment:
   <!-- Bito Reply -->
   The suggestion to use `Record<string, unknown>` and a `typeof === 'string'` guard is correct and improves type safety. It avoids the unsoundness of the double assertion `theme as unknown as Record<string, string>` by ensuring that non-string fields in `SupersetTheme` are not incorrectly treated as strings, which aligns with the project's type-safety standards.
   
   **superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts**
   ```
   const themeColors = theme as unknown as Record<string, unknown>;
     if (typeof themeColors[colorValue] === 'string') {
       return {
         text: themeColors[colorValue] as string,
         background: `${themeColors[colorValue]}${COMPARISON_TINT_ALPHA_HEX}`,
         strongText: themeColors[colorValue] as string,
       };
     }
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4374893972


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,384 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  // Pin the row count first so the per-row assertions can't pass vacuously.
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+  triggers.forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+
+  // A change attempt on a disabled picker must neither open it nor commit.
+  await userEvent.click(triggers[0]);
+  expect(
+    document.querySelector('.ant-color-picker-presets-color'),
+  ).not.toBeInTheDocument();
+  expect(onChange).not.toHaveBeenCalled();
+});
+
+test('explicit value takes precedence over legacy indices', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      value={['#ff0000', '#00ff00']}
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#FF0000')).toBeInTheDocument();
+  expect(screen.getByText('#00FF00')).toBeInTheDocument();
+});
+
+test('a non-positive legacy index falls back to the positional scheme color like the renderer', () => {
+  // The renderer computes `colors[(ind - 1) % n]`, which is `undefined` for
+  // index 0, so that band uses `colors[idx]`; it must not wrap to the last
+  // scheme color.
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="0,2"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.queryByText('#2CA02C')).not.toBeInTheDocument();
+});
+
+test('stays read-only when legacy indices cannot be resolved so untouched bands are never rewritten', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,60"

Review Comment:
   Good catch, @sadpandajoe. Added a case rendering with `value={[]}`, `intervals="20,60"` and `legacyIntervalColorIndices="10,3"`. It asserts both triggers carry the disabled class and a click leaves `onChange` uncalled.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4168638778


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,100 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !boundsComplete) return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {bounds.length === 0 ? (
+        <BoundLabel>
+          {t('Add interval bounds above to configure colors here.')}
+        </BoundLabel>
+      ) : (
+        bounds.map((bound, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <IntervalRow key={index}>

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Inconsistent i18n label interpolation</b></div>
   <div id="fix">
   
   This renders the row label as two nodes — `t('Up to')` plus `{bound}` — while sibling `BulletRangeColorsControl` (line 82) and this file's own `ariaLabel` (line 89) use the placeholder form `t('Up to %s', bound)`. The split msgid fragments the translation unit and hardcodes English word order, so translators cannot reposition the number. Use the placeholder form for consistency and correct i18n.
   </div>
   
   
   </div>
   
   <details>
   <summary><b>Citations</b></summary>
   <ul>
   
   <li>
   Rule Violated: <a href="https://github.com/apache/superset/blob/8f9a239/.cursor/rules/dev-standard.mdc#L119">dev-standard.mdc:119</a>
   </li>
   
   </ul>
   </details>
   
   
   
   
   <small><i>Code Review Run #c5f638</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4172963655


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,26 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',

Review Comment:
   The old text input was keyboard-editable, but these rows now use a click-only color-picker trigger with no tab stop or Enter/Space handler, so keyboard-only users cannot change interval colors. Could the picker expose a focusable, keyboard-activatable trigger?



##########
superset/translations/messages.pot:
##########
@@ -15385,6 +15450,12 @@ msgstr ""
 msgid "Unverified"
 msgstr ""
 
+msgid "Up to"

Review Comment:
   The translation check and Python catalog-drift test fail because the removed `Up to` label still has a catalog entry; the remaining `%s` message also lacks its format-validation flag. Could these entries be synchronized with the current labels?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4086988998


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);

Review Comment:
   <!-- Bito Reply -->
   The user's observation is correct. In JavaScript, spreading an object into a new object literal (`{...parsed, token: {...}}`) does not reorder existing keys if the key already exists; it only appends new keys to the end. Since `token` is already a key in `parsed`, its position remains unchanged, and the suggestion to preserve key order is not strictly necessary for this implementation.
   
   **superset-frontend/src/features/themes/ThemeColorPickers.tsx**
   ```
   const next = {
       ...parsed,
       token: {
         ...parsed.token,
         [key]: value,
       },
     };
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4141826549


##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import { tokenizeToNumericArray } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;

Review Comment:
   Fair point, extracted the shared row layout (`RangeRow`/`RangeLabel`) and the color-array-update logic into `controls/shared/RangeColorRow.tsx`, used by both controls now. Kept the legacy-color resolution and reset-button pieces separate since those genuinely differ.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,97 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
+    ? resolvedColor.slice(0, 7)
+    : resolvedColor;
+  return {
+    text: resolvedColor,
+    background: `${opaqueColor}${COMPARISON_TINT_ALPHA_HEX}`,

Review Comment:
   Good catch, fixed — the tint suffix is now only appended to 6/8-digit hex values; anything else (like an rgba token) passes through unchanged. Added a regression test in utils.test.ts.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+  expect(screen.getByText('-20')).toBeInTheDocument();
+});
+
+// The precise color resolution (legacy `comparisonColorScheme` fallback,
+// custom hex precedence, the neutral disabled state) is covered by pure
+// unit tests against `resolveComparisonColorKeys` / `getComparisonColorTokens`
+// in utils.test.ts, since emotion's `css` prop styles aren't reliably
+// observable through jsdom's computed styles. These tests instead confirm
+// PopKPI renders without crashing across the same prop combinations.

Review Comment:
   You're right, added a real `toHaveStyleRule` assertion on the arrow indicator and fixed the comment.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309437209


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -35,6 +36,30 @@ import {
   metricNameFontSizeWithVisibility,
 } from '../sharedControls';
 
+// Shared base for the `increase_color` / `decrease_color` pickers so the
+// presets, token resolution, output format and visibility rule can't drift
+// apart. No static default: charts saved before these controls existed only
+// have `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+// increase/decrease colors). Leaving each control's value undefined lets
+// `resolveComparisonColorKeys` (see BigNumberPeriodOverPeriod/utils.ts)
+// resolve the correct color from that legacy scheme at render time. A
+// hardcoded default here would win over the legacy fallback via
+// `applyDefaultFormData` and silently repaint old dashboards.
+const comparisonColorControlConfig: CustomControlItem['config'] = {
+  type: 'ColorPickerControl',
+  renderTrigger: true,
+  presets: [
+    {
+      label: t('Semantic colors'),
+      colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],

Review Comment:
   Right, the picker was always showing SPECIAL_COLORS'\'' fixed muted green for '\''Green'\'' regardless of what colorSuccess actually resolves to. It now checks for a matching theme token first and shows that, falling back to the fixed preset color only when there'\''s no live token to resolve against. Should keep the swatch honest even if colorSuccess gets restyled.



##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,55 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  // Skipped on the dashboard hydration path: the dashboard's `color_scheme`
+  // override is applied after this runs, so freezing literal colors here would
+  // pin the chart's own palette. The renderer resolves the legacy indices
+  // against the effective palette instead.
+  if (
+    resolveLegacyGaugeColors &&
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(

Review Comment:
   You'\''re right, and I think the comment'\''s own rationale here doesn'\''t actually hold up. interval_color_indices is not dropped by a save even without this migration, form_data gets persisted as a plain object rather than rebuilt from registered controls, so an untouched legacy string survives a save on its own. So I just turned the eager resolution off by default, same as the dashboard path already does, and only materialize literal colors once the user actually touches a color in the picker.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4362311026


##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2773,18 +2809,24 @@ def threshold_details(
         )
 
     layers: list[dict[str, Any]] = []
+    # Custom colors (if any) are authored positionally against the original,
+    # pre-sort `ranges` order via the `range_colors` control -- capture each
+    # range's color here, before the descending sort below reorders them.
+    # Mirrors Bullet/transformProps.ts exactly, so the MCP preview matches
+    # what Explore actually renders for the saved chart.
     range_entries = sorted(
         [
             (
                 threshold,
                 label_at(model.range_labels, index, threshold, "Range"),
+                model.range_colors[index] if index < len(model.range_colors) else "",

Review Comment:
   @sadpandajoe good catch. The preview now drops `range_colors` unless `ranges` was user-entered, so the synthesized band uses the default fill like Explore. Added a test.



##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########


Review Comment:
   @SBIN2010 yes. With `resolveThemeTokens` on, the presets now go through `toDisplayHex`, so the Green chip matches the trigger hex.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4066176725


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,174 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Pickers enabled but edits dropped</b></div>
   <div id="fix">
   
   When JSON is invalid (or `disabled` is true), `handleTokenChange` silently returns at line 139, but the `ColorPickerControl` (lines 162-167) is still rendered enabled — `ColorPickerControl` has no `disabled` prop (see its props interface), so clicks look accepted but do nothing. Consider disabling/hiding the pickers when `!isValid` so the UI matches the dropped-edit behavior.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #4132c8</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx:
##########
@@ -195,15 +203,13 @@ export default function PopKPI(props: PopKPIProps) {
     let bgColor = defaultBackgroundColor;
     let txtColor = defaultTextColor;
     if (comparisonColorEnabled && percentDifferenceNumber !== 0) {
-      const useSuccess =
-        (percentDifferenceNumber > 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Green) ||
-        (percentDifferenceNumber < 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Red);
-
-      // Set background and text colors based on the conditions
-      bgColor = useSuccess ? theme.colorSuccessBg : theme.colorErrorBg;
-      txtColor = useSuccess ? theme.colorSuccessText : theme.colorErrorText;
+      const colorValue =
+        percentDifferenceNumber > 0
+          ? resolvedIncreaseColor
+          : resolvedDecreaseColor;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicated color selection</b></div>
   <div id="fix">
   
   Lines 206-209 duplicate the `colorValue` selection already computed in `getArrowIndicatorColor` (lines 188-191). If one copy is later edited (e.g. a new `percentDifferenceNumber === 0` branch), the arrow color and the pill background/text can silently diverge. Hoist a single `comparisonColorValue` local and reuse it in both places.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #4132c8</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +100,57 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
+              type: 'ColorPickerControl',
+              label: t('Color for increase'),
+              // No static default: charts saved before this control existed
+              // only have `comparison_color_scheme` ('Green' | 'Red', where
+              // 'Red' reverses increase/decrease colors). Leaving this
+              // control's value undefined lets `resolveComparisonColorKeys`
+              // (see BigNumberPeriodOverPeriod/utils.ts) resolve the correct
+              // color from that legacy scheme at render time. A hardcoded
+              // default here would win over the legacy fallback via
+              // `applyDefaultFormData` and silently repaint old dashboards.
               renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
               ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
               visibility: ({ controls }) =>
                 controls?.comparison_color_enabled?.value === true,
               description: t(
-                'Adds color to the chart symbols based on the positive or ' +
-                  'negative change from the comparison value.',
+                'Color used for the arrow and symbols when the metric ' +
+                  'increased from the comparison value. Defaults to green.',
+              ),
+            },
+          },
+          {
+            name: 'decrease_color',
+            config: {
+              type: 'ColorPickerControl',
+              label: t('Color for decrease'),
+              // See the comment on `increase_color` above: no static
+              // default, so `resolveComparisonColorKeys` can apply the
+              // legacy `comparison_color_scheme` fallback for old charts.
+              renderTrigger: true,
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
+              ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
+              visibility: ({ controls }) =>
+                controls?.comparison_color_enabled?.value === true,
+              description: t(
+                'Color used for the arrow and symbols when the metric ' +
+                  'decreased from the comparison value. Defaults to red.',
               ),
             },
           },

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicated color control config</b></div>
   <div id="fix">
   
   The new `increase_color` and `decrease_color` controls duplicate the entire ColorPickerControl config (`presets`, `resolveThemeTokens`, `outputFormat`, `renderTrigger`, `visibility` callback). If a preset or visibility rule changes, one control can silently diverge from the other. Consider extracting a shared base config object and spreading it into both, overriding only `name`/`label`/`description`.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #4132c8</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5902615977

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5945540998

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4152913223


##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Untested non-finite drop path</b></div>
   <div id="fix">
   
   These assertions cover the happy path and blank-token drop, but `tokenizeToNumericArray` in `src/Bullet/utils.ts` also silently drops non-numeric and non-finite tokens (`Number.isFinite(Number(token))`) and trims whitespace. None of that behavior is pinned here, so a regression in the `.filter(n => Number.isFinite(n))` or `.map(token => token.trim())` steps would pass this suite unnoticed.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #05c8db</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,94 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string') return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing mid-edit guard</b></div>
   <div id="fix">
   
   `BulletRangeColorsControl` (same PR) guards `handleColorChange` with `isRangesInputComplete` and disables its picker because `tokenizeToNumericArray`/`parseIntervalBounds` leniently compact a mid-edit list like "20,,60", shifting colors to wrong positions. `IntervalColorsControl` uses the identical lenient `parseIntervalBounds` but has no such guard and never disables its picker, so a color edit during a transient edit can be positionally misaligned. Add the same completeness guard + `disabled`.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #05c8db</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});
+
+test('isRangesInputComplete is true for an empty or fully numeric list', () => {
+  expect(isRangesInputComplete(undefined)).toBe(true);
+  expect(isRangesInputComplete('')).toBe(true);
+  expect(isRangesInputComplete('20')).toBe(true);
+  expect(isRangesInputComplete('20,40,60')).toBe(true);
+});
+
+test('isRangesInputComplete tolerates a single trailing blank token while typing', () => {
+  expect(isRangesInputComplete('20,40,')).toBe(true);
+});

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Untested trailing-blank edge cases</b></div>
   <div id="fix">
   
   The single-trailing-blank tolerance is tested only for `'20,40,'`. The implementation in `isRangesInputComplete` slices off exactly one trailing empty token, so inputs like `','` (all tokens blank after slicing, leaving `tokensToValidate.length === 0`) or `'20,,'` (blank token remains before the trailing one) must return false. These boundaries are untested, so a regression in the `tokensToValidate.length > 0` guard or the slice logic would go undetected.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #05c8db</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,60 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .map(part => (part === '' ? NaN : Number(part)));
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
+          schemeColors.length
+      ];
+    }
+    return schemeColors[index % schemeColors.length];

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Legacy color fallback mismatch</b></div>
   <div id="fix">
   
   In `resolveLegacyIntervalColors`, the fallback `schemeColors[index % schemeColors.length]` for bounds beyond the legacy indices disagrees with the migration test. With bounds [20,40,60] and indices '2,3' on testScheme, index 2 resolves to '#2ca02c' but the test expects '#1f77b4', so the `handleDeprecatedControls` migration test fails and legacy Gauge charts get misaligned colors.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #05c8db</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4151735288


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,132 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+
+const parseBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')

Review Comment:
   Fixed, the editor now blocks color edits while ranges has a blank token between numbers so nothing gets misaligned once it's filled back in.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,109 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  // Non-hex theme tokens (e.g. an antd token resolving to an `rgba(...)`
+  // string) can't take a hex alpha suffix without producing invalid CSS, so
+  // pass those through unchanged rather than tinting them.
+  if (!isEightDigitHex && !isSixDigitHex) {
+    return {
+      text: resolvedColor,
+      background: resolvedColor,

Review Comment:
   Fixed, it now parses the rgb/rgba components and builds a real low-alpha background instead of reusing the same color for both.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4337116805


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +128,48 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,

Review Comment:
   The picked colors are applied here even when `ranges` is empty and the band comes from the automatic fallback (`[0, measure * 1.1]`). `range_colors` keeps its value on hide (`resetOnHide: false`), so a user who sets ranges `50,100` with red/green, then clears Ranges, still gets the fallback band painted green from the hidden `range_colors[1]`, and that is saved with no visible control to reset it. Should the colors only apply while `rawRanges` actually yields explicit thresholds?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4303319924


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,55 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  // Skipped on the dashboard hydration path: the dashboard's `color_scheme`
+  // override is applied after this runs, so freezing literal colors here would
+  // pin the chart's own palette. The renderer resolves the legacy indices
+  // against the effective palette instead.
+  if (
+    resolveLegacyGaugeColors &&
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(

Review Comment:
   Opening a legacy Gauge in Explore now turns the palette-relative `interval_color_indices` into literal `interval_colors` using the chart's own `color_scheme`, even if the user never touches interval colors. The renderer prefers `interval_colors` whenever it is populated, so after an unrelated save a dashboard that overrides the palette with a different scheme stops following that override. Skipping the migration on dashboard hydration doesn't cover this resave path. Should the legacy indices be kept as-is until the user explicitly edits an interval color?



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
-              renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
-              ],
-              visibility: ({ controls }) =>
-                controls?.comparison_color_enabled?.value === true,
+              ...comparisonColorControlConfig,
+              label: t('Color for increase'),

Review Comment:
   The old `SelectControl` gave screen-reader users a name from its label, but `ColorPickerControl` only sets `aria-label` from the `ariaLabel` prop and these two pickers don't pass one (the Gauge and Bullet controls do). Assistive tech will announce the color value rather than 'Color for increase' / 'Color for decrease'. Could `ariaLabel` be added to the shared config or per control?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')

Review Comment:
   This resolver handles each index token on its own, whereas `getIntervalBoundsAndColors` in `Gauge/transformProps.ts` discards the whole indices list when any token fails to parse. With bounds `20,60`, indices `2,invalid` and a scheme `[A,B,C]`, the renderer shows `[A,B]` today, but opening the chart in Explore migrates and a save persists `[B,B]` with no color interaction. Could the migration and the renderer share one legacy resolver so a save can't reinterpret existing state?



##########
superset-frontend/plugins/plugin-chart-echarts/src/utils/colors.ts:
##########
@@ -0,0 +1,63 @@
+/**
+ * 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 type { RgbaColor } from '@superset-ui/core';
+
+const HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;

Review Comment:
   `ColorPickerControl` emits 8-digit hex (`#rrggbbaa`) whenever the user picks a non-opaque color, but this pattern only accepts 3 or 6 digits. If someone picks `#33669980` for the Big Number comparison color and then switches the viz to Waterfall or Candlestick, `normalizeRgbaColor` falls back to the default color while the control still shows the custom one. Should the 8-digit form be accepted here and its alpha parsed?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4297916779


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,28 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',

Review Comment:
   Yep, that would have broken update_chart on any Bullet saved from Explore. range_colors is now in the preserved key set so it survives updates, and it's dropped from the typed validation copy so it isn't rejected as an unknown field.



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -1196,6 +1197,12 @@ def merge_gauge_update_form_data(  # noqa: C901
             patch.pop(MCP_DASHBOARD_TIME_FILTER_SUBJECT, None)
 
     merged.update(patch)
+    if (
+        "interval_color_indices" in fields_set
+        and "interval_colors" in existing_form_data
+    ):
+        # Explicit palette indices supersede saved literal interval colors.
+        merged.pop("interval_colors", None)

Review Comment:
   Right, the same-dataset update goes through the overlay path, so the pop never ran there. I applied the same rule in the Gauge plugin's finalize_update_form_data, so explicit interval_color_indices now drop saved interval_colors on both paths. Added a test for the same-dataset case.



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1185,9 +1185,22 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
-    colors = [
-        _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
-    ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    explicit_colors = form_data.get("interval_colors")
+    if not isinstance(explicit_colors, list):
+        explicit_colors = []
+    colors = []
+    for position in range(max(1, len(interval_bounds))):
+        # Explicit per-interval colors win; blanks fall back to the legacy
+        # palette index for that band, else the palette cycled by position.
+        custom = explicit_colors[position] if position < len(explicit_colors) else None
+        if isinstance(custom, str) and custom:
+            colors.append(custom)
+        elif position < len(color_indices):

Review Comment:
   Agreed, that diverged from the renderer. Once explicit interval_colors are saved the preview now ignores the legacy indices and a blank entry uses the positional palette color, same as Gauge/transformProps. Added a test for your 50,100 / 1,4 example.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5795344759

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4180049016


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts:
##########
@@ -50,7 +50,21 @@ export type EchartsGaugeFormData = QueryFormData & {
   endAngle: number;
   showPointer: boolean;
   intervals: string;
+  /**
+   * @deprecated Legacy 1-indexed positions into `colorScheme`, e.g. "1,2,4".
+   * Superset versions before `intervalColors` existed stored interval colors
+   * this way. Still read at render time (see
+   * `transformProps.ts#getIntervalBoundsAndColors`) so charts saved with
+   * this shape keep rendering identically without a migration.
+   */
   intervalColorIndices: string;
+  /**
+   * Real hex/rgb colors for each interval band, positionally matched to the
+   * bounds parsed from `intervals`. Authored via the `IntervalColorsControl`
+   * control panel row. Takes precedence over `intervalColorIndices` when
+   * present.
+   */
+  intervalColors?: string[];

Review Comment:
   Rebinding a Gauge's dataset through MCP `update_chart` drops these custom colors: `_GAUGE_PRESENTATION_FORM_DATA_KEYS` preserves `interval_color_indices` but not `interval_colors`, and the replacement params are saved without them. Could the new field be preserved during rebinding, with a regression asserting the picked colors survive?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts:
##########
@@ -53,29 +53,52 @@ import { getColtypesMapping } from '../utils/series';
 export const getIntervalBoundsAndColors = (
   intervals: string,
   intervalColorIndices: string,
+  intervalColors: string[] | undefined,
   colorFn: CategoricalColorScale,
   min: number,
   max: number,
 ): Array<[number, string]> => {
   let intervalBoundsNonNormalized;
-  let intervalColorIndicesArray;
   try {
     intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
-    intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
   } catch (error) {
     intervalBoundsNonNormalized = [] as number[];
-    intervalColorIndicesArray = [] as number[];
   }
 
   const intervalBounds = intervalBoundsNonNormalized.map(
     bound => (bound - min) / (max - min),
   );
-  const intervalColors = intervalColorIndicesArray.map(
+
+  // New-style: `interval_colors` holds real hex/rgb colors chosen through
+  // the Gauge control panel's per-interval `IntervalColorsControl`,
+  // positionally matched to the bounds parsed from `intervals`. Prefer this
+  // whenever it's populated.
+  if (Array.isArray(intervalColors) && intervalColors.length > 0) {
+    return intervalBounds.map((val, idx) => [
+      val,

Review Comment:
   A saved Gauge now uses `interval_colors` here, but its MCP Vega-Lite preview still reads only `interval_color_indices` in `_prepare_gauge_preview`, so custom red/green bands appear in the default palette instead. Could the preview honor the same explicit-color precedence?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,17 +103,27 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const bands = sortedRanges.map(({ value, label, color }, i) => ({
     name: label,
     itemStyle: {
-      color: bandFills[Math.min(i, bandFills.length - 1)],
+      // A custom `range_colors` entry wins; otherwise fall back to the
+      // default theme-token ramp exactly as before this control existed, so
+      // Bullet charts saved without `range_colors` are unaffected.
+      color: color || bandFills[Math.min(i, bandFills.length - 1)],

Review Comment:
   Choosing a black range fill with Show labels enabled makes its label unreadable in the light theme: the label sits inside the band and still uses the dark `colorTextSecondary` foreground. Could custom fills get a contrasting label color or place the label outside the fill?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,98 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||

Review Comment:
   With `interval_colors: ['#ff0000']` and legacy indices `3,1`, the renderer uses the second palette color for the missing second entry, but this picker displays the first; editing only the first row then persists that different color into the untouched second band. Could populated new arrays use the renderer's positional fallback instead of resurrecting legacy indices?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4081201010


##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx:
##########
@@ -0,0 +1,119 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import BulletRangeColorsControl from 'src/explore/components/controls/BulletRangeColorsControl';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no ranges are configured', () => {
+  render(<BulletRangeColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add ranges above to configure their colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed range, unset by default', () => {
+  render(
+    <BulletRangeColorsControl onChange={jest.fn()} ranges="100,200,300" />,
+  );
+
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 200/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 300/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+  // no row has a custom color yet, so no "Use default" reset link shows
+  expect(screen.queryByText('Use default')).not.toBeInTheDocument();
+});
+
+test('shows a "Use default" reset link only for rows with a custom color', () => {
+  render(
+    <BulletRangeColorsControl
+      onChange={jest.fn()}
+      ranges="100,200"
+      value={['#ff0000', '']}
+    />,
+  );
+
+  expect(screen.getAllByText('Use default')).toHaveLength(1);
+});
+
+test('calls onChange with the full color array when a row color is picked', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['', '']}
+    />,
+  );
+
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+
+  await userEvent.click(triggers[0]);
+  await waitFor(() => {
+    expect(document.querySelector('.ant-color-picker')).toBeInTheDocument();
+  });
+
+  const hexInput = document.querySelector<HTMLInputElement>(
+    '.ant-color-picker-input input',
+  );
+  expect(hexInput).toBeInTheDocument();
+  await userEvent.clear(hexInput!);
+  await userEvent.type(hexInput!, '00ff00{enter}');
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalled();
+  });
+  const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
+  expect(lastCall[0].toLowerCase()).toBe('#00ff00');
+  expect(lastCall[1]).toBe('');
+});
+
+test('resets a single row back to "no custom color" without touching the others', () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['#ff0000', '#00ff00']}
+    />,
+  );
+
+  const resetButtons = screen.getAllByText('Use default');
+  expect(resetButtons).toHaveLength(2);
+  userEvent.click(resetButtons[0]);

Review Comment:
   Good catch, fixed! Awaited the click before the `waitFor` assertion so it can't race.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309437959


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')

Review Comment:
   Agreed, made the migration parse with the exact same all-or-nothing semantics the renderer'\''s legacy fallback already has instead of the more lenient per-token parse, so a bad token can'\''t make this resolve any differently than the chart has always rendered.



##########
superset-frontend/plugins/plugin-chart-echarts/src/utils/colors.ts:
##########
@@ -0,0 +1,63 @@
+/**
+ * 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 type { RgbaColor } from '@superset-ui/core';
+
+const HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;

Review Comment:
   Good catch, accepting 8-digit hex now and parsing the alpha byte out of it instead of falling back to the default.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5909442044

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4170141279


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,71 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // strict: an explicitly-named but unregistered scheme should resolve to
+  // "no colors" here, not silently fall back to the registry's default
+  // scheme -- that fallback belongs to the display layer (index.tsx), not
+  // to this migration helper.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme, true)?.colors ?? [];

Review Comment:
   For an imported Gauge whose saved palette is no longer registered, the renderer still resolves legacy indices against the default palette, but this strict lookup leaves `interval_colors` unset; Explore then drops `interval_color_indices` on save and loses those picks. Could this preserve the renderer’s default-palette fallback, or retain the legacy value until it can be migrated?



##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +105,50 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  if (
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(

Review Comment:
   Dashboard hydration runs this migration before applying the dashboard’s `color_scheme` override, so a legacy Gauge saved under palette A now keeps A’s literal interval colors on a dashboard using palette B. Could dashboard rendering retain the palette-relative indices rather than freezing them before the effective palette is known?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,71 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // strict: an explicitly-named but unregistered scheme should resolve to
+  // "no colors" here, not silently fall back to the registry's default
+  // scheme -- that fallback belongs to the display layer (index.tsx), not
+  // to this migration helper.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme, true)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .map(part => (part === '' ? NaN : Number(part)));
+  // Bounds without an explicit legacy index cycle through the scheme on
+  // their own counter, independent of their position among the bounds that
+  // *do* have one -- e.g. indices "2,3" for 3 bounds should leave the third
+  // (uncovered) bound on the first scheme color, not the third.
+  let missingCount = 0;
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
+          schemeColors.length
+      ];
+    }
+    const fallbackColor = schemeColors[missingCount % schemeColors.length];

Review Comment:
   Loading a legacy Gauge with bounds `20,40,60` and indices `2,3` changes its third band: the renderer previously used the third palette color, but this migration uses the first. Could the fallback retain the bound’s original position and the migration test assert identical colors before and after normalization?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,25 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',
+            config: {
+              type: 'BulletRangeColorsControl',
+              label: t('Range colors'),
+              default: [],
+              renderTrigger: true,
+              description: t(
+                'Optional custom color for each range band, e.g. to match a brand palette. Bands left unset use the default shading.',
+              ),

Review Comment:
   Clearing the ranges text while replacing the thresholds hides this control, and `Control`’s default `resetOnHide` behavior immediately resets its custom colors to `[]`; re-entering the ranges cannot restore them, and saving persists the loss. Could the colors survive that temporary hide, with a clear/retype regression test through the Explore control lifecycle?



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,109 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  // Non-hex theme tokens (e.g. an antd token resolving to an `rgba(...)`
+  // string) can't take a hex alpha suffix without producing invalid CSS, so
+  // pass those through unchanged rather than tinting them.
+  if (!isEightDigitHex && !isSixDigitHex) {
+    return {
+      text: resolvedColor,
+      background: resolvedColor,

Review Comment:
   The RGB/RGBA case is fixed, but a theme with `colorPrimary: "#369"` still gives the comparison pill identical foreground and background colors, making its symbols invisible; `Theme.setConfig` preserves that valid shorthand value. Could this normalize supported CSS color formats before generating the tint?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,100 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !boundsComplete) return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {bounds.length === 0 ? (
+        <BoundLabel>
+          {t('Add interval bounds above to configure colors here.')}
+        </BoundLabel>
+      ) : (
+        bounds.map((bound, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <IntervalRow key={index}>

Review Comment:
   Agreed—the split label fixes the number after the translated prefix, so a locale needing the opposite word order cannot express it. Could this use `t("Up to %s", bound)` as the Bullet label does?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4334996059


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,133 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import { supersetTheme } from '@apache-superset/core/theme';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {

Review Comment:
   Added: renders with `percentDifferenceNumber={-0.2}` and asserts the down arrow and delta pill resolve to `#994422` (background `#9944221A`).



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4366863021


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -193,10 +234,12 @@ export default function ColorPickerControl({
     const rgb = color.toRgb();
     const hex = rgbaToHex(rgb).toLowerCase();
 
+    // Match against the same hex the preset chip and trigger display, so a
+    // special color picked from a theme-resolved chip maps back to its key.
     const specialEntry = resolveThemeTokens
-      ? Object.entries(SPECIAL_COLORS).find(
-          ([, rgba]) => rgbaToHex(rgba).toLowerCase() === hex,
-        )
+      ? Object.keys(SPECIAL_COLORS)
+          .map(key => [key, toDisplayHex(key, themeColors, true)] as const)
+          .find(([, displayHex]) => displayHex === hex)

Review Comment:
   The preset restriction covers callers that offer no trend presets, but Table with time comparison passes both the ordinary `colorSuccess`/`colorError` swatches and `Green`/`Red` (`presets={[...colors, ...extraColorChoices]}`). Those resolve to the same hex, so picking the ordinary `colorSuccess` swatch still emits `Green` (the `.find` matches it first) and the operator fields disappear. Also, with a theme value such as `colorSuccess: 'rgb(82, 196, 26)'`, `toDisplayHex('Green', ...)` returns the raw rgb string while `hex` is the normalized `#52c41a`, so choosing the Green preset falls through to `colorSuccess` instead of `Green`. Could the picker tell which swatch was chosen (rather than comparing resolved colors) and normalize both sides when it does compare?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4385135816


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,89 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, one per interval band (`boundCount` entries). Shared by the
+ * control panel (display-only fallback while a legacy chart is being edited)
+ * and by `handleDeprecatedControls` (which, on request, migrates the value
+ * into `interval_colors`).
+ */
+export const resolveLegacyIntervalColors = (
+  boundCount: number,
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // An index past the static palette can only be resolved against the
+  // renderer's render-time palette (which grows under USE_ANALOGOUS_COLORS),
+  // so wrapping it modulo the static scheme would show -- and, once a row is
+  // edited, persist -- a different color than the chart renders. Leave every
+  // band unresolved so such charts keep rendering through the renderer's own
+  // legacy path.
+  if (indices.some(index => index > schemeColors.length)) {

Review Comment:
   Good catch, took your suggestion: only the first `boundCount` indices are checked now, with a test for `2,3,7` on a 3-color scheme.



##########
superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts:
##########
@@ -687,41 +764,121 @@ describe('Min/Max calculation and axis labels', () => {
 });
 
 describe('getIntervalBoundsAndColors', () => {
-  test('should generate correct interval bounds and colors', () => {
+  test('should generate correct interval bounds and colors from legacy indices', () => {
     const colorFn = CategoricalColorNamespace.getScale(
       'supersetColors' as string,
     );
-    expect(getIntervalBoundsAndColors('', '', colorFn, 0, 10)).toEqual([]);
-    expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 0, 10)).toEqual(
-      [
-        [0.4, '#1f77b4'],
-        [1, '#ff7f0e'],
-      ],
-    );
     expect(
-      getIntervalBoundsAndColors('4, 8, 10', '9, 8, 7', colorFn, 0, 10),
+      getIntervalBoundsAndColors('', '', undefined, colorFn, 0, 10),
+    ).toEqual([]);
+    expect(
+      getIntervalBoundsAndColors('4, 10', '1, 2', undefined, colorFn, 0, 10),
+    ).toEqual([
+      [0.4, '#1f77b4'],
+      [1, '#ff7f0e'],
+    ]);
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 8, 10',
+        '9, 8, 7',
+        undefined,
+        colorFn,
+        0,
+        10,
+      ),
     ).toEqual([
       [0.4, '#bcbd22'],
       [0.8, '#7f7f7f'],
       [1, '#e377c2'],
     ]);
-    expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 2, 10)).toEqual(
-      [
-        [0.25, '#1f77b4'],
-        [1, '#ff7f0e'],
-      ],
-    );
     expect(
-      getIntervalBoundsAndColors('-4, 0', '1, 2', colorFn, -10, 0),
+      getIntervalBoundsAndColors('4, 10', '1, 2', undefined, colorFn, 2, 10),
+    ).toEqual([
+      [0.25, '#1f77b4'],
+      [1, '#ff7f0e'],
+    ]);
+    expect(
+      getIntervalBoundsAndColors('-4, 0', '1, 2', undefined, colorFn, -10, 0),
     ).toEqual([
       [0.6, '#1f77b4'],
       [1, '#ff7f0e'],
     ]);
     expect(
-      getIntervalBoundsAndColors('-4, -2', '1, 2', colorFn, -10, -2),
+      getIntervalBoundsAndColors('-4, -2', '1, 2', undefined, colorFn, -10, -2),
     ).toEqual([
       [0.75, '#1f77b4'],
       [1, '#ff7f0e'],
     ]);
   });
+
+  test('should prefer interval_colors (real hex values) over legacy indices when present', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 10',
+        '1, 2',
+        ['#ff0000', '#00ff00'],
+        colorFn,
+        0,
+        10,
+      ),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [1, '#00ff00'],
+    ]);
+  });
+
+  test('should fall back to the color scheme for bounds missing an explicit interval_colors entry', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors('4, 8, 10', '', ['#ff0000'], colorFn, 0, 10),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [0.8, colorFn.colors[1]],
+      [1, colorFn.colors[2]],
+    ]);
+  });
+
+  test('wraps the interval_colors fallback around the scheme like the editor does', () => {
+    // With more bounds than colors in the scheme, a bound past the end of
+    // `intervalColors` must wrap around the scheme the same way
+    // `IntervalColorsControl`'s own display does (`index % schemeColors.length`),
+    // rather than indexing straight off the end into `undefined`.
+    const colorFn = new CategoricalColorScale([
+      '#1f77b4',
+      '#ff7f0e',
+      '#2ca02c',
+    ]);
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 6, 8, 10',
+        '',
+        ['#ff0000'],
+        colorFn,
+        0,
+        10,
+      ),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [0.6, '#ff7f0e'],
+      [0.8, '#2ca02c'],
+      [1, '#1f77b4'],
+    ]);
+  });
+
+  test('an empty interval_colors array falls back to legacy indices', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors('4, 10', '1, 2', [], colorFn, 0, 10),

Review Comment:
   Switched the test to `2, 1` and asserted `[0.4, '#ff7f0e'], [1, '#1f77b4']`, so it now pins the legacy path rather than positional order.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5958037075

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4175417295


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   With imported params containing `comparison_color_scheme: 'Red'` and only `increase_color: '#336699'`, Explore still drops the scheme and turns decreases red instead of green because this migration requires both new colors to be absent. Could each missing color be migrated independently, with a control-serialization round-trip test?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4323642216


##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2799,9 +2834,18 @@ def threshold_details(
                     "x": {"datum": axis_min, "type": "quantitative"},
                     "x2": {"datum": threshold},
                     "y": y_encoding,
-                    "color": legend_color(
-                        (f"{label}: " if label else "")
-                        + f"≤ {_format_bullet_number(model.y_axis_format, threshold)}"
+                    # A custom `range_colors` entry wins; otherwise fall back
+                    # to the default categorical legend coloring exactly as
+                    # before this control existed, so a Bullet chart saved
+                    # without `range_colors` previews unchanged.
+                    "color": (
+                        {"value": color}

Review Comment:
   With a custom color set, the band layer switches to a constant `{"value": color}` encoding, so it no longer contributes a legend entry. For `ranges="50,100"` with both `range_colors` set and `show_legend=True`, the preview legend lists only the metric while Explore still lists both ranges. Should the custom-colored bands keep a nominal legend entry, for example by mapping the label to the color through the encoding's `scale` rather than a constant value?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4333244523


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,63 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: on request, migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Defaults to off
+  // (unlike the comparison_color_scheme migration above): unlike that
+  // control, `interval_color_indices` is never dropped from `form_data` by
+  // an unrelated save -- it isn't a registered control, but `form_data` is
+  // persisted as a plain object, not rebuilt from registered controls, so
+  // an untouched legacy string survives save as-is. Eagerly resolving it
+  // merely from opening the chart (the old default here) froze it into
+  // literal colors on the *first* unrelated resave too, silently breaking a
+  // dashboard override of this chart's `color_scheme` applied afterward --
+  // the exact hazard the dashboard hydration path below was already written
+  // to avoid, just not for this path. `IntervalColorsControl` already shows
+  // the resolved colors for display on its own (see its own
+  // `legacyIntervalColorIndices` prop), so the indices stay palette-relative
+  // and keep following the effective palette until the user actually edits
+  // an interval color, which is the only point this needs to be literal.
+  if (
+    resolveLegacyGaugeColors &&

Review Comment:
   Good catch, Explore does rebuild the query/save form data from registered controls, so the indices never reached the renderer there. Went with your hidden-control idea: `interval_color_indices` stays registered on the Gauge panel, so an untouched legacy chart keeps rendering and saving with its palette-relative colors until the first color edit populates `interval_colors`.



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2799,9 +2834,18 @@ def threshold_details(
                     "x": {"datum": axis_min, "type": "quantitative"},
                     "x2": {"datum": threshold},
                     "y": y_encoding,
-                    "color": legend_color(
-                        (f"{label}: " if label else "")
-                        + f"≤ {_format_bullet_number(model.y_axis_format, threshold)}"
+                    # A custom `range_colors` entry wins; otherwise fall back
+                    # to the default categorical legend coloring exactly as
+                    # before this control existed, so a Bullet chart saved
+                    # without `range_colors` previews unchanged.
+                    "color": (
+                        {"value": color}

Review Comment:
   Good call. Switched it to ride on the same `datum` encoding through a per-layer scale (`resolve.scale.color` is already independent), so custom-colored ranges keep their legend entries. Added a test for the `show_legend` plus `range_labels` case you described.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,284 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  document.querySelectorAll('.ant-color-picker-trigger').forEach(trigger => {

Review Comment:
   Yep, that could pass without a single `expect` running. It now pins two triggers first, then clicks one and checks the picker doesn't open and `onChange` isn't called.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+  expect(screen.getByText('-20')).toBeInTheDocument();
+});
+
+// The precise color resolution (legacy `comparisonColorScheme` fallback,
+// custom hex precedence, the neutral disabled state) is covered by pure
+// unit tests against `resolveComparisonColorKeys` / `getComparisonColorTokens`
+// in utils.test.ts, since emotion's `css` prop styles aren't reliably
+// observable through jsdom's computed styles. These tests instead confirm
+// PopKPI renders without crashing across the same prop combinations.
+test('renders without crashing with only the legacy comparisonColorScheme (backward compatibility)', () => {
+  render(<PopKPI {...baseProps} comparisonColorScheme="Red" />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+});
+
+test('renders without crashing with a custom increaseColor hex', () => {

Review Comment:
   Agreed, those only proved the arrow exists. The actual snag was that jest's babel config compiles the `css` prop with React's own JSX runtime (webpack goes through swc with `@emotion/react`), so the span's style was silently dropped in tests, hence the earlier 'Property not found: color'. Made the arrow a styled span like the pill, and the tests now assert the arrow and pill colors for the legacy scheme, a custom hex, and the disabled case.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,114 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost.
+  const writeLength = hasTrailingBlankToken(intervals)

Review Comment:
   Good catch. Went with resolving for the full previous length: while `intervals` ends in the trailing blank and there are no explicit colors yet, the legacy index count now sets the floor for the written array, so the band being retyped keeps its legacy color. Added a test for exactly the `20,40,60` / `3,2,1` sequence.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4336662101


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   With a 3-color scheme, intervals `50,100,150` and legacy indices `2,10`, editing only band 3 to `#123456` saves `["#1f77b4","#ff7f0e","#123456"]`. Untouched band 1 changes from `#ff7f0e` to `#1f77b4`, even without palette expansion.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   With `comparison_color_scheme: "Red"` and only `increase_color: "#336699"`, the migration leaves `decrease_color` absent. A control-serialization round trip drops the legacy scheme and changes decreases from Green to Red.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,109 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  // Non-hex theme tokens (e.g. an antd token resolving to an `rgba(...)`
+  // string) can't take a hex alpha suffix without producing invalid CSS, so
+  // pass those through unchanged rather than tinting them.
+  if (!isEightDigitHex && !isSixDigitHex) {
+    return {
+      text: resolvedColor,
+      background: resolvedColor,

Review Comment:
   With `colorPrimary: "#369"`, `getComparisonColorTokens("colorPrimary", theme)` returns `#369` for text, background and strongText. Chromium computes identical foreground/background colors, hiding the comparison symbols.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5804705364

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5799356043

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4362313350


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,42 @@ const config: ControlPanelConfig = {
         ],
         [
           {
+            // Legacy 1-indexed positions into `color_scheme`, replaced by
+            // `interval_colors` below. Explore builds the chart's query and
+            // save form data from registered controls only, so this stays
+            // registered (hidden) to keep an untouched legacy chart
+            // rendering and saving with its original palette-relative
+            // colors. The renderer ignores it once `interval_colors` is
+            // populated by the first edit in the control below.
             name: 'interval_color_indices',
             config: {
-              type: 'TextControl',
+              type: 'HiddenControl',
+              hidden: true,
+            },
+          },
+        ],
+        [
+          {
+            name: 'interval_colors',
+            config: {
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),

Review Comment:
   @SBIN2010 agreed, reworded: the colors keep following the chosen scheme until an interval color is edited.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4141828141


##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import { tokenizeToNumericArray } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;

Review Comment:
   <!-- Bito Reply -->
   The extraction of the shared row layout and color-array-update logic into a dedicated component is a sound approach. This change effectively addresses the duplication issue by centralizing the shared UI elements and logic, while maintaining the necessary separation for the unique legacy-color resolution and reset-button functionality.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,97 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
+    ? resolvedColor.slice(0, 7)
+    : resolvedColor;
+  return {
+    text: resolvedColor,
+    background: `${opaqueColor}${COMPARISON_TINT_ALPHA_HEX}`,

Review Comment:
   <!-- Bito Reply -->
   The update correctly addresses the issue by restricting the tint suffix to 6-digit hex values, ensuring that other formats like rgba tokens are passed through unchanged. This change prevents the generation of invalid CSS for the pill background.
   
   **superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts**
   ```
   const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
       ? resolvedColor.slice(0, 7)
       : resolvedColor;
     return {
       text: resolvedColor,
       background: /^#[0-9a-f]{6}$/i.test(opaqueColor)
         ? `${opaqueColor}${COMPARISON_TINT_ALPHA_HEX}`
         : opaqueColor,
   ```



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+  expect(screen.getByText('-20')).toBeInTheDocument();
+});
+
+// The precise color resolution (legacy `comparisonColorScheme` fallback,
+// custom hex precedence, the neutral disabled state) is covered by pure
+// unit tests against `resolveComparisonColorKeys` / `getComparisonColorTokens`
+// in utils.test.ts, since emotion's `css` prop styles aren't reliably
+// observable through jsdom's computed styles. These tests instead confirm
+// PopKPI renders without crashing across the same prop combinations.

Review Comment:
   <!-- Bito Reply -->
   The update to include `toHaveStyleRule` is a positive step for testing the arrow indicator's styling. This approach correctly leverages the testing utilities available in the project to verify styles, which is more robust than relying on manual inspection or skipping style tests entirely.
   
   **superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx**
   ```
   test('renders the up arrow and metric values when the metric increased', () => {
     render(<PopKPI {...baseProps} />);
   
     expect(screen.getByText('↑')).toBeInTheDocument();
     expect(screen.getByText('120')).toBeInTheDocument();
     expect(screen.getByText('Sales')).toBeInTheDocument();
   });
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4390054605


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,91 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, one per interval band (`boundCount` entries). Shared by the
+ * control panel (display-only fallback while a legacy chart is being edited)
+ * and by `handleDeprecatedControls` (which, on request, migrates the value
+ * into `interval_colors`).
+ */
+export const resolveLegacyIntervalColors = (
+  boundCount: number,
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // An index past the static palette can only be resolved against the
+  // renderer's render-time palette (which grows under USE_ANALOGOUS_COLORS),
+  // so wrapping it modulo the static scheme would show -- and, once a row is
+  // edited, persist -- a different color than the chart renders. Leave every
+  // band unresolved so such charts keep rendering through the renderer's own
+  // legacy path.
+  if (
+    indices.slice(0, boundCount).some(index => index > schemeColors.length)
+  ) {

Review Comment:
   Applied the `oxfmt` formatting in f2001b32be.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4384348414


##########
superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts:
##########
@@ -687,41 +764,121 @@ describe('Min/Max calculation and axis labels', () => {
 });
 
 describe('getIntervalBoundsAndColors', () => {
-  test('should generate correct interval bounds and colors', () => {
+  test('should generate correct interval bounds and colors from legacy indices', () => {
     const colorFn = CategoricalColorNamespace.getScale(
       'supersetColors' as string,
     );
-    expect(getIntervalBoundsAndColors('', '', colorFn, 0, 10)).toEqual([]);
-    expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 0, 10)).toEqual(
-      [
-        [0.4, '#1f77b4'],
-        [1, '#ff7f0e'],
-      ],
-    );
     expect(
-      getIntervalBoundsAndColors('4, 8, 10', '9, 8, 7', colorFn, 0, 10),
+      getIntervalBoundsAndColors('', '', undefined, colorFn, 0, 10),
+    ).toEqual([]);
+    expect(
+      getIntervalBoundsAndColors('4, 10', '1, 2', undefined, colorFn, 0, 10),
+    ).toEqual([
+      [0.4, '#1f77b4'],
+      [1, '#ff7f0e'],
+    ]);
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 8, 10',
+        '9, 8, 7',
+        undefined,
+        colorFn,
+        0,
+        10,
+      ),
     ).toEqual([
       [0.4, '#bcbd22'],
       [0.8, '#7f7f7f'],
       [1, '#e377c2'],
     ]);
-    expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 2, 10)).toEqual(
-      [
-        [0.25, '#1f77b4'],
-        [1, '#ff7f0e'],
-      ],
-    );
     expect(
-      getIntervalBoundsAndColors('-4, 0', '1, 2', colorFn, -10, 0),
+      getIntervalBoundsAndColors('4, 10', '1, 2', undefined, colorFn, 2, 10),
+    ).toEqual([
+      [0.25, '#1f77b4'],
+      [1, '#ff7f0e'],
+    ]);
+    expect(
+      getIntervalBoundsAndColors('-4, 0', '1, 2', undefined, colorFn, -10, 0),
     ).toEqual([
       [0.6, '#1f77b4'],
       [1, '#ff7f0e'],
     ]);
     expect(
-      getIntervalBoundsAndColors('-4, -2', '1, 2', colorFn, -10, -2),
+      getIntervalBoundsAndColors('-4, -2', '1, 2', undefined, colorFn, -10, -2),
     ).toEqual([
       [0.75, '#1f77b4'],
       [1, '#ff7f0e'],
     ]);
   });
+
+  test('should prefer interval_colors (real hex values) over legacy indices when present', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 10',
+        '1, 2',
+        ['#ff0000', '#00ff00'],
+        colorFn,
+        0,
+        10,
+      ),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [1, '#00ff00'],
+    ]);
+  });
+
+  test('should fall back to the color scheme for bounds missing an explicit interval_colors entry', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors('4, 8, 10', '', ['#ff0000'], colorFn, 0, 10),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [0.8, colorFn.colors[1]],
+      [1, colorFn.colors[2]],
+    ]);
+  });
+
+  test('wraps the interval_colors fallback around the scheme like the editor does', () => {
+    // With more bounds than colors in the scheme, a bound past the end of
+    // `intervalColors` must wrap around the scheme the same way
+    // `IntervalColorsControl`'s own display does (`index % schemeColors.length`),
+    // rather than indexing straight off the end into `undefined`.
+    const colorFn = new CategoricalColorScale([
+      '#1f77b4',
+      '#ff7f0e',
+      '#2ca02c',
+    ]);
+    expect(
+      getIntervalBoundsAndColors(
+        '4, 6, 8, 10',
+        '',
+        ['#ff0000'],
+        colorFn,
+        0,
+        10,
+      ),
+    ).toEqual([
+      [0.4, '#ff0000'],
+      [0.6, '#ff7f0e'],
+      [0.8, '#2ca02c'],
+      [1, '#1f77b4'],
+    ]);
+  });
+
+  test('an empty interval_colors array falls back to legacy indices', () => {
+    const colorFn = CategoricalColorNamespace.getScale(
+      'supersetColors' as string,
+    );
+    expect(
+      getIntervalBoundsAndColors('4, 10', '1, 2', [], colorFn, 0, 10),

Review Comment:
   This test would still pass if the renderer's `intervalColors.length > 0` guard were removed. Legacy indices `'1, 2'` resolve to `colorFn.colors[0]` and `colorFn.colors[1]`, which is exactly the positional wrap-around that an empty `interval_colors` array falls into without the guard. So a saved Gauge whose indices differ from the positional order (say `'2,1'`) could regress to the default `interval_colors: []` overriding its saved colors without any test failing. Could this use non-positional indices such as `'2, 1'` and assert `[0.4, '#ff7f0e'], [1, '#1f77b4']`, so the legacy path is what actually gets pinned?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3890453349


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,159 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    return parsed && typeof parsed === 'object' ? parsed : null;

Review Comment:
   Also confirmed. `tryParseThemeJson` only checked that the top-level parse was an object, so a valid-JSON-but-malformed `token` (a string or array) sailed through, and `patchThemeJsonToken`'s spread of a non-object silently rewrote it into numeric-keyed junk on the next picker edit. Added a plain-object check on `token` in 9b6b41e28d so that case now surfaces the existing invalid-JSON notice instead.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "netlify[bot] (via GitHub)" <gi...@apache.org>.
netlify[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5465089826

   ### <span aria-hidden="true">✅</span> Deploy Preview for *superset-docs-preview* ready!
   
   
   |  Name | Link |
   |:-:|------------------------|
   |<span aria-hidden="true">🔨</span> Latest commit | 96ba402c2101ef5d245d5e8ab8ecd515cb8217b2 |
   |<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/projects/superset-docs-preview/deploys/6a9354072ce1440008535be0 |
   |<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-42910--superset-docs-preview.netlify.app](https://deploy-preview-42910--superset-docs-preview.netlify.app) |
   |<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQyOTEwLS1zdXBlcnNldC1kb2NzLXByZXZpZXcubmV0bGlmeS5hcHAifQ.NQdOv88gjijSJYF_sd8uekSWyIHQJBjlZYWEPVW7R28)<br /><br />_Use your smartphone camera to open QR code link._</details> |
   |<span aria-hidden="true">🤖</span> Make changes | [Run an agent on this branch](https://app.netlify.com/projects/superset-docs-preview/agent-runs#DcrLDYAgDADQXXrWWBX8beAYWGokIiXIxRh3l9s7vBdikitmWAAq2JIJdBTvbHJD4iXV0dHJqSYJt3hnTXYSSrUcvTyrLXkwc68Vjh1xqxQiTrrXGyN8Pw) |
   ---
   <!-- [superset-docs-preview Preview](https://deploy-preview-42910--superset-docs-preview.netlify.app) -->
   _To edit notification comments on pull requests, go to your [Netlify project configuration](https://app.netlify.com/projects/superset-docs-preview/configuration/notifications#deploy-notifications)._


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5912598835

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5987307619

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4153176292


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,60 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .map(part => (part === '' ? NaN : Number(part)));
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
+          schemeColors.length
+      ];
+    }
+    return schemeColors[index % schemeColors.length];

Review Comment:
   <!-- Bito Reply -->
   The fix correctly addresses the issue by ensuring the fallback logic only counts entries that lack a legacy index, rather than using the absolute position of the bound. This adjustment aligns the resolution logic with the migration test expectations, preventing the color misalignment in legacy Gauge charts.
   
   **superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts**
   ```
   if (Number.isFinite(legacyIndex)) {
         return schemeColors[
           (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
             schemeColors.length
         ];
       }
       return schemeColors[index % schemeColors.length];
   ```



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,94 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string') return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };

Review Comment:
   <!-- Bito Reply -->
   This is a clear and appropriate way to address the reviewer's concern. By adding the `isRangesInputComplete` guard and disabling the picker, you ensure that the color mapping remains consistent and prevents positional misalignment during transient edits.



##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});

Review Comment:
   <!-- Bito Reply -->
   The suggestion is appropriate as it improves test coverage for the `tokenizeToNumericArray` utility. By explicitly testing the handling of non-numeric tokens, non-finite values, and whitespace trimming, you ensure that the function's filtering and mapping logic remains robust against future regressions.
   
   **superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts**
   ```
   test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
     expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
     expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
     expect(tokenizeToNumericArray('')).toBeNull();
     expect(tokenizeToNumericArray(undefined)).toBeNull();
   });
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4153176049


##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});

Review Comment:
   Added coverage for the whitespace-trim and non-finite/non-numeric drop paths.



##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});
+
+test('isRangesInputComplete is true for an empty or fully numeric list', () => {
+  expect(isRangesInputComplete(undefined)).toBe(true);
+  expect(isRangesInputComplete('')).toBe(true);
+  expect(isRangesInputComplete('20')).toBe(true);
+  expect(isRangesInputComplete('20,40,60')).toBe(true);
+});
+
+test('isRangesInputComplete tolerates a single trailing blank token while typing', () => {
+  expect(isRangesInputComplete('20,40,')).toBe(true);
+});

Review Comment:
   Added tests for both, a blank token before the tolerated trailing one and an all-blank string.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4172454425


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,100 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !boundsComplete) return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {bounds.length === 0 ? (
+        <BoundLabel>
+          {t('Add interval bounds above to configure colors here.')}
+        </BoundLabel>
+      ) : (
+        bounds.map((bound, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <IntervalRow key={index}>

Review Comment:
   Switched to `t("Up to %s", bound)` in 5295540.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,71 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // strict: an explicitly-named but unregistered scheme should resolve to
+  // "no colors" here, not silently fall back to the registry's default
+  // scheme -- that fallback belongs to the display layer (index.tsx), not
+  // to this migration helper.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme, true)?.colors ?? [];
+  const indices = (legacyIntervalColorIndices ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .map(part => (part === '' ? NaN : Number(part)));
+  // Bounds without an explicit legacy index cycle through the scheme on
+  // their own counter, independent of their position among the bounds that
+  // *do* have one -- e.g. indices "2,3" for 3 bounds should leave the third
+  // (uncovered) bound on the first scheme color, not the third.
+  let missingCount = 0;
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %
+          schemeColors.length
+      ];
+    }
+    const fallbackColor = schemeColors[missingCount % schemeColors.length];

Review Comment:
   @sadpandajoe good catch. Bounds without a legacy index now keep their positional scheme color, same as the renderer, so `20,40,60` with `2,3` gives the third band the third color. Migration test updated to assert that.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,71 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // strict: an explicitly-named but unregistered scheme should resolve to
+  // "no colors" here, not silently fall back to the registry's default
+  // scheme -- that fallback belongs to the display layer (index.tsx), not
+  // to this migration helper.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme, true)?.colors ?? [];

Review Comment:
   @sadpandajoe agreed. The resolver now does a non-strict lookup, so an unregistered scheme falls back to the default palette like the renderer does. Test updated accordingly.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4172455029


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,100 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !boundsComplete) return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {bounds.length === 0 ? (
+        <BoundLabel>
+          {t('Add interval bounds above to configure colors here.')}
+        </BoundLabel>
+      ) : (
+        bounds.map((bound, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <IntervalRow key={index}>

Review Comment:
   <!-- Bito Reply -->
   The suggestion to use `t('Up to %s', bound)` is correct and should be applied. This approach improves internationalization by allowing translators to adjust the word order, which is not possible when the string is split into two separate nodes.
   
   **superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx**
   ```
   bounds.map((bound, index) => (
             // eslint-disable-next-line react/no-array-index-key
             <IntervalRow key={index}>
               <BoundLabel>{t('Up to %s', bound)}</BoundLabel>
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4387287415


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,91 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, one per interval band (`boundCount` entries). Shared by the
+ * control panel (display-only fallback while a legacy chart is being edited)
+ * and by `handleDeprecatedControls` (which, on request, migrates the value
+ * into `interval_colors`).
+ */
+export const resolveLegacyIntervalColors = (
+  boundCount: number,
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // An index past the static palette can only be resolved against the
+  // renderer's render-time palette (which grows under USE_ANALOGOUS_COLORS),
+  // so wrapping it modulo the static scheme would show -- and, once a row is
+  // edited, persist -- a different color than the chart renders. Leave every
+  // band unresolved so such charts keep rendering through the renderer's own
+  // legacy path.
+  if (
+    indices.slice(0, boundCount).some(index => index > schemeColors.length)
+  ) {

Review Comment:
   `oxfmt` keeps this condition on one line (it fits in 80 columns), so running the repo's formatter rewrites these three lines.
   
   ```suggestion
     if (indices.slice(0, boundCount).some(index => index > schemeColors.length)) {
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4373200437


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,350 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  // Pin the row count first so the per-row assertions can't pass vacuously.
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+  triggers.forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+
+  // A change attempt on a disabled picker must neither open it nor commit.
+  await userEvent.click(triggers[0]);
+  expect(
+    document.querySelector('.ant-color-picker-presets-color'),
+  ).not.toBeInTheDocument();
+  expect(onChange).not.toHaveBeenCalled();
+});
+
+test('explicit value takes precedence over legacy indices', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      value={['#ff0000', '#00ff00']}
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#FF0000')).toBeInTheDocument();
+  expect(screen.getByText('#00FF00')).toBeInTheDocument();
+});
+
+test('a non-positive legacy index falls back to the positional scheme color like the renderer', () => {
+  // The renderer computes `colors[(ind - 1) % n]`, which is `undefined` for
+  // index 0, so that band uses `colors[idx]`; it must not wrap to the last
+  // scheme color.
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="0,2"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.queryByText('#2CA02C')).not.toBeInTheDocument();
+});
+
+test('stays read-only when legacy indices cannot be resolved so untouched bands are never rewritten', async () => {

Review Comment:
   The `!hasExplicitColors` clause in `legacyUnresolved` is what keeps the editor writable once explicit colors exist, but nothing pins it: this test uses no `value`, and the explicit-color tests all use in-palette indices. If that clause were dropped, a chart with `interval_colors: ['#ff0000']` and legacy indices `10,3` would turn read-only and no test would fail. Could you add a case with `intervals="20,60"`, `value={['#ff0000']}`, `legacyIntervalColorIndices="10,3"`, then pick a preset on row 0 and assert `onChange` is called?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4402649226


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -3822,6 +3829,9 @@ def merge_bullet_form_data(
         "marker_labels",
         "marker_lines",
         "marker_line_labels",
+        # Explore-only per-band colors; the typed config does not model them,
+        # so keep them as opaque saved state across MCP updates.
+        "range_colors",

Review Comment:
   Preserving `range_colors` here without tying it to `ranges` means a ranges-only `update_chart` keeps the old colors, and they get re-applied by position to the new thresholds. `merge_bullet_form_data` already clears `range_labels` for exactly this reason (`dependent_controls`, a few lines down), but `range_colors` isn't in that map.
   
   Example: a chart saved with `ranges="50,100"` and `range_colors=["#ff0000","#00ff00"]`, then updated with `ranges="100,200,300"`: the labels are reset, but the 100 band renders red and the 200 band green in both Explore and the MCP preview, and that gets saved.
   
   Should `ranges` replaced without `range_colors` also reset `range_colors` to `[]`?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4401991142


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -193,10 +234,21 @@ export default function ColorPickerControl({
     const rgb = color.toRgb();
     const hex = rgbaToHex(rgb).toLowerCase();
 
+    // Match against the same hex the preset chip and trigger display, so a
+    // special color picked from a theme-resolved chip maps back to its key.
+    // Only keys the caller actually offered as presets are candidates;
+    // otherwise an ordinary theme swatch that shares a hex with Green/Red
+    // (e.g. `colorSuccess`) would be misread as the special key.
+    const offeredSpecialKeys = new Set(
+      (customPresets ?? []).flatMap(item =>
+        item.colors.filter(color => color in SPECIAL_COLORS),
+      ),
+    );
     const specialEntry = resolveThemeTokens
-      ? Object.entries(SPECIAL_COLORS).find(
-          ([, rgba]) => rgbaToHex(rgba).toLowerCase() === hex,
-        )
+      ? Object.keys(SPECIAL_COLORS)
+          .filter(key => offeredSpecialKeys.has(key))
+          .map(key => [key, toDisplayHex(key, themeColors, true)] as const)

Review Comment:
   Good catch. The special-key check now normalizes both sides to hex like `reverseMap` does, with a test using an `rgb(...)` `colorSuccess`.



##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,60 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: on request, migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Defaults to off,
+  // unlike the comparison_color_scheme migration above: the Gauge control
+  // panel keeps `interval_color_indices` registered as a hidden control, so
+  // Explore (which builds the chart's query and save form data from
+  // registered controls) still hands an untouched legacy string to the
+  // renderer and keeps it on save, where it stays palette-relative. Eagerly
+  // freezing it into literal colors merely from opening the chart would
+  // silently break a dashboard override of this chart's `color_scheme`
+  // applied afterward -- the exact hazard the dashboard hydration path
+  // below was already written to avoid. `IntervalColorsControl` shows the
+  // resolved colors for display on its own (see its
+  // `legacyIntervalColorIndices` prop), and only the user's first edit of
+  // an interval color makes them literal.
+  if (
+    resolveLegacyGaugeColors &&

Review Comment:
   No caller opts in, so I dropped the option, the branch and its tests. Legacy indices are still resolved for display by `IntervalColorsControl`.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4088539582


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,106 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+});

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Unused test prop unasserted</b></div>
   <div id="fix">
   
   The down-arrow test passes `valueDifference="-20"` but never asserts it renders, so a regression dropping the delta value would still pass. `PopKPI` renders each `symbol_with_value.value` (PopKPI.tsx `SYMBOLS_WITH_VALUES`); add `expect(screen.getByText('-20')).toBeInTheDocument()` beside the arrow assertion. The value fixtures in `baseProps` ('100', '20%') are likewise never asserted.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #1279fd</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,114 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;
+
+const parseRanges = (ranges?: string): number[] =>
+  (ranges ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(range => Number.isFinite(range));

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicated range parsing</b></div>
   <div id="fix">
   
   `parseRanges` re-implements `tokenizeToNumericArray` in `Bullet/utils.ts` (same split/trim/Number/isFinite pipeline). That util's comment explicitly requires the two to stay in sync because colors are matched positionally against this tokenized list — a divergence would silently misalign `range_colors` to thresholds. Consider sharing one tokenizer.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #1279fd</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4076772053


##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx:
##########
@@ -0,0 +1,119 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import BulletRangeColorsControl from 'src/explore/components/controls/BulletRangeColorsControl';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no ranges are configured', () => {
+  render(<BulletRangeColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add ranges above to configure their colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed range, unset by default', () => {
+  render(
+    <BulletRangeColorsControl onChange={jest.fn()} ranges="100,200,300" />,
+  );
+
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 200/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 300/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+  // no row has a custom color yet, so no "Use default" reset link shows
+  expect(screen.queryByText('Use default')).not.toBeInTheDocument();
+});
+
+test('shows a "Use default" reset link only for rows with a custom color', () => {
+  render(
+    <BulletRangeColorsControl
+      onChange={jest.fn()}
+      ranges="100,200"
+      value={['#ff0000', '']}
+    />,
+  );
+
+  expect(screen.getAllByText('Use default')).toHaveLength(1);
+});
+
+test('calls onChange with the full color array when a row color is picked', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['', '']}
+    />,
+  );
+
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+
+  await userEvent.click(triggers[0]);
+  await waitFor(() => {
+    expect(document.querySelector('.ant-color-picker')).toBeInTheDocument();
+  });
+
+  const hexInput = document.querySelector<HTMLInputElement>(
+    '.ant-color-picker-input input',
+  );
+  expect(hexInput).toBeInTheDocument();
+  await userEvent.clear(hexInput!);
+  await userEvent.type(hexInput!, '00ff00{enter}');
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalled();
+  });
+  const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
+  expect(lastCall[0].toLowerCase()).toBe('#00ff00');
+  expect(lastCall[1]).toBe('');
+});
+
+test('resets a single row back to "no custom color" without touching the others', () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['#ff0000', '#00ff00']}
+    />,
+  );
+
+  const resetButtons = screen.getAllByText('Use default');
+  expect(resetButtons).toHaveLength(2);
+  userEvent.click(resetButtons[0]);

Review Comment:
   This click can still be in flight when `waitFor` starts, because `userEvent.click` is async; that makes the assertion order-dependent and can leave an `act` warning in the focused suite. Could this test await the click before asserting `onChange`?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5866833832

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5766623331

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4173309898


##########
superset/translations/messages.pot:
##########
@@ -15385,6 +15450,12 @@ msgstr ""
 msgid "Unverified"
 msgstr ""
 
+msgid "Up to"

Review Comment:
   Dropped the orphaned `Up to` entry and added the `python-format` flag to `Up to %s`; the drift check passes locally against a fresh extraction.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5930079371

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4151337845


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   Opening a saved chart with `comparison_color_scheme: 'Red'` in Explore now reverses its increase/decrease colors: `getFormDataFromControls` only serializes registered controls, so removing the old control drops the scheme before `resolveComparisonColorKeys` can use it, and saving persists that reset. Could we retain the legacy field or migrate its value into the new controls during initialization?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,132 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+
+const parseBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')

Review Comment:
   Temporarily changing bounds from `20,40,60` to `20,,60` shifts the editor's rows even though the renderer rejects that list; changing the displayed 60 row then writes a two-color array, drops the third color, and attaches the edit to 40 when that bound is restored. Could the editor share the renderer's parsing/validity rule and avoid persisting color edits while the bounds are invalid?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,26 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),
               renderTrigger: true,
-              default: DEFAULT_FORM_DATA.intervalColorIndices,
+              default: DEFAULT_FORM_DATA.intervalColors,
+              shouldMapStateToProps: () => true,
+              mapStateToProps: (state: ControlPanelState) => ({
+                intervals: state?.controls?.intervals?.value as string,
+                // `interval_color_indices` is no longer a registered control
+                // (replaced by this one), so it never appears under
+                // `state.controls`. Read it from the raw persisted

Review Comment:
   This preserves the legacy colors in the picker display, but not in Explore's render/save data: a chart saved with `interval_color_indices: '2,3'` still has `interval_colors: []`, and `getFormDataFromControls` drops the unregistered legacy field, so its preview uses scheme colors 1 and 2 and saving can discard the original picks. Could we preserve the old control or initialize the new control's value from those indices before serialization?



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,109 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  // Non-hex theme tokens (e.g. an antd token resolving to an `rgba(...)`
+  // string) can't take a hex alpha suffix without producing invalid CSS, so
+  // pass those through unchanged rather than tinting them.
+  if (!isEightDigitHex && !isSixDigitHex) {
+    return {
+      text: resolvedColor,
+      background: resolvedColor,

Review Comment:
   For a theme token resolving to `rgb(...)` or `rgba(...)`, this assigns the same color to the comparison symbol's foreground and background, making the symbol invisible or very low-contrast instead of giving it a light tint. Could the background tint be generated independently of the input color format?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4250033076


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/types.ts:
##########
@@ -50,7 +50,21 @@ export type EchartsGaugeFormData = QueryFormData & {
   endAngle: number;
   showPointer: boolean;
   intervals: string;
+  /**
+   * @deprecated Legacy 1-indexed positions into `colorScheme`, e.g. "1,2,4".
+   * Superset versions before `intervalColors` existed stored interval colors
+   * this way. Still read at render time (see
+   * `transformProps.ts#getIntervalBoundsAndColors`) so charts saved with
+   * this shape keep rendering identically without a migration.
+   */
   intervalColorIndices: string;
+  /**
+   * Real hex/rgb colors for each interval band, positionally matched to the
+   * bounds parsed from `intervals`. Authored via the `IntervalColorsControl`
+   * control panel row. Takes precedence over `intervalColorIndices` when
+   * present.
+   */
+  intervalColors?: string[];

Review Comment:
   Good catch, @sadpandajoe. `interval_colors` is now in the preserved presentation keys on rebind, and an explicit `interval_color_indices` update drops the saved literal colors so they do not shadow it. Added regression tests for both.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts:
##########
@@ -53,29 +53,52 @@ import { getColtypesMapping } from '../utils/series';
 export const getIntervalBoundsAndColors = (
   intervals: string,
   intervalColorIndices: string,
+  intervalColors: string[] | undefined,
   colorFn: CategoricalColorScale,
   min: number,
   max: number,
 ): Array<[number, string]> => {
   let intervalBoundsNonNormalized;
-  let intervalColorIndicesArray;
   try {
     intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
-    intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
   } catch (error) {
     intervalBoundsNonNormalized = [] as number[];
-    intervalColorIndicesArray = [] as number[];
   }
 
   const intervalBounds = intervalBoundsNonNormalized.map(
     bound => (bound - min) / (max - min),
   );
-  const intervalColors = intervalColorIndicesArray.map(
+
+  // New-style: `interval_colors` holds real hex/rgb colors chosen through
+  // the Gauge control panel's per-interval `IntervalColorsControl`,
+  // positionally matched to the bounds parsed from `intervals`. Prefer this
+  // whenever it's populated.
+  if (Array.isArray(intervalColors) && intervalColors.length > 0) {
+    return intervalBounds.map((val, idx) => [
+      val,

Review Comment:
   Fixed, @sadpandajoe. `_prepare_gauge_preview` now lets explicit `interval_colors` win, with blank entries falling back to the palette color. Covered by a new test.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4301743887


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/utils.ts:
##########
@@ -21,17 +21,98 @@
 export function tokenizeToNumericArray(value?: string): number[] | null {
   if (!value?.trim()) return null;
   // Lenient by design: this runs on every keystroke, so partial input like
-  // "50," must not throw. Empty and non-numeric tokens are dropped.
+  // "50," must not throw. Empty, non-numeric, and non-finite tokens (e.g.
+  // "Infinity", or a value large enough to overflow to it) are dropped.
+  // `BulletRangeColorsControl` shares this exact function (not a
+  // reimplementation) since its per-range colors are matched positionally
+  // against this same tokenized list -- a divergent tokenizer would
+  // silently misalign colors to the wrong bands.
   const numbers = value
     .split(',')
     .map(token => token.trim())
     .filter(token => token !== '')
     .map(token => Number(token))
-    .filter(n => !Number.isNaN(n));
+    .filter(n => Number.isFinite(n));
   return numbers.length ? numbers : null;
 }
 
 export function tokenizeToStringArray(value?: string): string[] | null {
   if (!value?.trim()) return null;
   return value.split(',').map(token => token.trim());
 }
+
+/**
+ * Whether every comma-separated token in `value` is itself a complete,
+ * valid number -- i.e. the string isn't in a transient, mid-edit state such
+ * as "20,,60" (a blank token between two valid ones). A single blank
+ * trailing token (e.g. "20,40,") is tolerated, since that's the normal shape
+ * of the string while the user is still typing the next value.
+ *
+ * `tokenizeToNumericArray` is deliberately lenient (it silently drops blank
+ * tokens so partial input never throws), which is right for parsing the
+ * list once it's complete but wrong for anything that positionally matches
+ * against it, like `BulletRangeColorsControl`'s per-range colors -- a blank
+ * in the middle must not be treated as "one fewer range" there, since a
+ * color edit made while the list is in that transient state would
+ * permanently misalign once the blank is filled back in.
+ */
+export function isRangesInputComplete(value?: string): boolean {
+  if (!value?.trim()) return true;
+  const tokens = value.split(',').map(token => token.trim());
+  const tokensToValidate =
+    tokens[tokens.length - 1] === '' ? tokens.slice(0, -1) : tokens;

Review Comment:
   A trailing blank is still treated as complete here, so a color edit made while the ranges text is temporarily `20,40,` passes the guard. With ranges `20,40,60` and three custom colors, erase the `60` and change the first color: `rangeValues` has two entries, so `replaceColorAtIndex` writes a two-element array and the third color is gone for good. Re-entering `60` falls back to the default band. `IntervalColorsControl` has the same shape. Could the editors keep the trailing entries (or block edits) while the last token is blank?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,28 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',

Review Comment:
   Saving a Bullet with `ranges: "50,100"` and `range_colors: ["#ff0000", "#00ff00"]` renders red and green bands in Explore, but the MCP Vega-Lite preview (`_generate_bullet_vega_lite_preview` in `superset/mcp_service/chart/preview_utils.py`) still colors bands through `legend_color()` with the default categorical scale and a fixed opacity, and never reads `range_colors`. The preview then shows different colors than the saved chart. The Gauge preview was updated for its new colors in this PR, but Bullet's wasn't. Should the Bullet preview honor `range_colors` the same way?



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
-              renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
-              ],
-              visibility: ({ controls }) =>
-                controls?.comparison_color_enabled?.value === true,
+              ...comparisonColorControlConfig,
+              label: t('Color for increase'),

Review Comment:
   These two color pickers get no `ariaLabel`, and the picker doesn't derive one from `label`, so a screen-reader user hears two identically named color buttons with no way to tell increase from decrease. The `SelectControl` they replace was labelled by its header. Could each config pass an `ariaLabel` (e.g. the same text as its `label`)?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts:
##########
@@ -53,29 +53,52 @@ import { getColtypesMapping } from '../utils/series';
 export const getIntervalBoundsAndColors = (
   intervals: string,
   intervalColorIndices: string,
+  intervalColors: string[] | undefined,
   colorFn: CategoricalColorScale,
   min: number,
   max: number,
 ): Array<[number, string]> => {
   let intervalBoundsNonNormalized;
-  let intervalColorIndicesArray;
   try {
     intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
-    intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
   } catch (error) {
     intervalBoundsNonNormalized = [] as number[];
-    intervalColorIndicesArray = [] as number[];
   }
 
   const intervalBounds = intervalBoundsNonNormalized.map(
     bound => (bound - min) / (max - min),
   );
-  const intervalColors = intervalColorIndicesArray.map(
+
+  // New-style: `interval_colors` holds real hex/rgb colors chosen through
+  // the Gauge control panel's per-interval `IntervalColorsControl`,
+  // positionally matched to the bounds parsed from `intervals`. Prefer this
+  // whenever it's populated.
+  if (Array.isArray(intervalColors) && intervalColors.length > 0) {
+    return intervalBounds.map((val, idx) => [
+      val,
+      intervalColors[idx] || colorFn.colors[idx],

Review Comment:
   With one saved custom color and nine or more bounds, the tenth band here resolves to `intervalColors[9] || colorFn.colors[9]`, which is `undefined` for a nine-color scheme, while `IntervalColorsControl` displays the wrapped scheme color (`index % schemeColors.length`). Example: `interval_colors: ["#ff0000"]`, bounds `10,20,...,100`, `echarts5Colors`: the editor shows `#5470C6` for the tenth row, and editing any other row persists that color into the untouched band, which the renderer had drawn without one. Should the renderer wrap with the same modulo so the two agree?



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -35,6 +36,30 @@ import {
   metricNameFontSizeWithVisibility,
 } from '../sharedControls';
 
+// Shared base for the `increase_color` / `decrease_color` pickers so the
+// presets, token resolution, output format and visibility rule can't drift
+// apart. No static default: charts saved before these controls existed only
+// have `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+// increase/decrease colors). Leaving each control's value undefined lets
+// `resolveComparisonColorKeys` (see BigNumberPeriodOverPeriod/utils.ts)
+// resolve the correct color from that legacy scheme at render time. A
+// hardcoded default here would win over the legacy fallback via
+// `applyDefaultFormData` and silently repaint old dashboards.
+const comparisonColorControlConfig: CustomControlItem['config'] = {
+  type: 'ColorPickerControl',
+  renderTrigger: true,
+  presets: [
+    {
+      label: t('Semantic colors'),
+      colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],

Review Comment:
   Picking the Green preset stores `"Green"`, which `PopKPI` renders with `theme.colorSuccess`, but `ColorPickerControl` displays it from `SPECIAL_COLORS` as `#00960033`. With `colorSuccess` set to `#52c41a`, the swatch shows a dark translucent green, and adjusting only the opacity starts from that color and writes a literal hex that changes both hue and the semantic theme link. Should the preset display resolve through the same theme token the chart uses, or should these presets resolve to real colors when selected?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4303319937


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
-              renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
-              ],
-              visibility: ({ controls }) =>
-                controls?.comparison_color_enabled?.value === true,
+              ...comparisonColorControlConfig,
+              label: t('Color for increase'),

Review Comment:
   The old `SelectControl` gave screen-reader users a name from its label, but `ColorPickerControl` only sets `aria-label` from the `ariaLabel` prop and these two pickers don't pass one (the Gauge and Bullet controls do). Assistive tech will announce the color value rather than 'Color for increase' / 'Color for decrease'. Could `ariaLabel` be added to the shared config or per control?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309433600


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.test.tsx:
##########
@@ -238,3 +238,10 @@ test('handles undefined theme gracefully', () => {
 
   expect(() => render(<ColorPickerControl {...defaultProps} />)).not.toThrow();
 });
+
+test('trigger is exposed as a focusable button for keyboard users', () => {
+  const { container } = render(<ColorPickerControl {...defaultProps} />);
+  const trigger = container.querySelector('.ant-color-picker-trigger');
+  expect(trigger).toHaveAttribute('tabindex', '0');

Review Comment:
   Fair, that test only checked the static attributes and would stay green with the handler gone. Added tests that actually fire Enter and Space on the focused trigger and assert the popup opens, plus one checking a disabled trigger stays closed on both. Confirmed they fail if I rip the keydown handler out.



##########
superset-frontend/plugins/plugin-chart-echarts/test/utils/colors.test.ts:
##########
@@ -0,0 +1,48 @@
+/**
+ * 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 { normalizeRgbaColor } from '../../src/utils/colors';
+
+const fallback = { r: 1, g: 2, b: 3, a: 1 };
+
+test('passes through an rgb object', () => {
+  expect(
+    normalizeRgbaColor({ r: 10, g: 20, b: 30, a: 0.5 }, fallback),
+  ).toEqual({ r: 10, g: 20, b: 30, a: 0.5 });
+});
+
+test('parses 6 and 3 digit hex strings', () => {

Review Comment:
   Agreed, added tests that run a carried-over hex color through the actual Waterfall and Candlestick transforms and check the generated series color, not just normalizeRgbaColor in isolation.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4366676709


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -193,10 +234,12 @@ export default function ColorPickerControl({
     const rgb = color.toRgb();
     const hex = rgbaToHex(rgb).toLowerCase();
 
+    // Match against the same hex the preset chip and trigger display, so a
+    // special color picked from a theme-resolved chip maps back to its key.
     const specialEntry = resolveThemeTokens
-      ? Object.entries(SPECIAL_COLORS).find(
-          ([, rgba]) => rgbaToHex(rgba).toLowerCase() === hex,
-        )
+      ? Object.keys(SPECIAL_COLORS)
+          .map(key => [key, toDisplayHex(key, themeColors, true)] as const)
+          .find(([, displayHex]) => displayHex === hex)

Review Comment:
   Good catch @sadpandajoe. The Green/Red match now only considers keys actually present in the supplied presets, so the plain `colorSuccess` swatch falls through to the theme-token lookup. Added a component test pinning `onChange('colorSuccess')` with no trend presets.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3890370993


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,17 +103,27 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))

Review Comment:
   **Suggestion:** The color indexes follow transformed ranges, but the control drops non-finite values while this parser keeps Infinity, shifting later colors onto the wrong bands. [logic error]
   
   **Assessment:** 🟠 `Major` · 🔁 `Occurrence: Rarely`
   
   [![Use CodeAnt Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill) [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=6ad006ed51f04948bbf50747f14cbba8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=6ad006ed51f04948bbf50747f14cbba8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts
   **Line:** 109:114
   **Comment:**
   	*Logic Error: The color indexes follow transformed ranges, but the control drops non-finite values while this parser keeps Infinity, shifting later colors onto the wrong bands.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42910&comment_hash=59891704be2beffd861d7010af4d11f0ded5cb0107849e8700cc4b20977cb06a&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42910&comment_hash=59891704be2beffd861d7010af4d11f0ded5cb0107849e8700cc4b20977cb06a&reaction=dislike'>👎</a>



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3887611234


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,22 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),
               renderTrigger: true,
-              default: DEFAULT_FORM_DATA.intervalColorIndices,
+              default: DEFAULT_FORM_DATA.intervalColors,
+              shouldMapStateToProps: () => true,
+              mapStateToProps: (state: ControlPanelState) => ({
+                intervals: state?.controls?.intervals?.value as string,
+                legacyIntervalColorIndices: state?.controls
+                  ?.interval_color_indices?.value as string,

Review Comment:
   `interval_color_indices` is no longer part of this control-panel config, so `getAllControlsState` never creates `state.controls.interval_color_indices`. This lookup is therefore always undefined for a legacy chart. Explore also builds its render/save form data from the configured controls only, so opening a chart saved with non-sequential legacy indices (for example `2,3`) immediately previews and can save sequential scheme colors instead. Preserve the legacy field as a hidden control or migrate its value into `interval_colors` during control initialization.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,22 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',

Review Comment:
   `IntervalColorsControl` is registered in the runtime control map, but it is not included in `InternalControlType` (`packages/superset-ui-chart-controls/src/types.ts:162-196`). Consequently this string is not assignable to `ControlPanelConfig`'s `ControlType`, so the frontend type check will fail. Add the new control name to that union as part of this change.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,75 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is a literal hex, in which case the
+ * background is a light (~10% opacity) tint of that same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  return {
+    text: colorValue,
+    background: `${colorValue}1A`,
+    strongText: colorValue,

Review Comment:
   The non-semantic branch cannot assume `colorValue` is a six-digit hex value. With `resolveThemeTokens`, `ColorPickerControl` emits any matching theme token name (for example `colorPrimary`), and its alpha-enabled picker can emit eight-digit hex values. Those inputs produce invalid CSS such as `colorPrimary1A` or `#336699801A`, and token names are also invalid as the `text` color. Resolve arbitrary token names through `theme` first, then generate the 10% tint by replacing/normalizing alpha rather than appending it.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,25 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',
+            config: {
+              type: 'BulletRangeColorsControl',

Review Comment:
   `BulletRangeColorsControl` is added to the runtime map but not to the `InternalControlType` union in `packages/superset-ui-chart-controls/src/types.ts:162-196`. This literal therefore does not satisfy the typed `ControlPanelConfig` and breaks the frontend type check. Please add the new control name to that union.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +100,48 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
+              type: 'ColorPickerControl',
+              label: t('Color for increase'),
               default: ColorSchemeEnum.Green,

Review Comment:
   This default defeats the legacy fallback at runtime. Dashboard hydration calls `applyDefaultFormData`, which fills every missing configured control with its default; an old chart containing only `comparison_color_scheme: 'Red'` therefore receives `increase_color: 'Green'` (and the sibling decrease default) before `resolveComparisonColorKeys` runs. Because the new values are then present, the resolver never applies the reversed legacy scheme, so existing dashboards change colors. The new controls need to remain absent or be initialized from the legacy scheme rather than unconditionally defaulted.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3890452661


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,17 +103,27 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))

Review Comment:
   Confirmed, good catch. `tokenizeToNumericArray` kept `Infinity` (only dropped `NaN`) while `BulletRangeColorsControl`'s own `parseRanges` already drops non-finite values, so an overflowing range value desynced the two positionally-matched lists and shifted `range_colors` onto the wrong band. Filtered on `Number.isFinite` in both places in 9b6b41e28d, plus a regression test.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-6127917670

   ## CodeAnt PR Risk: Medium Risk
   
   - The PR needs attention before merging because a legacy color choice can be lost when only one replacement color is already set.
   - In store.ts, migration skips both legacy colors if either new field exists; saving a legacy “Red” chart can then reset the missing decrease color to red instead of green.
   
   **Assessed commit:** `eefb8a14d136`


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5773652681

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4068747476


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx:
##########
@@ -195,15 +203,13 @@ export default function PopKPI(props: PopKPIProps) {
     let bgColor = defaultBackgroundColor;
     let txtColor = defaultTextColor;
     if (comparisonColorEnabled && percentDifferenceNumber !== 0) {
-      const useSuccess =
-        (percentDifferenceNumber > 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Green) ||
-        (percentDifferenceNumber < 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Red);
-
-      // Set background and text colors based on the conditions
-      bgColor = useSuccess ? theme.colorSuccessBg : theme.colorErrorBg;
-      txtColor = useSuccess ? theme.colorSuccessText : theme.colorErrorText;
+      const colorValue =
+        percentDifferenceNumber > 0
+          ? resolvedIncreaseColor
+          : resolvedDecreaseColor;

Review Comment:
   Good catch, hoisted a single `comparisonColorValue` that both the arrow and the pill read from now.



##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,174 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;

Review Comment:
   Fair point. Added a `disabled` prop to `ColorPickerControl` (straight pass-through to antd), and the theme pickers disable whenever the JSON is invalid or the editor is read-only, with tests for both.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +100,57 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
+              type: 'ColorPickerControl',
+              label: t('Color for increase'),
+              // No static default: charts saved before this control existed
+              // only have `comparison_color_scheme` ('Green' | 'Red', where
+              // 'Red' reverses increase/decrease colors). Leaving this
+              // control's value undefined lets `resolveComparisonColorKeys`
+              // (see BigNumberPeriodOverPeriod/utils.ts) resolve the correct
+              // color from that legacy scheme at render time. A hardcoded
+              // default here would win over the legacy fallback via
+              // `applyDefaultFormData` and silently repaint old dashboards.
               renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
               ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
               visibility: ({ controls }) =>
                 controls?.comparison_color_enabled?.value === true,
               description: t(
-                'Adds color to the chart symbols based on the positive or ' +
-                  'negative change from the comparison value.',
+                'Color used for the arrow and symbols when the metric ' +
+                  'increased from the comparison value. Defaults to green.',
+              ),
+            },
+          },
+          {
+            name: 'decrease_color',
+            config: {
+              type: 'ColorPickerControl',
+              label: t('Color for decrease'),
+              // See the comment on `increase_color` above: no static
+              // default, so `resolveComparisonColorKeys` can apply the
+              // legacy `comparison_color_scheme` fallback for old charts.
+              renderTrigger: true,
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
+              ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
+              visibility: ({ controls }) =>
+                controls?.comparison_color_enabled?.value === true,
+              description: t(
+                'Color used for the arrow and symbols when the metric ' +
+                  'decreased from the comparison value. Defaults to red.',
               ),
             },
           },

Review Comment:
   Done, pulled the shared bits into a `comparisonColorControlConfig` that both controls spread, so only `label` and `description` differ now.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4086987043


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,91 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
+    ? resolvedColor.slice(0, 7)
+    : resolvedColor;
+  return {
+    text: resolvedColor,
+    background: `${opaqueColor}1A`,

Review Comment:
   Good catch, pulled that into a `COMPARISON_TINT_ALPHA_HEX` constant with a comment on what the alpha works out to.



##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);

Review Comment:
   Spreading `parsed` first keeps `token` in its original slot when the key already exists (checked in node: `{components, token}` stays `["components", "token"]`). It only lands last when the JSON had no `token` yet, which seems fine, so leaving this as-is.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4119026002


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,106 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+});

Review Comment:
   Good catch, fixed. Added the assertion on the -20 value text next to the arrow.



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,114 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;
+
+const parseRanges = (ranges?: string): number[] =>
+  (ranges ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(range => Number.isFinite(range));

Review Comment:
   Agreed, this one's the exact class of bug we already found once in this PR (the Infinity-vs-NaN mismatch). Dropped parseRanges and imported tokenizeToNumericArray from Bullet/utils.ts directly (newly exported from the plugin's public barrel) so there's one tokenizer, not two to keep in sync.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4153178848


##########
superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts:
##########
@@ -0,0 +1,49 @@
+/**
+ * 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 {
+  isRangesInputComplete,
+  tokenizeToNumericArray,
+} from '../../src/Bullet/utils';
+
+test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
+  expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
+  expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
+  expect(tokenizeToNumericArray('')).toBeNull();
+  expect(tokenizeToNumericArray(undefined)).toBeNull();
+});
+
+test('isRangesInputComplete is true for an empty or fully numeric list', () => {
+  expect(isRangesInputComplete(undefined)).toBe(true);
+  expect(isRangesInputComplete('')).toBe(true);
+  expect(isRangesInputComplete('20')).toBe(true);
+  expect(isRangesInputComplete('20,40,60')).toBe(true);
+});
+
+test('isRangesInputComplete tolerates a single trailing blank token while typing', () => {
+  expect(isRangesInputComplete('20,40,')).toBe(true);
+});

Review Comment:
   <!-- Bito Reply -->
   The added tests correctly address the concerns raised by the reviewer. By testing an empty string, an undefined input, and an input with an embedded blank token (e.g., '20,,60'), you have verified the behavior of the tokenization and completion logic at the boundaries identified in the review.
   
   **superset-frontend/plugins/plugin-chart-echarts/test/Bullet/utils.test.ts**
   ```
   test('tokenizeToNumericArray drops blank tokens, including an embedded one', () => {
     expect(tokenizeToNumericArray('20,40,60')).toEqual([20, 40, 60]);
     expect(tokenizeToNumericArray('20,,60')).toEqual([20, 60]);
     expect(tokenizeToNumericArray('')).toBeNull();
     expect(tokenizeToNumericArray(undefined)).toBeNull();
   });
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4179835973


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,25 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',
+            config: {
+              type: 'BulletRangeColorsControl',
+              label: t('Range colors'),
+              default: [],
+              renderTrigger: true,
+              description: t(
+                'Optional custom color for each range band, e.g. to match a brand palette. Bands left unset use the default shading.',
+              ),

Review Comment:
   Good catch @sadpandajoe. `range_colors` now sets `resetOnHide: false`, so the colors survive the temporary hide while the ranges text is retyped. Added a control-panel test asserting it.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309435168


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,103 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;

Review Comment:
   The precedence logic itself was already right, hasExplicitColors covers the partial case the same as the full one, but you'\''re right there was no test pinning that down. Added one with a one-entry value array and legacy indices '\''3,1'\'', checked row 1 shows the scheme'\''s positional color instead of the legacy one, and that editing row 0 does not drag the legacy color into row 1'\''s onChange.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,28 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',

Review Comment:
   Yep, missed that one. The MCP preview now captures range_colors the same pre-sort way Bullet/transformProps.ts does and uses it as a fixed color on the rect layer, falling back to the legend-based coloring when it'\''s unset so an existing Bullet chart previews unchanged.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4312275369


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   Replaying the analogous-color expansion isn't needed to avoid the wrong color. The wrong value is only written because an index past the static palette (10 on a 9-color scheme) is wrapped modulo 9 and then persisted once any row is edited. Could `resolveLegacyIntervalColors` skip resolution (leave the legacy indices untouched) whenever any index exceeds the scheme length, so those charts keep rendering through the renderer's own path?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4312598188


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   Good call, @sadpandajoe. `resolveLegacyIntervalColors` now leaves every band unresolved when any index exceeds the scheme length, so those charts keep rendering through the renderer's own path. Added a test.



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1200,9 +1200,23 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
-    colors = [
-        _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
-    ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    explicit_colors = form_data.get("interval_colors")

Review Comment:
   Fixed, @sadpandajoe. Legacy index parsing/validation is skipped when `interval_colors` is populated, with tests for stale and unparseable indices alongside explicit colors.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,82 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // Bounds without an explicit legacy index keep their positional color,
+  // exactly as the Gauge renderer does (`colors[idx]`).
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %

Review Comment:
   Fixed, @sadpandajoe. Indices below 1 now fall back to the positional scheme color like the renderer, with a parity test for `0,2`.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts:
##########
@@ -225,6 +227,8 @@ export default function transformProps(chartProps: ChartProps) {
     compType,
     comparisonColorEnabled,
     comparisonColorScheme,
+    increaseColor,

Review Comment:
   Added a `transformProps` test that passes `increase_color`/`decrease_color` through `ChartProps` and asserts `increaseColor`/`decreaseColor`, @sadpandajoe.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4322114381


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+  expect(screen.getByText('-20')).toBeInTheDocument();
+});
+
+// The precise color resolution (legacy `comparisonColorScheme` fallback,
+// custom hex precedence, the neutral disabled state) is covered by pure
+// unit tests against `resolveComparisonColorKeys` / `getComparisonColorTokens`
+// in utils.test.ts, since emotion's `css` prop styles aren't reliably
+// observable through jsdom's computed styles. These tests instead confirm
+// PopKPI renders without crashing across the same prop combinations.
+test('renders without crashing with only the legacy comparisonColorScheme (backward compatibility)', () => {
+  render(<PopKPI {...baseProps} comparisonColorScheme="Red" />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+});
+
+test('renders without crashing with a custom increaseColor hex', () => {

Review Comment:
   These three color tests only assert that the arrow renders, so they still pass if PopKPI stops applying the resolved colors. For example, with `comparisonColorScheme="Red"` and `increaseColor="#336699"`, dropping the wiring would paint the arrow with the legacy error color and `↑` would still be found. The helper tests cover the conversion but not that PopKPI uses it. Could this assert the arrow and pill colors with `toHaveStyleRule`, plus the neutral styling when `comparisonColorEnabled` is false?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,114 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost.
+  const writeLength = hasTrailingBlankToken(intervals)

Review Comment:
   While `intervals` ends in the tolerated trailing blank, `writeLength` is the shortened bounds length, so a color edit persists a shorter explicit array and turns off the legacy fallback. For example, with bounds `20,40,60` and legacy indices `3,2,1`, erase `60`, edit the first color while the bounds read `20,40,`, then restore `60`. The third band now renders positional color 3 instead of its original legacy color 1. Should the color edit be held until the bounds are complete, or the legacy colors be resolved for the full previous length?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4297914208


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +105,50 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  if (
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(

Review Comment:
   Good catch. The dashboard hydration path (applyDefaultFormData) no longer resolves the legacy Gauge indices into literal colors, so they stay palette-relative and the renderer resolves them against the effective scheme after the dashboard override is applied. The migration still runs when opening the chart in Explore.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,26 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',

Review Comment:
   Fair, the old text input was keyboard friendly and the antd trigger isn't. ColorPickerControl now makes the swatch a focusable button (tabindex + role) and Enter/Space open the picker, so this covers the Gauge/Bullet rows and the other users of the control.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +107,40 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const getBandLabelColor = (fill?: string): string => {
+    const luminance = fill ? getColorLuminance(fill) : null;
+    if (luminance === null) return theme.colorTextSecondary;
+    return luminance < 0.4 ? theme.colorTextLightSolid : theme.colorText;

Review Comment:
   Agreed, a theme token can't be right here. The label color now comes from the fill itself (fixed light or dark foreground by luminance), so white in a dark theme gets dark text. Near-opaque fills like #000000fc are measured by their own channels, and only mostly transparent fills (alpha under 0.5) fall back to the theme secondary text color.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   Good catch. I kept the control names so saved Big Number charts don't need a migration, and made the Waterfall and Candlestick readers tolerate a hex string (falling back to their defaults for anything unrecognized) via a small normalizeRgbaColor helper, so switching viz types no longer throws.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4366134065


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -193,10 +234,12 @@ export default function ColorPickerControl({
     const rgb = color.toRgb();
     const hex = rgbaToHex(rgb).toLowerCase();
 
+    // Match against the same hex the preset chip and trigger display, so a
+    // special color picked from a theme-resolved chip maps back to its key.
     const specialEntry = resolveThemeTokens
-      ? Object.entries(SPECIAL_COLORS).find(
-          ([, rgba]) => rgbaToHex(rgba).toLowerCase() === hex,
-        )
+      ? Object.keys(SPECIAL_COLORS)
+          .map(key => [key, toDisplayHex(key, themeColors, true)] as const)
+          .find(([, displayHex]) => displayHex === hex)

Review Comment:
   Picking the ordinary success swatch now stores `Green` instead of `colorSuccess`. With `theme.colorSuccess = '#52c41a'`, `toDisplayHex('Green', themeColors, true)` returns that same hex, so this match fires for any control with `resolveThemeTokens`, whether or not Green/Red presets were supplied. In Table conditional formatting (no `extraColorChoices`), choosing the `colorSuccess` (or `colorError`) swatch emits `Green`/`Red`, which hides the operator and target fields (`showOperatorFields` in `FormattingPopoverContent`) and turns an ordinary color rule into a trend rule. Could the special-key match be limited to the Green/Red values actually present in the supplied presets, or checked after the theme-token reverse lookup? A component test with `colorSuccess` set to the SPECIAL_COLORS-resolved hex, clicking the ordinary `colorSuccess` swatch with no trend presets and asserting `onChange('colorSuccess')`, would pin it.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4368845591


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,125 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost. For a legacy
+  // chart with no explicit colors yet, the legacy indices are the only
+  // record of how many bands there were, so they set that floor too;
+  // otherwise the first color edit in this window would persist a shorter
+  // explicit array, and the bound being retyped would come back with the
+  // positional scheme color instead of its legacy one.
+  const legacyIndexCount = legacyIntervalColorIndices?.trim()
+    ? legacyIntervalColorIndices.split(',').length
+    : 0;
+  const writeLength = hasTrailingBlankToken(intervals)
+    ? Math.max(
+        bounds.length,
+        value?.length ?? 0,
+        hasExplicitColors ? 0 : legacyIndexCount,
+      )
+    : bounds.length;
+  const legacyColors = resolveLegacyColors(
+    writeLength,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    (hasExplicitColors ? '' : legacyColors[index]) ||
+    schemeColors[index % (schemeColors.length || 1)] ||

Review Comment:
   Editing one band can silently recolor an untouched band here. `colorAt` falls back to the static `schemeColors[index % len]`, but the renderer resolves legacy indices against its own render-time palette (it wraps with `(ind - 1) % colorFn.colors.length`, and that palette grows under `USE_ANALOGOUS_COLORS`), while `resolveLegacyIntervalColors` deliberately returns blanks when an index passes the static palette. Example: palette `[A,B,C]`, bounds `50,100,150`, legacy indices `2,10` renders band 1 as B, but the editor shows A for it; changing only band 3 persists `[A,B,custom]`, so band 1 flips from B to A. The same happens with a nine-color palette under analogous colors and ten bands: editing band 1 persists a wrapped first-palette color into untouched band 10. Could the blank-legacy case keep the editor read-only (or resolve against the same render-time palette the renderer uses) so a row edit only writes the edited band?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4374082469


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,384 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  // Pin the row count first so the per-row assertions can't pass vacuously.
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+  triggers.forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+
+  // A change attempt on a disabled picker must neither open it nor commit.
+  await userEvent.click(triggers[0]);
+  expect(
+    document.querySelector('.ant-color-picker-presets-color'),
+  ).not.toBeInTheDocument();
+  expect(onChange).not.toHaveBeenCalled();
+});
+
+test('explicit value takes precedence over legacy indices', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      value={['#ff0000', '#00ff00']}
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#FF0000')).toBeInTheDocument();
+  expect(screen.getByText('#00FF00')).toBeInTheDocument();
+});
+
+test('a non-positive legacy index falls back to the positional scheme color like the renderer', () => {
+  // The renderer computes `colors[(ind - 1) % n]`, which is `undefined` for
+  // index 0, so that band uses `colors[idx]`; it must not wrap to the last
+  // scheme color.
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="0,2"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.queryByText('#2CA02C')).not.toBeInTheDocument();
+});
+
+test('stays read-only when legacy indices cannot be resolved so untouched bands are never rewritten', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,60"

Review Comment:
   The read-only test omits `value`, but the Gauge control's default is `interval_colors: []` (`DEFAULT_FORM_DATA.intervalColors`). Dropping `value.length > 0` from `hasExplicitColors` would make `[]` count as explicit colors, re-enable editing for an unresolvable legacy chart, and every test here would still pass. Could this case also render with `value={[]}`, `intervals="20,60"` and `legacyIntervalColorIndices="10,3"`, then assert both triggers carry the disabled class and a click leaves `onChange` uncalled?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,125 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost. For a legacy
+  // chart with no explicit colors yet, the legacy indices are the only
+  // record of how many bands there were, so they set that floor too;
+  // otherwise the first color edit in this window would persist a shorter
+  // explicit array, and the bound being retyped would come back with the
+  // positional scheme color instead of its legacy one.
+  const legacyIndexCount = legacyIntervalColorIndices?.trim()
+    ? legacyIntervalColorIndices.split(',').length
+    : 0;
+  const writeLength = hasTrailingBlankToken(intervals)
+    ? Math.max(
+        bounds.length,
+        value?.length ?? 0,
+        hasExplicitColors ? 0 : legacyIndexCount,
+      )
+    : bounds.length;
+  const legacyColors = resolveLegacyColors(
+    writeLength,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    (hasExplicitColors ? '' : legacyColors[index]) ||
+    schemeColors[index % (schemeColors.length || 1)] ||

Review Comment:
   Part of this still reproduces: the read-only guard only covers unresolved legacy indices, but the renderer also expands its palette under `USE_ANALOGOUS_COLORS` when there are more bands than colors, with no legacy indices at all. Example: a nine-color scheme with ten bounds and no `interval_colors` renders band 10 with an analogous color, while `colorAt(9)` shows static color 1; editing band 1 persists that wrapped color into band 10. The same happens when `interval_colors` holds fewer entries than the bounds. Could the editor stay read-only (or resolve against the render-time palette) whenever a band index is past the static scheme length and has no explicit color?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3887770126


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +100,48 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
+              type: 'ColorPickerControl',
+              label: t('Color for increase'),
               default: ColorSchemeEnum.Green,

Review Comment:
   Good catch, fixed. Dropped the static default on `increase_color`/`decrease_color` so the legacy `comparison_color_scheme` fallback in `resolveComparisonColorKeys` still kicks in instead of getting preempted by `applyDefaultFormData`.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,22 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),
               renderTrigger: true,
-              default: DEFAULT_FORM_DATA.intervalColorIndices,
+              default: DEFAULT_FORM_DATA.intervalColors,
+              shouldMapStateToProps: () => true,
+              mapStateToProps: (state: ControlPanelState) => ({
+                intervals: state?.controls?.intervals?.value as string,
+                legacyIntervalColorIndices: state?.controls
+                  ?.interval_color_indices?.value as string,

Review Comment:
   Fixed. `interval_color_indices` isn't a registered control anymore, so `mapStateToProps` now reads it off `state.form_data` instead of `state.controls`, matching the pattern already used for things like `server_pagination` elsewhere in the codebase.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,75 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is a literal hex, in which case the
+ * background is a light (~10% opacity) tint of that same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  return {
+    text: colorValue,
+    background: `${colorValue}1A`,
+    strongText: colorValue,

Review Comment:
   Fixed. `getComparisonColorTokens` now resolves theme token names via the theme object and strips any pre-existing alpha channel before appending the tint suffix, so it stays valid CSS either way.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,22 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',

Review Comment:
   `IntervalColorsControl` only ever shows up in a `controlSetRows` entry, which types `config` as `BaseControlConfig<any>` via `CustomControlItem`, not against `InternalControlType` directly. tsc already runs clean here in `validate-frontend` on this commit, so there's no type error to add it for.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,25 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',
+            config: {
+              type: 'BulletRangeColorsControl',

Review Comment:
   Same story as the IntervalColorsControl thread on this PR, this goes through CustomControlItem's config: BaseControlConfig<any>, not InternalControlType, so it's already unchecked there and tsc is green on this commit.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r3890372071


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,159 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    return parsed && typeof parsed === 'object' ? parsed : null;

Review Comment:
   **Suggestion:** Valid JSON with a non-object `token` is accepted, then spreading strings or arrays rewrites malformed token data into unexpected numeric keys when a picker changes. [type error]
   
   **Assessment:** 🟠 `Major` · 🔁 `Occurrence: Sometimes`
   
   [![Use CodeAnt Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill) [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7c98c1ae8f1641fbb41bf7d109ae6d29&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=7c98c1ae8f1641fbb41bf7d109ae6d29&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/themes/ThemeColorPickers.tsx
   **Line:** 67:68
   **Comment:**
   	*Type Error: Valid JSON with a non-object `token` is accepted, then spreading strings or arrays rewrites malformed token data into unexpected numeric keys when a picker changes.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42910&comment_hash=7b071721b8122a607f0d933fcaccf3f039572668a55ab5b2533281de76bf1f6d&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42910&comment_hash=7b071721b8122a607f0d933fcaccf3f039572668a55ab5b2533281de76bf1f6d&reaction=dislike'>👎</a>



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5471088304

   Heya @sha174n, thanks for the read-through! Given each swap already lands as its own bisectable commit with its own backward-compat handling, I'm inclined to keep this bundled rather than split it four ways at this point. Happy to reconsider if another maintainer feels strongly about it.
   
   And yeah, the useMemo already covers the re-parse cost, as you noted.


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4165184828


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +104,45 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed = formData.comparison_color_scheme === 'Red';
+    formData.increase_color = legacyReversed ? 'Red' : 'Green';
+    formData.decrease_color = legacyReversed ? 'Green' : 'Red';

Review Comment:
   Switched to `ColorSchemeEnum` in a89872c.



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { Button } from '@superset-ui/core/components';
+import {
+  tokenizeToNumericArray,
+  isRangesInputComplete,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow,
+  RangeLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { BulletRangeColorsControlProps } from './types';
+
+/**
+ * Per-range color editor for the Bullet chart. Row *count* is driven by the
+ * sibling `ranges` control (one row per parsed threshold) so range values
+ * keep a single source of truth; this control only owns colors, stored as
+ * an array of hex strings positionally matched to those thresholds. An
+ * empty string entry (or a missing entry past the end of the array) means
+ * "no custom color" -- `Bullet/transformProps.ts` falls back to the default
+ * theme-token band ramp for that range, so existing saved Bullet charts
+ * (which have no `range_colors` at all) render identically to before this
+ * control existed.
+ */
+export default function BulletRangeColorsControl({
+  value,
+  onChange,
+  ranges,
+  ...headerProps
+}: BulletRangeColorsControlProps) {
+  const rangeValues = tokenizeToNumericArray(ranges) ?? [];
+  // While `ranges` is mid-edit (e.g. "20,,60"), tokenizeToNumericArray's
+  // leniency silently compacts it to a shorter list, which would otherwise
+  // shift colors to the wrong positions. Block commits until it's complete
+  // again so no edit can be positionally misaligned once it is.
+  const rangesComplete = isRangesInputComplete(ranges);
+
+  const colorAt = (index: number): string => value?.[index] || '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, color, colorAt));
+  };
+
+  const handleReset = (index: number) => {
+    if (!rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, '', colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {rangeValues.length === 0 ? (
+        <RangeLabel>
+          {t('Add ranges above to configure their colors here.')}
+        </RangeLabel>
+      ) : (
+        rangeValues.map((range, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <RangeRow key={index}>

Review Comment:
   Now `t("Up to %s", range)`, and updated the `.pot` entry to match.



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx:
##########
@@ -0,0 +1,144 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import BulletRangeColorsControl from 'src/explore/components/controls/BulletRangeColorsControl';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no ranges are configured', () => {
+  render(<BulletRangeColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add ranges above to configure their colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed range, unset by default', () => {
+  render(
+    <BulletRangeColorsControl onChange={jest.fn()} ranges="100,200,300" />,
+  );
+
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 200/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 300/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+  // no row has a custom color yet, so no "Use default" reset link shows
+  expect(screen.queryByText('Use default')).not.toBeInTheDocument();
+});
+
+test('shows a "Use default" reset link only for rows with a custom color', () => {
+  render(
+    <BulletRangeColorsControl
+      onChange={jest.fn()}
+      ranges="100,200"
+      value={['#ff0000', '']}
+    />,
+  );
+
+  expect(screen.getAllByText('Use default')).toHaveLength(1);
+});
+
+test('calls onChange with the full color array when a row color is picked', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['', '']}
+    />,
+  );
+
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+
+  await userEvent.click(triggers[0]);
+  await waitFor(() => {
+    expect(document.querySelector('.ant-color-picker')).toBeInTheDocument();
+  });
+
+  const hexInput = document.querySelector<HTMLInputElement>(
+    '.ant-color-picker-input input',
+  );
+  expect(hexInput).toBeInTheDocument();
+  await userEvent.clear(hexInput!);
+  await userEvent.type(hexInput!, '00ff00{enter}');
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalled();
+  });
+  const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
+  expect(lastCall[0].toLowerCase()).toBe('#00ff00');
+  expect(lastCall[1]).toBe('');
+});
+
+test('resets a single row back to "no custom color" without touching the others', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['#ff0000', '#00ff00']}
+    />,
+  );
+
+  const resetButtons = screen.getAllByText('Use default');
+  expect(resetButtons).toHaveLength(2);
+  await userEvent.click(resetButtons[0]);
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalledWith(['', '#00ff00']);
+  });
+});
+
+test('disables editing and resets while ranges has a blank token between numbers', () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="20,,60"
+      value={['#ff0000', '#00ff00', '#0000ff']}
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // collapse row count or allow commits that would misalign colors once
+  // the blank is filled back in.
+  document.querySelectorAll('.ant-color-picker-trigger').forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+  // AntD's Button always wraps its text child in an inner <span>, so
+  // querying by text here would match that span -- which `toBeDisabled`
+  // never recognizes (it only supports actual form-control elements).
+  // Query by role to get the real <button>, where `disabled` lives.
+  screen.getAllByRole('button', { name: 'Use default' }).forEach(button => {
+    expect(button).toBeDisabled();
+  });
+});

Review Comment:
   Added the `onChange` not-called assertion.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,124 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;

Review Comment:
   Cast to `Record<string, unknown>` with a `typeof === "string"` guard now.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4170143233


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,100 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||
+    schemeColors[index % (schemeColors.length || 1)] ||
+    '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !boundsComplete) return;
+    onChange?.(replaceColorAtIndex(bounds.length, index, color, colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {bounds.length === 0 ? (
+        <BoundLabel>
+          {t('Add interval bounds above to configure colors here.')}
+        </BoundLabel>
+      ) : (
+        bounds.map((bound, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <IntervalRow key={index}>

Review Comment:
   <!-- Bito Reply -->
   Yes, using `t('Up to %s', bound)` is the recommended approach for i18n consistency. It allows translators to adjust the word order to fit the target language's grammar, whereas splitting the string into two nodes forces an English-specific word order.
   
   **superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx**
   ```
   bounds.map((bound, index) => (
             // eslint-disable-next-line react/no-array-index-key
             <IntervalRow key={index}>
               <BoundLabel>{t('Up to %s', bound)}</BoundLabel>
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5969037470

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4119026934


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,106 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+});

Review Comment:
   <!-- Bito Reply -->
   The update is acknowledged. Adding the assertion for the value text ensures the test correctly validates the rendered output, addressing the previous concern regarding unasserted test props.



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,114 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;
+
+const parseRanges = (ranges?: string): number[] =>
+  (ranges ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(range => Number.isFinite(range));

Review Comment:
   <!-- Bito Reply -->
   This is a sound approach. Centralizing the tokenization logic into a single utility function eliminates the risk of synchronization drift between the two implementations, which is a common source of bugs in this type of configuration control.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4250508646


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +105,50 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  if (
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(
+      bounds,
+      formData.interval_color_indices,
+      formData.color_scheme,
+    );
+    if (resolved.some(color => color !== '')) {
+      formData.interval_colors = resolved;

Review Comment:
   This resolves legacy gauge indices against the chart's own `color_scheme` when the form data is hydrated, and the result is stored as literal `interval_colors`. On a dashboard with a color scheme override (`getFormDataWithExtraFilters` sets `color_scheme` from the dashboard after `applyDefaultFormData` runs), a legacy gauge that used to take its interval colors from the dashboard palette will now keep the old chart-level palette colors, since `interval_colors` wins in `getIntervalBoundsAndColors`. Existing dashboards would change color without anyone editing or saving the chart. Should the migration be limited to the Explore edit path, or should the legacy indices stay bound to the effective scheme at render time?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4254358648


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   `increase_color`/`decrease_color` are also the Waterfall and Candlestick control names, which expect `{r, g, b}` objects. Setting `increase_color: '#336699'` here and then switching the chart type to Waterfall keeps the string, and `rgbToHex(increaseColor.r, ...)` is called with `undefined`, so the chart throws. The guard added for the other direction doesn't cover this one; should these controls get distinct names, or the Waterfall/Candlestick readers tolerate strings?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +107,40 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const getBandLabelColor = (fill?: string): string => {
+    const luminance = fill ? getColorLuminance(fill) : null;
+    if (luminance === null) return theme.colorTextSecondary;
+    return luminance < 0.4 ? theme.colorTextLightSolid : theme.colorText;

Review Comment:
   A custom band color can make its own label unreadable: a white fill in the dark theme takes this `theme.colorText` branch, which is also light, and a translucent fill such as `#000000fc` returns `null` luminance and keeps the dark `colorTextSecondary` on a near-black band. With Show labels on, those labels disappear. Should the label color be derived from the fill itself (including its alpha) rather than from a theme token?



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -1196,6 +1197,12 @@ def merge_gauge_update_form_data(  # noqa: C901
             patch.pop(MCP_DASHBOARD_TIME_FILTER_SUBJECT, None)
 
     merged.update(patch)
+    if (
+        "interval_color_indices" in fields_set
+        and "interval_colors" in existing_form_data
+    ):
+        # Explicit palette indices supersede saved literal interval colors.
+        merged.pop("interval_colors", None)

Review Comment:
   This only runs in `merge_gauge_update_form_data`, which Gauge reaches on a dataset rebind. A same-dataset `update_chart` goes through `overlay_update_form_data` instead, so a saved Gauge with `interval_colors: ['#ff0000', '#00ff00']` that is updated with `interval_color_indices: '2,3'` keeps the literal colors and the requested indices are ignored by the renderer. Should the same pop be applied on the overlay path?



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/controlPanel.tsx:
##########
@@ -59,6 +62,28 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'range_colors',

Review Comment:
   Saving a Bullet chart from Explore now persists `range_colors` (even as `[]`), but `BulletChartConfig` neither models nor strips it. A later MCP `update_chart` that only changes, say, the metric merges the saved form data and `validate_merged_bullet_form_data` rejects `range_colors` as an unknown field, so the update fails. Should `range_colors` be modeled on the config or added to the preserved-key set?



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1185,9 +1185,22 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
-    colors = [
-        _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
-    ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    explicit_colors = form_data.get("interval_colors")
+    if not isinstance(explicit_colors, list):
+        explicit_colors = []
+    colors = []
+    for position in range(max(1, len(interval_bounds))):
+        # Explicit per-interval colors win; blanks fall back to the legacy
+        # palette index for that band, else the palette cycled by position.
+        custom = explicit_colors[position] if position < len(explicit_colors) else None
+        if isinstance(custom, str) and custom:
+            colors.append(custom)
+        elif position < len(color_indices):

Review Comment:
   The frontend ignores legacy indices once `interval_colors` is non-empty and uses the positional palette color for a blank entry (`Gauge/transformProps.ts`). Here a blank entry still falls back to `color_indices[position]`, so with bounds `50,100`, indices `1,4` and `interval_colors: ['#ff0000', '']` the preview paints the second band with palette color 4 while the saved chart renders color 2. Should this fall back only to the positional palette color when explicit colors are present?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309432719


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +111,42 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const getBandLabelColor = (fill?: string): string => {
+    const luminance = fill ? getColorLuminance(fill) : null;
+    if (luminance === null) return theme.colorTextSecondary;
+    // Fixed light/dark foregrounds, not `theme.colorText`: that token flips
+    // to a light color in dark themes and would vanish on a light custom fill.
+    return luminance < 0.4 ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR;

Review Comment:
   Yeah, 0.4 was just a number I picked by eye, not derived from anything real. Swapped it for an actual WCAG contrast comparison between the fill and each of the two fixed foregrounds, so it picks whichever one really contrasts better instead of a cutoff that does not match where they actually break even. Added a test with #999999 since that'\''s right in the range the old cutoff got wrong.



##########
superset-frontend/src/explore/store.test.tsx:
##########
@@ -373,3 +385,108 @@ test('handleDeprecatedControls is idempotent — no-op when matrixify_enable alr
   expect(formData.matrixify_mode_rows).toBe('dimensions');
   expect(formData.matrixify_mode_columns).toBe('metrics');
 });
+
+// ============================================================
+// #42910: legacy color-picker field migrations. `comparison_color_scheme`
+// and `interval_color_indices` are no longer registered controls, so
+// `getFormDataFromControls` drops them on the next save — these migrations
+// carry their value forward into the controls that replaced them before
+// that can happen.
+// ============================================================
+
+test('handleDeprecatedControls migrates comparison_color_scheme Green into increase/decrease colors', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Green',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('Green');
+  expect(formData.decrease_color).toBe('Red');
+});
+
+test('handleDeprecatedControls migrates the reversed comparison_color_scheme Red choice', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Red',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('Red');
+  expect(formData.decrease_color).toBe('Green');
+});
+
+test('handleDeprecatedControls does not override already-set increase/decrease colors', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Red',
+    increase_color: '#336699',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('#336699');
+  expect(formData.decrease_color).toBeUndefined();
+});
+
+test('handleDeprecatedControls is a no-op for BigNumberPeriodOverPeriod without a legacy scheme', () => {
+  const formData: any = { viz_type: VizType.BigNumberPeriodOverPeriod };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBeUndefined();
+  expect(formData.decrease_color).toBeUndefined();
+});
+
+test('handleDeprecatedControls migrates interval_color_indices into interval_colors', () => {
+  const formData: any = {
+    viz_type: VizType.Gauge,
+    intervals: '20,40,60',
+    interval_color_indices: '2,3',
+    color_scheme: 'testScheme',
+  };
+  handleDeprecatedControls(formData);
+
+  // The uncovered third bound keeps its positional scheme color, matching
+  // what the Gauge renderer drew before the migration.
+  expect(formData.interval_colors).toEqual(['#ff7f0e', '#2ca02c', '#2ca02c']);
+});
+
+test('applyDefaultFormData keeps legacy interval_color_indices unresolved for dashboards', () => {
+  // The dashboard's color_scheme override is applied after hydration, so the
+  // legacy palette-relative indices must not be frozen into literal colors.
+  const result = applyDefaultFormData({
+    viz_type: VizType.Gauge,
+    datasource: '1__table',
+    intervals: '20,40,60',
+    interval_color_indices: '2,3',
+    color_scheme: 'testScheme',
+  } as any);
+
+  expect(result.interval_color_indices).toBe('2,3');
+  expect(result.interval_colors).toEqual([]);

Review Comment:
   This one'\''s actually already fixed on master, a follow-up commit added the `?? []` guard so it is not failing in CI anymore. But you'\''re right that it was never really exercising the real default, just papering over `interval_colors` being absent from `controlsState` entirely. Registered the actual Gauge `interval_colors` control for that test now so it goes through `getAllControlsState`'\''s real default instead.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309431762


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;
+      onChange(patchThemeJsonToken(jsonData, key, color));
+    };
+
+  return (
+    <div data-test="theme-color-pickers">
+      {!isValid && (
+        <Typography.Text
+          type="secondary"
+          data-test="theme-color-pickers-invalid-json-notice"
+        >
+          {t(
+            'Fix the JSON errors below to edit colors here; your changes ' +
+              'sync automatically once the JSON is valid again.',
+          )}
+        </Typography.Text>
+      )}
+      <TokenGrid>
+        {CURATED_COLOR_TOKENS.map(({ key, label }) => {
+          const tokenValue = tokenValues[key];
+          return (
+            <TokenRow key={key} data-test={`theme-color-picker-${key}`}>
+              <Typography.Text>{label()}</Typography.Text>
+              <ColorPickerControl
+                ariaLabel={label()}
+                value={typeof tokenValue === 'string' ? tokenValue : undefined}

Review Comment:
   Good catch. `toDisplayHex` was doing the SPECIAL_COLORS/theme-token lookup unconditionally instead of gating it on `resolveThemeTokens`, so this caller (which never sets that flag) could hit a numeric theme key like `fontSize` and call `.toLowerCase()` on a number mid-render. Fixed by actually checking the flag before any of that resolution runs, so a plain string just stays a plain string here. Added a direct test on `toDisplayHex` for this.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   This one I did not fix. legacyColors.ts only has the registry'\''s static palette to work with, while the renderer'\''s colorFn.colors can grow past that once incrementColorRange() kicks in for grouped series under UseAnalogousColors, and that growth depends on the actual query result and render-time color assignment, not just the scheme name. Reproducing that in the control panel would mean replaying the whole analogous-color expansion outside of an actual render pass, which feels like a lot of surface area to take on for a flag-gated edge case. Leaving this open, want your read on whether it'\''s worth doing or just a documented limitation.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309435948


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/utils.ts:
##########
@@ -21,17 +21,98 @@
 export function tokenizeToNumericArray(value?: string): number[] | null {
   if (!value?.trim()) return null;
   // Lenient by design: this runs on every keystroke, so partial input like
-  // "50," must not throw. Empty and non-numeric tokens are dropped.
+  // "50," must not throw. Empty, non-numeric, and non-finite tokens (e.g.
+  // "Infinity", or a value large enough to overflow to it) are dropped.
+  // `BulletRangeColorsControl` shares this exact function (not a
+  // reimplementation) since its per-range colors are matched positionally
+  // against this same tokenized list -- a divergent tokenizer would
+  // silently misalign colors to the wrong bands.
   const numbers = value
     .split(',')
     .map(token => token.trim())
     .filter(token => token !== '')
     .map(token => Number(token))
-    .filter(n => !Number.isNaN(n));
+    .filter(n => Number.isFinite(n));
   return numbers.length ? numbers : null;
 }
 
 export function tokenizeToStringArray(value?: string): string[] | null {
   if (!value?.trim()) return null;
   return value.split(',').map(token => token.trim());
 }
+
+/**
+ * Whether every comma-separated token in `value` is itself a complete,
+ * valid number -- i.e. the string isn't in a transient, mid-edit state such
+ * as "20,,60" (a blank token between two valid ones). A single blank
+ * trailing token (e.g. "20,40,") is tolerated, since that's the normal shape
+ * of the string while the user is still typing the next value.
+ *
+ * `tokenizeToNumericArray` is deliberately lenient (it silently drops blank
+ * tokens so partial input never throws), which is right for parsing the
+ * list once it's complete but wrong for anything that positionally matches
+ * against it, like `BulletRangeColorsControl`'s per-range colors -- a blank
+ * in the middle must not be treated as "one fewer range" there, since a
+ * color edit made while the list is in that transient state would
+ * permanently misalign once the blank is filled back in.
+ */
+export function isRangesInputComplete(value?: string): boolean {
+  if (!value?.trim()) return true;
+  const tokens = value.split(',').map(token => token.trim());
+  const tokensToValidate =
+    tokens[tokens.length - 1] === '' ? tokens.slice(0, -1) : tokens;

Review Comment:
   Confirmed, erasing the last value and picking a color right then was writing a shorter array and dropping the last color for good. Both controls now keep the array at its previous length while there'\''s a trailing blank token, the same window isRangesInputComplete already tolerates, instead of shrinking to the transiently-shorter parsed list. Added a test on each control that erases the last value, picks a color on row 0, and checks the third color survives.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts:
##########
@@ -53,29 +53,52 @@ import { getColtypesMapping } from '../utils/series';
 export const getIntervalBoundsAndColors = (
   intervals: string,
   intervalColorIndices: string,
+  intervalColors: string[] | undefined,
   colorFn: CategoricalColorScale,
   min: number,
   max: number,
 ): Array<[number, string]> => {
   let intervalBoundsNonNormalized;
-  let intervalColorIndicesArray;
   try {
     intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
-    intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
   } catch (error) {
     intervalBoundsNonNormalized = [] as number[];
-    intervalColorIndicesArray = [] as number[];
   }
 
   const intervalBounds = intervalBoundsNonNormalized.map(
     bound => (bound - min) / (max - min),
   );
-  const intervalColors = intervalColorIndicesArray.map(
+
+  // New-style: `interval_colors` holds real hex/rgb colors chosen through
+  // the Gauge control panel's per-interval `IntervalColorsControl`,
+  // positionally matched to the bounds parsed from `intervals`. Prefer this
+  // whenever it's populated.
+  if (Array.isArray(intervalColors) && intervalColors.length > 0) {
+    return intervalBounds.map((val, idx) => [
+      val,
+      intervalColors[idx] || colorFn.colors[idx],

Review Comment:
   Good catch, the new-style fallback was indexing colorFn.colors straight off the end instead of wrapping like IntervalColorsControl'\''s own display does. Wrapped it with the same modulo so the two agree, and editing an untouched band can'\''t persist undefined into it anymore.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
-              renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
-              ],
-              visibility: ({ controls }) =>
-                controls?.comparison_color_enabled?.value === true,
+              ...comparisonColorControlConfig,
+              label: t('Color for increase'),

Review Comment:
   Good catch, added ariaLabel to both.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4312259197


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts:
##########
@@ -225,6 +227,8 @@ export default function transformProps(chartProps: ChartProps) {
     compType,
     comparisonColorEnabled,
     comparisonColorScheme,
+    increaseColor,

Review Comment:
   The new `increaseColor`/`decreaseColor` pass-through isn't covered by any test: the helper tests and the `PopKPI` renders bypass `transformProps`, so removing these two assignments (or mis-casing the snake_case form_data keys) leaves everything green while picker changes stop reaching the chart. Could a `transformProps` test pass `increase_color`/`decrease_color` through `ChartProps` and assert the returned `increaseColor`/`decreaseColor`?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,82 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // Bounds without an explicit legacy index keep their positional color,
+  // exactly as the Gauge renderer does (`colors[idx]`).
+  return bounds.map((_, index) => {
+    const legacyIndex = indices[index];
+    if (schemeColors.length === 0) return '';
+    if (Number.isFinite(legacyIndex)) {
+      return schemeColors[
+        (((legacyIndex - 1) % schemeColors.length) + schemeColors.length) %

Review Comment:
   The positive-modulo normalization differs from the renderer for non-positive legacy indices: `getIntervalBoundsAndColors` computes `colors[(ind - 1) % n]`, which is `undefined` for index `0`, so that band falls back to `colors[idx]`. With a 3-color scheme [A,B,C], bounds `20,60`, and indices `0,2`, the chart renders [A,B] but this resolver shows [C,B], and editing only the second row then persists C into the untouched first band. Should this keep the renderer's fallback for indices below 1, with a parity test for `0,2`?



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1200,9 +1200,23 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
-    colors = [
-        _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
-    ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    explicit_colors = form_data.get("interval_colors")

Review Comment:
   Explicit `interval_colors` only take precedence here, after the `interval_color_indices` length check at line 1158 has already run. For a legacy Gauge with `intervals: "30,60,100"`, a stale `interval_color_indices: "1,2"` (kept in form_data after a bound was added), and three saved `interval_colors`, Explore renders the three explicit colors but this preview returns `InvalidGaugeFormData`. Could the legacy-index parsing/validation be skipped when `interval_colors` is populated, and a test added with unparseable or wrong-length indices alongside explicit colors?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "SBIN2010 (via GitHub)" <gi...@apache.org>.
SBIN2010 commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4353275203


##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,42 @@ const config: ControlPanelConfig = {
         ],
         [
           {
+            // Legacy 1-indexed positions into `color_scheme`, replaced by
+            // `interval_colors` below. Explore builds the chart's query and
+            // save form data from registered controls only, so this stays
+            // registered (hidden) to keep an untouched legacy chart
+            // rendering and saving with its original palette-relative
+            // colors. The renderer ignores it once `interval_colors` is
+            // populated by the first edit in the control below.
             name: 'interval_color_indices',
             config: {
-              type: 'TextControl',
+              type: 'HiddenControl',
+              hidden: true,
+            },
+          },
+        ],
+        [
+          {
+            name: 'interval_colors',
+            config: {
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),

Review Comment:
   This description says legacy interval indices are resolved the first time the panel is opened. `handleDeprecatedControls` now leaves `interval_color_indices` palette-relative until the user edits a color, and the hidden control is what keeps that value on save. Could the description say the colors stay scheme-relative until an interval color is edited?



##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########


Review Comment:
   With `resolveThemeTokens`, `toDisplayHex` shows `theme.colorSuccess` for the stored value `"Green"`, but this preset swatch is still `SPECIAL_COLORS.Green` (`#00960033`). The trigger and the preset chip no longer share a hex, so the Green preset does not read as the selected value. Could the preset list use the same `SPECIAL_COLOR_THEME_TOKENS` lookup as `toDisplayHex`?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4320746382


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,63 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: on request, migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Defaults to off
+  // (unlike the comparison_color_scheme migration above): unlike that
+  // control, `interval_color_indices` is never dropped from `form_data` by
+  // an unrelated save -- it isn't a registered control, but `form_data` is
+  // persisted as a plain object, not rebuilt from registered controls, so
+  // an untouched legacy string survives save as-is. Eagerly resolving it
+  // merely from opening the chart (the old default here) froze it into
+  // literal colors on the *first* unrelated resave too, silently breaking a
+  // dashboard override of this chart's `color_scheme` applied afterward --
+  // the exact hazard the dashboard hydration path below was already written
+  // to avoid, just not for this path. `IntervalColorsControl` already shows
+  // the resolved colors for display on its own (see its own
+  // `legacyIntervalColorIndices` prop), so the indices stay palette-relative
+  // and keep following the effective palette until the user actually edits
+  // an interval color, which is the only point this needs to be literal.
+  if (
+    resolveLegacyGaugeColors &&

Review Comment:
   With the Gauge migration off by default, opening a legacy Gauge in Explore builds its render and save data from the registered controls, and `interval_color_indices` is no longer one (the Gauge control panel dropped it). A chart saved with a `[A,B,C]` palette, `intervals: "20,40"` and `interval_color_indices: "2,3"` therefore previews with positional colors `[A,B]` instead of `[B,C]`, and a resave drops the indices, so the stored chart changes colors without the user touching the Interval colors control. Should the indices stay in the form data (for example a hidden control) until an interval color is actually edited?



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2799,9 +2834,18 @@ def threshold_details(
                     "x": {"datum": axis_min, "type": "quantitative"},
                     "x2": {"datum": threshold},
                     "y": y_encoding,
-                    "color": legend_color(
-                        (f"{label}: " if label else "")
-                        + f"≤ {_format_bullet_number(model.y_axis_format, threshold)}"
+                    # A custom `range_colors` entry wins; otherwise fall back
+                    # to the default categorical legend coloring exactly as
+                    # before this control existed, so a Bullet chart saved
+                    # without `range_colors` previews unchanged.
+                    "color": (
+                        {"value": color}

Review Comment:
   A custom `range_colors` entry swaps this layer's color encoding for a fixed `{"value": color}`, which bypasses `legend_color`. With `ranges: "50,100"`, `range_labels: "Low,High"`, `show_legend: true` and `range_colors: ["#ff0000","#00ff00"]`, the preview legend lists only the metric and both range entries disappear, while a chart without custom colors keeps them. Could the custom colors be applied through a scale on the legend-bearing encoding so the range labels stay in the legend?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,284 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  document.querySelectorAll('.ant-color-picker-trigger').forEach(trigger => {

Review Comment:
   Every assertion in this test sits inside `querySelectorAll(...).forEach(...)`, so if the control rendered no pickers for `intervals="20,,60"` the test would still pass without running one `expect`. Could it first assert that two `.ant-color-picker-trigger` elements exist, and then check that a change attempt does not call `onChange`?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   The out-of-palette case still changes untouched bands once any row is edited. With a 3-color scheme, `intervals: "50,100,150"` and `interval_color_indices: "10,2"`, `resolveLegacyIntervalColors` now returns blanks, but `colorAt` in `IntervalColorsControl/index.tsx` falls through to `schemeColors[index % n]`, and `replaceColorAtIndex` writes those static colors for every other row. Editing only the second row therefore persists the static first palette color for the first band, replacing the color the renderer produced for index 10, and `interval_colors` then takes precedence over the legacy indices. Could `colorAt` leave unresolved legacy bands untouched, for example by not writing explicit colors until all rows are resolved?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4334044214


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,133 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import { supersetTheme } from '@apache-superset/core/theme';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {

Review Comment:
   The decrease path has no color assertion: every color test here uses the default positive change, and this one only checks the arrow and value text. If PopKPI used the increase color (or dropped the decrease wiring) for a drop, nothing would fail. Could this render with `percentDifferenceNumber={-0.2}`, `increaseColor="#336699"` and `decreaseColor="#994422"` and assert the down arrow and delta pill resolve to `#994422` (background `#9944221A`)?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4299753258


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;
+      onChange(patchThemeJsonToken(jsonData, key, color));
+    };
+
+  return (
+    <div data-test="theme-color-pickers">
+      {!isValid && (
+        <Typography.Text
+          type="secondary"
+          data-test="theme-color-pickers-invalid-json-notice"
+        >
+          {t(
+            'Fix the JSON errors below to edit colors here; your changes ' +
+              'sync automatically once the JSON is valid again.',
+          )}
+        </Typography.Text>
+      )}
+      <TokenGrid>
+        {CURATED_COLOR_TOKENS.map(({ key, label }) => {
+          const tokenValue = tokenValues[key];
+          return (
+            <TokenRow key={key} data-test={`theme-color-picker-${key}`}>
+              <Typography.Text>{label()}</Typography.Text>
+              <ColorPickerControl
+                ariaLabel={label()}
+                value={typeof tokenValue === 'string' ? tokenValue : undefined}

Review Comment:
   A theme whose JSON has `{"token":{"colorPrimary":"Red"}}` is valid CSS red (`#ff0000`), but `ColorPickerControl` treats the string `Red` as the Green/Red comparison preset even without `resolveThemeTokens`, so this picker shows `#96000033`. Nudging the opacity then writes that wrong color back into the theme. Should this caller normalize literal color strings, or turn off the preset lookup for theme tokens?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,103 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;

Review Comment:
   The partial-explicit case isn't covered at the editor boundary: with `intervals="20,60"`, `value={['#ff0000']}` and legacy indices `"3,1"`, the second row should show the scheme's positional color rather than the legacy one. The only precedence test passes a full two-entry array, so reverting `hasExplicitColors` would still pass while editing row 1 silently persists the wrong color for row 2. Could you add a test that renders this partial value and asserts the second swatch and the `onChange` array after editing the first row?



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -4228,6 +4238,9 @@ def merge_update_form_data(  # noqa: C901
     | FORM_DATA_UPDATE_PRESERVE_KEYS["presentation"]
     | FORM_DATA_UPDATE_PRESERVE_KEYS["time"]
 ) - _get_known_fields(BulletChartConfig)
+# Explore-authored Bullet controls the typed config does not model. They are
+# preserved on update but excluded from the typed validation copy.
+_BULLET_UNMODELED_NATIVE_KEYS = frozenset({"range_colors"})

Review Comment:
   The new `range_colors` exemption that keeps a saved Bullet chart's colors out of the typed validation copy has no regression test. If someone drops it from this set, an MCP `update_chart` that only changes the metric on a Bullet saved with `range_colors: ["#ff0000", "#00ff00"]` (or `[]`) starts rejecting the merged state as an unknown field, and nothing in `test_bullet_chart.py` would fail. Could you add a test that merges such an update and asserts `validate_merged_bullet_form_data` succeeds with the colors unchanged?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4376003190


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -35,6 +36,30 @@ import {
   metricNameFontSizeWithVisibility,
 } from '../sharedControls';
 
+// Shared base for the `increase_color` / `decrease_color` pickers so the
+// presets, token resolution, output format and visibility rule can't drift
+// apart. No static default: charts saved before these controls existed only
+// have `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+// increase/decrease colors). Leaving each control's value undefined lets
+// `resolveComparisonColorKeys` (see BigNumberPeriodOverPeriod/utils.ts)
+// resolve the correct color from that legacy scheme at render time. A
+// hardcoded default here would win over the legacy fallback via
+// `applyDefaultFormData` and silently repaint old dashboards.
+const comparisonColorControlConfig: CustomControlItem['config'] = {

Review Comment:
   With no `increase_color`/`decrease_color` saved (any new chart), both pickers read 'Transparent' while PopKPI paints the theme success/error colors, so the editor never shows what the chart draws.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,135 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost. For a legacy
+  // chart with no explicit colors yet, the legacy indices are the only
+  // record of how many bands there were, so they set that floor too;
+  // otherwise the first color edit in this window would persist a shorter
+  // explicit array, and the bound being retyped would come back with the
+  // positional scheme color instead of its legacy one.
+  const legacyIndexCount = legacyIntervalColorIndices?.trim()
+    ? legacyIntervalColorIndices.split(',').length
+    : 0;
+  const writeLength = hasTrailingBlankToken(intervals)
+    ? Math.max(
+        bounds.length,
+        value?.length ?? 0,
+        hasExplicitColors ? 0 : legacyIndexCount,
+      )
+    : bounds.length;
+  const legacyColors = resolveLegacyColors(
+    writeLength,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Legacy indices past the static palette resolve only against the
+  // renderer's render-time palette, so the editor cannot show (or safely
+  // persist) the colors the chart renders. Stay read-only until explicit
+  // colors exist, so a row edit never rewrites an untouched band.
+  const legacyUnresolved =
+    !hasExplicitColors &&
+    legacyIndexCount > 0 &&
+    legacyColors.length > 0 &&
+    legacyColors.every(color => color === '');
+  const editable = boundsComplete && !legacyUnresolved;

Review Comment:
   When an in-use legacy index is past the static palette, every row is disabled with no explanation and the legacy field is hidden, so these bands can't be recolored except by switching scheme, while the help text still says colors follow the scheme until one is edited.



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,89 @@
+/**
+ * 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 {
+  getCategoricalSchemeRegistry,
+  validateNumber,
+} from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, one per interval band (`boundCount` entries). Shared by the
+ * control panel (display-only fallback while a legacy chart is being edited)
+ * and by `handleDeprecatedControls` (which, on request, migrates the value
+ * into `interval_colors`).
+ */
+export const resolveLegacyIntervalColors = (
+  boundCount: number,
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  // Parse with the exact same semantics `getIntervalBoundsAndColors` in
+  // Gauge/transformProps.ts uses for this same legacy fallback (via
+  // `parseNumbersList`, not reimported here to avoid pulling the whole
+  // `@superset-ui/plugin-chart-echarts` package -- and every chart plugin it
+  // eagerly registers -- into `explore/store.ts`'s import graph): one
+  // unparseable (or blank) token discards every legacy index for the whole
+  // list, rather than resolving the other tokens independently. A more
+  // lenient parser here would let this one-time migration (and this
+  // display-only preview) disagree with how an untouched legacy chart has
+  // always rendered -- see #42910 review thread on this file.
+  let indices: number[];
+  try {
+    indices = (legacyIntervalColorIndices ?? '').split(',').map(part => {
+      if (validateNumber(part)) throw new Error('All values must be numeric');
+      return Number(part);
+    });
+  } catch {
+    indices = [];
+  }
+  // An index past the static palette can only be resolved against the
+  // renderer's render-time palette (which grows under USE_ANALOGOUS_COLORS),
+  // so wrapping it modulo the static scheme would show -- and, once a row is
+  // edited, persist -- a different color than the chart renders. Leave every
+  // band unresolved so such charts keep rendering through the renderer's own
+  // legacy path.
+  if (indices.some(index => index > schemeColors.length)) {

Review Comment:
   Only the first `boundCount` legacy indices are rendered, but every one is checked here: indices `2,3,7` on a 3-color scheme with bounds `20,60` lock both rows and show positional colors, not the rendered `#ff7f0e`/`#2ca02c`.
   
   ```suggestion
     if (indices.slice(0, boundCount).some(index => index > schemeColors.length)) {
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5225162182

   ## [Codecov](https://app.codecov.io/gh/apache/superset/pull/42910?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :x: Patch coverage is `94.40994%` with `9 lines` in your changes missing coverage. Please review.
   :white_check_mark: Project coverage is 66.48%. Comparing base ([`3872790`](https://app.codecov.io/gh/apache/superset/commit/38727906a2b1c68f3c4c56409ea6e94de01fb48a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`7546cfe`](https://app.codecov.io/gh/apache/superset/commit/7546cfe744b084651bfd04081e104e8279e27314?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   
   | [Files with missing lines](https://app.codecov.io/gh/apache/superset/pull/42910?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Patch % | Lines |
   |---|---|---|
   | [...s/plugin-chart-echarts/src/Bullet/controlPanel.tsx](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&filepath=superset-frontend%2Fplugins%2Fplugin-chart-echarts%2Fsrc%2FBullet%2FcontrolPanel.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQtZnJvbnRlbmQvcGx1Z2lucy9wbHVnaW4tY2hhcnQtZWNoYXJ0cy9zcmMvQnVsbGV0L2NvbnRyb2xQYW5lbC50c3g=) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [...ns/plugin-chart-echarts/src/Gauge/controlPanel.tsx](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&filepath=superset-frontend%2Fplugins%2Fplugin-chart-echarts%2Fsrc%2FGauge%2FcontrolPanel.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQtZnJvbnRlbmQvcGx1Z2lucy9wbHVnaW4tY2hhcnQtZWNoYXJ0cy9zcmMvR2F1Z2UvY29udHJvbFBhbmVsLnRzeA==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [...omponents/controls/IntervalColorsControl/index.tsx](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&filepath=superset-frontend%2Fsrc%2Fexplore%2Fcomponents%2Fcontrols%2FIntervalColorsControl%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQtZnJvbnRlbmQvc3JjL2V4cGxvcmUvY29tcG9uZW50cy9jb250cm9scy9JbnRlcnZhbENvbG9yc0NvbnRyb2wvaW5kZXgudHN4) | 95.00% | [2 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [...igNumber/BigNumberPeriodOverPeriod/controlPanel.ts](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&filepath=superset-frontend%2Fplugins%2Fplugin-chart-echarts%2Fsrc%2FBigNumber%2FBigNumberPeriodOverPeriod%2FcontrolPanel.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQtZnJvbnRlbmQvcGx1Z2lucy9wbHVnaW4tY2hhcnQtZWNoYXJ0cy9zcmMvQmlnTnVtYmVyL0JpZ051bWJlclBlcmlvZE92ZXJQZXJpb2QvY29udHJvbFBhbmVsLnRz) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [...s/plugin-chart-echarts/src/Gauge/transformProps.ts](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&filepath=superset-frontend%2Fplugins%2Fplugin-chart-echarts%2Fsrc%2FGauge%2FtransformProps.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQtZnJvbnRlbmQvcGx1Z2lucy9wbHVnaW4tY2hhcnQtZWNoYXJ0cy9zcmMvR2F1Z2UvdHJhbnNmb3JtUHJvcHMudHM=) | 87.50% | [1 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/42910?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   ```diff
   @@            Coverage Diff             @@
   ##           master   #42910      +/-   ##
   ==========================================
   + Coverage   66.40%   66.48%   +0.08%     
   ==========================================
     Files        2857     2860       +3     
     Lines      161293   161434     +141     
     Branches    37134    37174      +40     
   ==========================================
   + Hits       107114   107337     +223     
   + Misses      52153    52071      -82     
     Partials     2026     2026              
   ```
   
   | [Flag](https://app.codecov.io/gh/apache/superset/pull/42910/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
   |---|---|---|
   | [javascript](https://app.codecov.io/gh/apache/superset/pull/42910/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `73.38% <94.40%> (+0.14%)` | :arrow_up: |
   
   Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
   </details>
   
   [:umbrella: View full report in Codecov by Harness](https://app.codecov.io/gh/apache/superset/pull/42910?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).   
   :loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4085500119


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>JSON key reordering on edit</b></div>
   <div id="fix">
   
   `patchThemeJsonToken` spreads `{...parsed, token: {...}}`, which moves `token` to the front of the object (verified: keys become `['token','components']`). Since the JSON textarea is the source of truth, every picker change reorders the JSON keys and produces diff noise on each edit. Preserve original key order when writing the patched object.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #5bd339</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,91 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
+    ? resolvedColor.slice(0, 7)
+    : resolvedColor;
+  return {
+    text: resolvedColor,
+    background: `${opaqueColor}1A`,

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Magic tint suffix '1A'</b></div>
   <div id="fix">
   
   `background: \`${opaqueColor}1A\`` encodes the pill-tint business rule (~10% alpha, per the comment on lines 126-128) as a bare hex fragment. A named constant like `COMPARISON_TINT_ALPHA_HEX = '1A'` would make the tint level searchable and adjustable; today a designer tweak means editing an opaque literal. Minimal fix: add the named constant and an inline comment at this line.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #5bd339</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4068748058


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx:
##########
@@ -195,15 +203,13 @@ export default function PopKPI(props: PopKPIProps) {
     let bgColor = defaultBackgroundColor;
     let txtColor = defaultTextColor;
     if (comparisonColorEnabled && percentDifferenceNumber !== 0) {
-      const useSuccess =
-        (percentDifferenceNumber > 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Green) ||
-        (percentDifferenceNumber < 0 &&
-          comparisonColorScheme === ColorSchemeEnum.Red);
-
-      // Set background and text colors based on the conditions
-      bgColor = useSuccess ? theme.colorSuccessBg : theme.colorErrorBg;
-      txtColor = useSuccess ? theme.colorSuccessText : theme.colorErrorText;
+      const colorValue =
+        percentDifferenceNumber > 0
+          ? resolvedIncreaseColor
+          : resolvedDecreaseColor;

Review Comment:
   <!-- Bito Reply -->
   The suggestion to hoist the color selection logic is appropriate. By centralizing the color determination into a single variable, you ensure consistency between the arrow indicator and the pill's styling, reducing the risk of divergence if the logic is updated in the future.
   
   **superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx**
   ```
   const colorValue =
           percentDifferenceNumber > 0
             ? resolvedIncreaseColor
             : resolvedDecreaseColor;
   ```



##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,174 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;

Review Comment:
   <!-- Bito Reply -->
   The changes correctly address the issue by adding a `disabled` prop to `ColorPickerControl` and ensuring the theme pickers are disabled when the JSON is invalid or the editor is in read-only mode. This ensures the UI behavior is consistent with the underlying logic that drops edits in those states.
   
   **superset-frontend/src/features/themes/ThemeColorPickers.tsx**
   ```
   const handleTokenChange =
       (key: CuratedColorToken) => (color: ColorPickerValue) => {
         if (typeof color !== 'string' || !isValid || disabled) return;
   ```



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +100,57 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',
             config: {
-              type: 'SelectControl',
-              label: t('color scheme for comparison'),
-              default: ColorSchemeEnum.Green,
+              type: 'ColorPickerControl',
+              label: t('Color for increase'),
+              // No static default: charts saved before this control existed
+              // only have `comparison_color_scheme` ('Green' | 'Red', where
+              // 'Red' reverses increase/decrease colors). Leaving this
+              // control's value undefined lets `resolveComparisonColorKeys`
+              // (see BigNumberPeriodOverPeriod/utils.ts) resolve the correct
+              // color from that legacy scheme at render time. A hardcoded
+              // default here would win over the legacy fallback via
+              // `applyDefaultFormData` and silently repaint old dashboards.
               renderTrigger: true,
-              choices: [
-                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
-                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
               ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
               visibility: ({ controls }) =>
                 controls?.comparison_color_enabled?.value === true,
               description: t(
-                'Adds color to the chart symbols based on the positive or ' +
-                  'negative change from the comparison value.',
+                'Color used for the arrow and symbols when the metric ' +
+                  'increased from the comparison value. Defaults to green.',
+              ),
+            },
+          },
+          {
+            name: 'decrease_color',
+            config: {
+              type: 'ColorPickerControl',
+              label: t('Color for decrease'),
+              // See the comment on `increase_color` above: no static
+              // default, so `resolveComparisonColorKeys` can apply the
+              // legacy `comparison_color_scheme` fallback for old charts.
+              renderTrigger: true,
+              presets: [
+                {
+                  label: t('Semantic colors'),
+                  colors: [ColorSchemeEnum.Green, ColorSchemeEnum.Red],
+                },
+              ],
+              resolveThemeTokens: true,
+              outputFormat: 'hex',
+              visibility: ({ controls }) =>
+                controls?.comparison_color_enabled?.value === true,
+              description: t(
+                'Color used for the arrow and symbols when the metric ' +
+                  'decreased from the comparison value. Defaults to red.',
               ),
             },
           },

Review Comment:
   <!-- Bito Reply -->
   The implementation of the shared `comparisonColorControlConfig` correctly addresses the suggestion to reduce duplication. By extracting the common configuration properties and spreading them into both controls, the code is now more maintainable and less prone to divergence.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5707066072

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5471758694

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4140298863


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,97 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, string>;
+  const resolvedColor = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const opaqueColor = /^#[0-9a-f]{8}$/i.test(resolvedColor)
+    ? resolvedColor.slice(0, 7)
+    : resolvedColor;
+  return {
+    text: resolvedColor,
+    background: `${opaqueColor}${COMPARISON_TINT_ALPHA_HEX}`,

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Invalid CSS tint on rgba tokens</b></div>
   <div id="fix">
   
   Theme tokens resolved via `resolveThemeTokens` can hold `rgba()` strings (e.g. `colorText` is `rgba(255,255,255,0.85)` in the dark theme), and `getComparisonColorTokens` unconditionally appends `COMPARISON_TINT_ALPHA_HEX` to any non-8-digit-hex value, producing invalid CSS like `rgba(...)1A` for the pill background. Restrict the tint suffix to 6-digit hex values and pass other formats through unchanged.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e33986</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Button } from '@superset-ui/core/components';
+import { tokenizeToNumericArray } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import { BulletRangeColorsControlProps } from './types';
+
+const RangeRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+const RangeLabel = styled.span`
+  min-width: 90px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+`;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicated sibling control logic</b></div>
   <div id="fix">
   
   This new control duplicates the sibling `IntervalColorsControl` almost verbatim: identical `RangeRow`/`RangeLabel` styled CSS, the same `handleColorChange` (map over parsed values, swap one index), and the same `ColorPickerControl outputFormat="hex"` row rendering. A bug fix or behavior change in one will silently diverge from the other. Consider extracting the shared row/color-picker logic.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e33986</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.test.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 { render, screen } from 'spec/helpers/testing-library';
+import PopKPI from './PopKPI';
+import { PopKPIProps } from './types';
+
+const baseProps: PopKPIProps = {
+  height: 200,
+  width: 400,
+  data: [],
+  metrics: [],
+  metricName: 'Sales',
+  showMetricName: true,
+  headerText: '',
+  boldText: true,
+  bigNumber: '120',
+  prevNumber: '100',
+  valueDifference: '20',
+  percentDifferenceFormattedString: '20%',
+  compType: 'r',
+  percentDifferenceNumber: 0.2,
+  subtitleFontSize: 0.15,
+  shift: '',
+  headerFontSize: 'l',
+  subheaderFontSize: 'm',
+  comparisonColorEnabled: true,
+};
+
+test('renders the up arrow and metric values when the metric increased', () => {
+  render(<PopKPI {...baseProps} />);
+
+  expect(screen.getByText('↑')).toBeInTheDocument();
+  expect(screen.getByText('120')).toBeInTheDocument();
+  expect(screen.getByText('Sales')).toBeInTheDocument();
+});
+
+test('renders the down arrow when the metric decreased', () => {
+  render(
+    <PopKPI
+      {...baseProps}
+      percentDifferenceNumber={-0.2}
+      valueDifference="-20"
+    />,
+  );
+
+  expect(screen.getByText('↓')).toBeInTheDocument();
+  expect(screen.getByText('-20')).toBeInTheDocument();
+});
+
+// The precise color resolution (legacy `comparisonColorScheme` fallback,
+// custom hex precedence, the neutral disabled state) is covered by pure
+// unit tests against `resolveComparisonColorKeys` / `getComparisonColorTokens`
+// in utils.test.ts, since emotion's `css` prop styles aren't reliably
+// observable through jsdom's computed styles. These tests instead confirm
+// PopKPI renders without crashing across the same prop combinations.

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Inaccurate jsdom styles comment</b></div>
   <div id="fix">
   
   The justification that emotion's `css` prop styles 'aren't reliably observable through jsdom's computed styles' is inaccurate: `@emotion/jest@^11.14.2` is a devDependency in `superset-frontend/package.json` and provides `toHaveStyleRule` for exactly this. The comment misleads maintainers into accepting the mount-only sibling tests as the best possible coverage.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e33986</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5970939697

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5954461928

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4165186128


##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +104,45 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed = formData.comparison_color_scheme === 'Red';
+    formData.increase_color = legacyReversed ? 'Red' : 'Green';
+    formData.decrease_color = legacyReversed ? 'Green' : 'Red';

Review Comment:
   <!-- Bito Reply -->
   The suggestion to use `ColorSchemeEnum` is appropriate. It improves maintainability by ensuring that the migration logic remains synchronized with the render-time fallback, preventing potential discrepancies if the legacy color scheme semantics are updated in the future.
   
   **superset-frontend/src/explore/store.ts**
   ```
   const legacyReversed = formData.comparison_color_scheme === ColorSchemeEnum.Red;
       formData.increase_color = legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green;
       formData.decrease_color = legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red;
   ```



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx:
##########
@@ -0,0 +1,107 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { Button } from '@superset-ui/core/components';
+import {
+  tokenizeToNumericArray,
+  isRangesInputComplete,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow,
+  RangeLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { BulletRangeColorsControlProps } from './types';
+
+/**
+ * Per-range color editor for the Bullet chart. Row *count* is driven by the
+ * sibling `ranges` control (one row per parsed threshold) so range values
+ * keep a single source of truth; this control only owns colors, stored as
+ * an array of hex strings positionally matched to those thresholds. An
+ * empty string entry (or a missing entry past the end of the array) means
+ * "no custom color" -- `Bullet/transformProps.ts` falls back to the default
+ * theme-token band ramp for that range, so existing saved Bullet charts
+ * (which have no `range_colors` at all) render identically to before this
+ * control existed.
+ */
+export default function BulletRangeColorsControl({
+  value,
+  onChange,
+  ranges,
+  ...headerProps
+}: BulletRangeColorsControlProps) {
+  const rangeValues = tokenizeToNumericArray(ranges) ?? [];
+  // While `ranges` is mid-edit (e.g. "20,,60"), tokenizeToNumericArray's
+  // leniency silently compacts it to a shorter list, which would otherwise
+  // shift colors to the wrong positions. Block commits until it's complete
+  // again so no edit can be positionally misaligned once it is.
+  const rangesComplete = isRangesInputComplete(ranges);
+
+  const colorAt = (index: number): string => value?.[index] || '';
+
+  const handleColorChange = (index: number) => (color: ColorPickerValue) => {
+    if (typeof color !== 'string' || !rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, color, colorAt));
+  };
+
+  const handleReset = (index: number) => {
+    if (!rangesComplete) return;
+    onChange?.(replaceColorAtIndex(rangeValues.length, index, '', colorAt));
+  };
+
+  return (
+    <div>
+      <ControlHeader {...headerProps} />
+      {rangeValues.length === 0 ? (
+        <RangeLabel>
+          {t('Add ranges above to configure their colors here.')}
+        </RangeLabel>
+      ) : (
+        rangeValues.map((range, index) => (
+          // eslint-disable-next-line react/no-array-index-key
+          <RangeRow key={index}>

Review Comment:
   <!-- Bito Reply -->
   The update to use `t('Up to %s', range)` is appropriate. This change correctly addresses the issue of fragmented i18n labels by allowing translators to handle the full phrase, including the interpolated value, which improves localization consistency.
   
   **superset-frontend/src/explore/components/controls/BulletRangeColorsControl/index.tsx**
   ```
   rangeValues.map((range, index) => (
             // eslint-disable-next-line react/no-array-index-key
             <RangeRow key={index}>
   ```



##########
superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx:
##########
@@ -0,0 +1,144 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import BulletRangeColorsControl from 'src/explore/components/controls/BulletRangeColorsControl';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no ranges are configured', () => {
+  render(<BulletRangeColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add ranges above to configure their colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed range, unset by default', () => {
+  render(
+    <BulletRangeColorsControl onChange={jest.fn()} ranges="100,200,300" />,
+  );
+
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 200/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 300/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+  // no row has a custom color yet, so no "Use default" reset link shows
+  expect(screen.queryByText('Use default')).not.toBeInTheDocument();
+});
+
+test('shows a "Use default" reset link only for rows with a custom color', () => {
+  render(
+    <BulletRangeColorsControl
+      onChange={jest.fn()}
+      ranges="100,200"
+      value={['#ff0000', '']}
+    />,
+  );
+
+  expect(screen.getAllByText('Use default')).toHaveLength(1);
+});
+
+test('calls onChange with the full color array when a row color is picked', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['', '']}
+    />,
+  );
+
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+
+  await userEvent.click(triggers[0]);
+  await waitFor(() => {
+    expect(document.querySelector('.ant-color-picker')).toBeInTheDocument();
+  });
+
+  const hexInput = document.querySelector<HTMLInputElement>(
+    '.ant-color-picker-input input',
+  );
+  expect(hexInput).toBeInTheDocument();
+  await userEvent.clear(hexInput!);
+  await userEvent.type(hexInput!, '00ff00{enter}');
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalled();
+  });
+  const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
+  expect(lastCall[0].toLowerCase()).toBe('#00ff00');
+  expect(lastCall[1]).toBe('');
+});
+
+test('resets a single row back to "no custom color" without touching the others', async () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="100,200"
+      value={['#ff0000', '#00ff00']}
+    />,
+  );
+
+  const resetButtons = screen.getAllByText('Use default');
+  expect(resetButtons).toHaveLength(2);
+  await userEvent.click(resetButtons[0]);
+
+  await waitFor(() => {
+    expect(onChange).toHaveBeenCalledWith(['', '#00ff00']);
+  });
+});
+
+test('disables editing and resets while ranges has a blank token between numbers', () => {
+  const onChange = jest.fn();
+  render(
+    <BulletRangeColorsControl
+      onChange={onChange}
+      ranges="20,,60"
+      value={['#ff0000', '#00ff00', '#0000ff']}
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // collapse row count or allow commits that would misalign colors once
+  // the blank is filled back in.
+  document.querySelectorAll('.ant-color-picker-trigger').forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+  // AntD's Button always wraps its text child in an inner <span>, so
+  // querying by text here would match that span -- which `toBeDisabled`
+  // never recognizes (it only supports actual form-control elements).
+  // Query by role to get the real <button>, where `disabled` lives.
+  screen.getAllByRole('button', { name: 'Use default' }).forEach(button => {
+    expect(button).toBeDisabled();
+  });
+});

Review Comment:
   <!-- Bito Reply -->
   The added assertion correctly addresses the suggestion to ensure `onChange` is not called when the UI is in a disabled state. This improves test coverage by verifying that the component maintains its expected behavior under restricted conditions.
   
   **superset-frontend/src/explore/components/controls/BulletRangeColorsControl/BulletRangeColorsControl.test.tsx**
   ```
   screen.getAllByRole('button', { name: 'Use default' }).forEach(button => {
       expect(button).toBeDisabled();
     });
     expect(onChange).not.toHaveBeenCalled();
   ```



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4151734208


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts:
##########
@@ -100,21 +125,24 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'comparison_color_scheme',
+            name: 'increase_color',

Review Comment:
   Good catch, fixed by migrating comparison_color_scheme into increase_color/decrease_color in handleDeprecatedControls so it survives the first save instead of vanishing.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx:
##########
@@ -278,15 +279,26 @@ const config: ControlPanelConfig = {
         ],
         [
           {
-            name: 'interval_color_indices',
+            name: 'interval_colors',
             config: {
-              type: 'TextControl',
+              type: 'IntervalColorsControl',
               label: t('Interval colors'),
               description: t(
-                'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+                'Pick a color for each interval band defined above by its upper bound. Charts saved with the legacy 1-indexed "Interval colors" text field are automatically resolved against the chosen color scheme the first time this panel is opened.',
               ),
               renderTrigger: true,
-              default: DEFAULT_FORM_DATA.intervalColorIndices,
+              default: DEFAULT_FORM_DATA.intervalColors,
+              shouldMapStateToProps: () => true,
+              mapStateToProps: (state: ControlPanelState) => ({
+                intervals: state?.controls?.intervals?.value as string,
+                // `interval_color_indices` is no longer a registered control
+                // (replaced by this one), so it never appears under
+                // `state.controls`. Read it from the raw persisted

Review Comment:
   Same fix pattern here, migrated interval_color_indices into interval_colors in handleDeprecatedControls so it's not just a display-only fallback anymore.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #42910:
URL: https://github.com/apache/superset/pull/42910#issuecomment-5926332654

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers an incremental AI Review.
   - `/review full` - Manually triggers a full AI Review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4250508652


##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,126 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, unknown>;
+  const themeValue = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : undefined;
+  const resolvedColor =
+    typeof themeValue === 'string' ? themeValue : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  if (!isEightDigitHex && !isSixDigitHex) {
+    // Non-hex theme tokens (e.g. an antd token resolving to an
+    // `rgb(...)`/`rgba(...)` string) can't take a hex alpha suffix without
+    // producing invalid CSS. Rebuild the background with a low alpha from
+    // the color's own r/g/b components instead, so the pill still gets a
+    // light tint rather than becoming the same color as its own text.
+    const rgbMatch = resolvedColor.match(

Review Comment:
   `increase_color`/`decrease_color` are also control names in Waterfall and Candlestick, where the value is an `{r, g, b, a}` object (the Waterfall default is `{ r: 90, g: 193, b: 137, a: 1 }`). Switching one of those charts to Big Number Period-over-Period in Explore carries the same-named values across, and `resolveComparisonColorKeys` keeps them because they are not nullish. When comparison colors are enabled and the difference is non-zero, `resolvedColor.match(...)` is called on an object and throws, so the chart fails to render. Can the helper normalize or ignore non-string values?



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1185,9 +1185,20 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
+    explicit_colors = form_data.get("interval_colors")
     colors = [
         _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
     ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    if isinstance(explicit_colors, list):
+        # Explicit per-interval colors win; blanks keep the palette fallback.
+        colors = [
+            custom if isinstance(custom, str) and custom else fallback
+            for custom, fallback in zip(

Review Comment:
   The explicit colors are zipped against `colors`, whose length comes from the legacy indices or the 8-entry fallback palette, not from the number of interval bounds. A gauge with nine bounds and nine `interval_colors` (no legacy indices) loses the ninth color, and one with `interval_color_indices: "1,2"` and three explicit colors loses the third. The native chart renders every band, so the preview ends up with fewer bands than the real chart. Should the result be sized by `len(interval_bounds)`, with the fallback palette cycled?



##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +105,50 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Same rationale as
+  // the comparison_color_scheme migration above -- `interval_color_indices`
+  // is no longer a registered control, so it's otherwise dropped on save.
+  if (
+    formData.viz_type === VizType.Gauge &&
+    formData.interval_color_indices &&
+    (!formData.interval_colors || formData.interval_colors.length === 0)
+  ) {
+    const bounds = parseIntervalBounds(formData.intervals);
+    const resolved = resolveLegacyIntervalColors(
+      bounds,
+      formData.interval_color_indices,
+      formData.color_scheme,
+    );
+    if (resolved.some(color => color !== '')) {
+      formData.interval_colors = resolved;

Review Comment:
   This resolves legacy gauge indices against the chart's own `color_scheme` when the form data is hydrated, and the result is stored as literal `interval_colors`. On a dashboard with a color scheme override (`getFormDataWithExtraFilters` sets `color_scheme` from the dashboard after `applyDefaultFormData` runs), a legacy gauge that used to take its interval colors from the dashboard palette will now keep the old chart-level palette colors, since `interval_colors` wins in `getIntervalBoundsAndColors`. Existing dashboards would change color without anyone editing or saving the chart. Should the migration be limited to the Explore edit path, or should the legacy indices stay bound to the effective scheme at render time?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4323642216


##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2799,9 +2834,18 @@ def threshold_details(
                     "x": {"datum": axis_min, "type": "quantitative"},
                     "x2": {"datum": threshold},
                     "y": y_encoding,
-                    "color": legend_color(
-                        (f"{label}: " if label else "")
-                        + f"≤ {_format_bullet_number(model.y_axis_format, threshold)}"
+                    # A custom `range_colors` entry wins; otherwise fall back
+                    # to the default categorical legend coloring exactly as
+                    # before this control existed, so a Bullet chart saved
+                    # without `range_colors` previews unchanged.
+                    "color": (
+                        {"value": color}

Review Comment:
   With a custom color set, the band layer switches to a constant `{"value": color}` encoding, so it no longer contributes a legend entry. For `ranges="50,100"` with both `range_colors` set and `show_legend=True`, the preview legend lists only the metric while Explore still lists both ranges. Should the custom-colored bands keep a nominal legend entry, for example by mapping the label to the color through the encoding's `scale` rather than a constant value?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4309434296


##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;
+      onChange(patchThemeJsonToken(jsonData, key, color));
+    };
+
+  return (
+    <div data-test="theme-color-pickers">
+      {!isValid && (
+        <Typography.Text
+          type="secondary"
+          data-test="theme-color-pickers-invalid-json-notice"
+        >
+          {t(
+            'Fix the JSON errors below to edit colors here; your changes ' +
+              'sync automatically once the JSON is valid again.',
+          )}
+        </Typography.Text>
+      )}
+      <TokenGrid>
+        {CURATED_COLOR_TOKENS.map(({ key, label }) => {
+          const tokenValue = tokenValues[key];
+          return (
+            <TokenRow key={key} data-test={`theme-color-picker-${key}`}>
+              <Typography.Text>{label()}</Typography.Text>
+              <ColorPickerControl
+                ariaLabel={label()}
+                value={typeof tokenValue === 'string' ? tokenValue : undefined}

Review Comment:
   Same root cause as the other toDisplayHex thread on this file, it was resolving SPECIAL_COLORS even when resolveThemeTokens was never set. The same fix covers this too, '\''Red'\'' now just renders as itself here instead of getting matched against the preset.



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -4228,6 +4238,9 @@ def merge_update_form_data(  # noqa: C901
     | FORM_DATA_UPDATE_PRESERVE_KEYS["presentation"]
     | FORM_DATA_UPDATE_PRESERVE_KEYS["time"]
 ) - _get_known_fields(BulletChartConfig)
+# Explore-authored Bullet controls the typed config does not model. They are
+# preserved on update but excluded from the typed validation copy.
+_BULLET_UNMODELED_NATIVE_KEYS = frozenset({"range_colors"})

Review Comment:
   Good point, added a test that merges a metric-only update onto a Bullet saved with range_colors and checks validate_merged_bullet_form_data still succeeds with the colors unchanged. Pulled the key out of the exemption set first to confirm it actually breaks without it, it does, raises on the unknown field.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4253946692


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,98 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import { isRangesInputComplete } from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  const legacyColors = resolveLegacyColors(
+    bounds,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    legacyColors[index] ||

Review Comment:
   Good catch. Once `interval_colors` is populated, the picker now falls back to the scheme's positional color like the renderer does, instead of the legacy indices.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,17 +103,27 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const bands = sortedRanges.map(({ value, label, color }, i) => ({
     name: label,
     itemStyle: {
-      color: bandFills[Math.min(i, bandFills.length - 1)],
+      // A custom `range_colors` entry wins; otherwise fall back to the
+      // default theme-token ramp exactly as before this control existed, so
+      // Bullet charts saved without `range_colors` are unaffected.
+      color: color || bandFills[Math.min(i, bandFills.length - 1)],

Review Comment:
   Fixed: custom fills now get a contrasting label color (light text on dark fills, dark text on light ones); theme-ramp fills keep the default.



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts:
##########
@@ -74,3 +76,126 @@ export const getHeaderFontSize = (proportionValue: number) =>
 export const getComparisonFontSize = (proportionValue: number) =>
   comparisonFontSizesMapping[proportionValue] ??
   sharedFontSizes[sharedFontSizes.length - 1];
+
+export interface ComparisonColorTokens {
+  /** Color for the arrow indicator and (when the symbol is index 0) text. */
+  text: string;
+  /** Background color for the increase/decrease pill. */
+  background: string;
+  /** Foreground color for the increase/decrease pill's text. */
+  strongText: string;
+}
+
+/**
+ * Resolves the increase/decrease colors to use for rendering, given the
+ * chart's current `increaseColor` / `decreaseColor` (from the
+ * `ColorPickerControl`s added after this became customizable) and the
+ * legacy `comparisonColorScheme` field.
+ *
+ * Charts saved before `increaseColor` / `decreaseColor` existed only have
+ * `comparisonColorScheme`, a 2-choice select ('Green' | 'Red') where 'Green'
+ * meant "green for increase, red for decrease" and 'Red' meant the reverse.
+ * Both legacy choices map onto the same 'Green' | 'Red' semantic token names
+ * used by the new controls' presets, so resolving through it here
+ * reproduces the exact old behavior (including the reversed case) without a
+ * data migration.
+ */
+export const resolveComparisonColorKeys = (
+  comparisonColorScheme: string | undefined,
+  increaseColor: string | undefined,
+  decreaseColor: string | undefined,
+): { increaseColor: string; decreaseColor: string } => {
+  const legacyReversed = comparisonColorScheme === ColorSchemeEnum.Red;
+  return {
+    increaseColor:
+      increaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Red : ColorSchemeEnum.Green),
+    decreaseColor:
+      decreaseColor ??
+      (legacyReversed ? ColorSchemeEnum.Green : ColorSchemeEnum.Red),
+  };
+};
+
+/**
+ * Hex alpha suffix appended to a custom comparison color to build the pill
+ * background tint: 0x1A / 0xFF is roughly 10% opacity.
+ */
+export const COMPARISON_TINT_ALPHA_HEX = '1A';
+
+/**
+ * Resolves a single color value (semantic token name or literal hex from
+ * the color picker) to the (arrow/text, background, strong-text) triad used
+ * across the comparison pills. 'Green' / 'Red' keep using the paired
+ * success/error theme tokens exactly as before these colors were
+ * customizable; any other value is either a theme token name (e.g.
+ * 'colorPrimary', emitted by the picker's `resolveThemeTokens` option) or a
+ * literal hex -- 6-digit, or 8-digit when the alpha-enabled picker is used
+ * -- in which case the background is a light (~10% opacity) tint of that
+ * same color.
+ */
+export const getComparisonColorTokens = (
+  colorValue: string,
+  theme: SupersetTheme,
+): ComparisonColorTokens => {
+  if (colorValue === ColorSchemeEnum.Green) {
+    return {
+      text: theme.colorSuccess,
+      background: theme.colorSuccessBg,
+      strongText: theme.colorSuccessText,
+    };
+  }
+  if (colorValue === ColorSchemeEnum.Red) {
+    return {
+      text: theme.colorError,
+      background: theme.colorErrorBg,
+      strongText: theme.colorErrorText,
+    };
+  }
+  const themeColors = theme as unknown as Record<string, unknown>;
+  const themeValue = Object.prototype.hasOwnProperty.call(
+    themeColors,
+    colorValue,
+  )
+    ? themeColors[colorValue]
+    : undefined;
+  const resolvedColor =
+    typeof themeValue === 'string' ? themeValue : colorValue;
+  // An 8-digit hex (alpha-enabled picker) already carries its own alpha
+  // channel; strip it before appending the tint suffix below so the
+  // background stays a valid 8-digit hex instead of stacking a second one.
+  const isEightDigitHex = /^#[0-9a-f]{8}$/i.test(resolvedColor);
+  const isSixDigitHex = /^#[0-9a-f]{6}$/i.test(resolvedColor);
+  if (!isEightDigitHex && !isSixDigitHex) {
+    // Non-hex theme tokens (e.g. an antd token resolving to an
+    // `rgb(...)`/`rgba(...)` string) can't take a hex alpha suffix without
+    // producing invalid CSS. Rebuild the background with a low alpha from
+    // the color's own r/g/b components instead, so the pill still gets a
+    // light tint rather than becoming the same color as its own text.
+    const rgbMatch = resolvedColor.match(

Review Comment:
   Fixed: `resolveComparisonColorKeys` now ignores non-string values, so carried-over Waterfall/Candlestick objects fall back to the Green/Red defaults. Added a test.



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -1185,9 +1185,20 @@ def numeric_bound(field_name: str) -> float | None:
             }
         )
 
+    explicit_colors = form_data.get("interval_colors")
     colors = [
         _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in color_indices
     ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    if isinstance(explicit_colors, list):
+        # Explicit per-interval colors win; blanks keep the palette fallback.
+        colors = [
+            custom if isinstance(custom, str) and custom else fallback
+            for custom, fallback in zip(

Review Comment:
   Fixed: the preview now sizes colors by the number of interval bounds, with explicit colors winning and the palette cycled for the rest. Added tests.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4339319556


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +128,48 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,

Review Comment:
   Good catch @sadpandajoe. `range_colors` is now only applied when `ranges` has user-entered values, so the automatic fallback band keeps the default theme fill. Added a test for it.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4339770598


##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -2773,18 +2809,24 @@ def threshold_details(
         )
 
     layers: list[dict[str, Any]] = []
+    # Custom colors (if any) are authored positionally against the original,
+    # pre-sort `ranges` order via the `range_colors` control -- capture each
+    # range's color here, before the descending sort below reorders them.
+    # Mirrors Bullet/transformProps.ts exactly, so the MCP preview matches
+    # what Explore actually renders for the saved chart.
     range_entries = sorted(
         [
             (
                 threshold,
                 label_at(model.range_labels, index, threshold, "Range"),
+                model.range_colors[index] if index < len(model.range_colors) else "",

Review Comment:
   A Bullet chart whose Ranges were cleared still carries its hidden `range_colors` (the control keeps them on hide), and this line paints the synthesized `[0, measure * 1.1]` band with that leftover color in the MCP preview. For example, `ranges: ""`, `range_colors: ["#ff0000", "#00ff00"]` and a metric result of 100 gives a green automatic band here, while Explore now ignores `range_colors` unless `ranges` has user-entered values (`Bullet/transformProps.ts`) and uses the default theme fill. Should the color lookup apply only when `ranges` was explicitly supplied, so the preview matches the saved chart?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4298535930


##########
superset-frontend/plugins/plugin-chart-echarts/src/Bullet/transformProps.ts:
##########
@@ -102,23 +111,42 @@ export default function transformProps(
     theme.colorFillSecondary,
     theme.colorFill,
   ];
+  // Custom colors (if any) are authored positionally against the original,
+  // pre-sort `ranges` order via the `range_colors` control -- capture each
+  // range's color here, before the descending sort below reorders them.
   const sortedRanges = [...ranges]
-    .map((value, i) => ({ value, label: rangeLabels[i] }))
+    .map((value, i) => ({
+      value,
+      label: rangeLabels[i],
+      color: rangeColors?.[i] || undefined,
+    }))
     .filter(({ value }) => value > axisMin)
     .sort((a, b) => b.value - a.value);
   // Each band's label sits inside its own top-right corner: the visible strip
   // of a nested band ends at its threshold, so the label lands inside the
   // range it names and the rightmost label cannot clip at the chart edge.
-  const bands = sortedRanges.map(({ value, label }, i) => ({
+  const getBandLabelColor = (fill?: string): string => {
+    const luminance = fill ? getColorLuminance(fill) : null;
+    if (luminance === null) return theme.colorTextSecondary;
+    // Fixed light/dark foregrounds, not `theme.colorText`: that token flips
+    // to a light color in dark themes and would vanish on a light custom fill.
+    return luminance < 0.4 ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR;

Review Comment:
   `getColorLuminance` returns WCAG relative luminance, where white and the `#1f1f1f` foreground break even around 0.21, but the cutoff here is 0.4. With Show labels on and a custom band fill of `#999999` (luminance ≈ 0.32) the label is white at about 2.85:1 contrast, versus about 5.8:1 with the dark foreground, so common mid-tone fills get hard-to-read labels. Should the threshold be derived from the contrast ratio against each candidate foreground instead?



##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/legacyColors.ts:
##########
@@ -0,0 +1,64 @@
+/**
+ * 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
+
+export const parseIntervalBounds = (intervals?: string): number[] =>
+  (intervals ?? '')
+    .split(',')
+    .map(part => part.trim())
+    .filter(part => part !== '')
+    .map(Number)
+    .filter(bound => Number.isFinite(bound));
+
+/**
+ * Resolves the legacy `interval_color_indices` control (comma-separated,
+ * 1-indexed positions into the chosen categorical color scheme) into real
+ * hex colors, positionally matched to `bounds`. Shared by the control panel
+ * (display-only fallback while a legacy chart is being edited) and by
+ * `handleDeprecatedControls` (which migrates the value into `interval_colors`
+ * on load, since `interval_color_indices` is no longer a registered control
+ * and would otherwise be silently dropped on the next save).
+ */
+export const resolveLegacyIntervalColors = (
+  bounds: number[],
+  legacyIntervalColorIndices: string | undefined,
+  colorScheme: string | undefined,
+): string[] => {
+  // Non-strict lookup mirrors the Gauge renderer: an unregistered scheme
+  // falls back to the registry's default scheme.
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];

Review Comment:
   This resolves legacy indices against the registry's original palette, but the Gauge renderer indexes `colorFn.colors` after grouped-series labels have already expanded it when `USE_ANALOGOUS_COLORS` is on. For a saved Gauge with `color_scheme: 'echarts5Colors'` (9 colors), ten grouped rows, `intervals: '50,100'` and `interval_color_indices: '10,2'`, the old render used the first generated analogous color for index 10; the migration now wraps modulo 9 to `#5470C6`, and saving persists that wrong band color. Could the indices stay palette-relative until the effective palette is known, or be resolved against the same expanded palette the renderer uses?



##########
superset-frontend/src/features/themes/ThemeColorPickers.tsx:
##########
@@ -0,0 +1,175 @@
+/**
+ * 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 } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { styled } from '@apache-superset/core/theme';
+import { Typography } from '@superset-ui/core/components/Typography';
+import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
+import type { ColorPickerValue } from 'src/explore/components/controls/ColorPickerControl';
+
+/**
+ * Curated antd theme SEED tokens (plus a handful of the most load-bearing
+ * MapToken/AliasToken derivatives) surfaced as individual color pickers, so
+ * the common case -- "change the brand color" -- doesn't require pasting a
+ * hand-edited JSON blob. This intentionally does not attempt to cover the
+ * full antd token surface (100+ tokens): anything not listed here is still
+ * fully editable via the JSON textarea below, which remains the source of
+ * truth. Names are taken directly from antd's own `SeedToken` /
+ * `MapToken` types -- see `antd/es/theme/interface/{seeds,maps/colors}.d.ts`
+ * -- never invented.
+ */
+export const CURATED_COLOR_TOKENS = [
+  { key: 'colorPrimary', label: () => t('Primary') },
+  { key: 'colorSuccess', label: () => t('Success') },
+  { key: 'colorWarning', label: () => t('Warning') },
+  { key: 'colorError', label: () => t('Error') },
+  { key: 'colorInfo', label: () => t('Info') },
+  { key: 'colorLink', label: () => t('Link') },
+  { key: 'colorText', label: () => t('Text') },
+  { key: 'colorTextSecondary', label: () => t('Secondary text') },
+  { key: 'colorBgBase', label: () => t('Base background') },
+  { key: 'colorBgContainer', label: () => t('Container background') },
+  { key: 'colorBorder', label: () => t('Border') },
+] as const;
+
+export type CuratedColorToken = (typeof CURATED_COLOR_TOKENS)[number]['key'];
+
+interface ParsedThemeJson {
+  token?: Record<string, unknown>;
+  [key: string]: unknown;
+}
+
+const isPlainObject = (value: unknown): value is Record<string, unknown> =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+/** Attempts to parse `jsonData` as a theme config object; returns `null`
+ * (never throws) when the JSON is empty, mid-edit, or otherwise invalid --
+ * matching the JSON-parse error handling already used elsewhere in
+ * ThemeModal (`formatJsonData`, `isValidJson`). A `token` that parses but
+ * isn't itself a plain object (a string, array, or number) also counts as
+ * invalid here, since `patchThemeJsonToken` below spreads it -- and
+ * spreading a string or array silently rewrites it into numeric-keyed
+ * junk rather than the token map callers expect. */
+export const tryParseThemeJson = (
+  jsonData: string | undefined,
+): ParsedThemeJson | null => {
+  if (!jsonData?.trim()) return null;
+  try {
+    const parsed = JSON.parse(jsonData);
+    if (!isPlainObject(parsed)) return null;
+    if (
+      'token' in parsed &&
+      parsed.token !== undefined &&
+      !isPlainObject(parsed.token)
+    ) {
+      return null;
+    }
+    return parsed;
+  } catch {
+    return null;
+  }
+};
+
+/** Patches a single curated token into `jsonData`'s `token` object,
+ * preserving every other key (including tokens this UI doesn't curate) and
+ * re-serializing with the same 2-space indent used throughout this modal. */
+export const patchThemeJsonToken = (
+  jsonData: string,
+  key: CuratedColorToken,
+  value: string,
+): string => {
+  const parsed = tryParseThemeJson(jsonData) ?? {};
+  const next = {
+    ...parsed,
+    token: {
+      ...parsed.token,
+      [key]: value,
+    },
+  };
+  return JSON.stringify(next, null, 2);
+};
+
+const TokenGrid = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: ${({ theme }) => theme.sizeUnit * 3}px;
+  margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+const TokenRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+interface ThemeColorPickersProps {
+  jsonData: string;
+  onChange: (nextJsonData: string) => void;
+  disabled?: boolean;
+}
+
+export default function ThemeColorPickers({
+  jsonData,
+  onChange,
+  disabled = false,
+}: ThemeColorPickersProps) {
+  const parsed = useMemo(() => tryParseThemeJson(jsonData), [jsonData]);
+  const isValid = parsed !== null;
+  const tokenValues = parsed?.token ?? {};
+
+  const handleTokenChange =
+    (key: CuratedColorToken) => (color: ColorPickerValue) => {
+      if (typeof color !== 'string' || !isValid || disabled) return;
+      onChange(patchThemeJsonToken(jsonData, key, color));
+    };
+
+  return (
+    <div data-test="theme-color-pickers">
+      {!isValid && (
+        <Typography.Text
+          type="secondary"
+          data-test="theme-color-pickers-invalid-json-notice"
+        >
+          {t(
+            'Fix the JSON errors below to edit colors here; your changes ' +
+              'sync automatically once the JSON is valid again.',
+          )}
+        </Typography.Text>
+      )}
+      <TokenGrid>
+        {CURATED_COLOR_TOKENS.map(({ key, label }) => {
+          const tokenValue = tokenValues[key];
+          return (
+            <TokenRow key={key} data-test={`theme-color-picker-${key}`}>
+              <Typography.Text>{label()}</Typography.Text>
+              <ColorPickerControl
+                ariaLabel={label()}
+                value={typeof tokenValue === 'string' ? tokenValue : undefined}

Review Comment:
   Typing a raw string that happens to match a numeric theme key into the JSON, e.g. `{"token":{"colorPrimary":"fontSize"}}`, passes the `typeof === 'string'` guard here, then `ColorPickerControl.toDisplayHex` resolves it as a theme-token name and calls `.toLowerCase()` on the numeric `theme.fontSize`. That throws during render, before Apply or Save, and takes down the modal. Should the picker value be validated as a color (or token resolution guarded for non-string values) so mid-edit JSON stays editable?



##########
superset-frontend/src/explore/components/controls/ColorPickerControl.test.tsx:
##########
@@ -238,3 +238,10 @@ test('handles undefined theme gracefully', () => {
 
   expect(() => render(<ColorPickerControl {...defaultProps} />)).not.toThrow();
 });
+
+test('trigger is exposed as a focusable button for keyboard users', () => {
+  const { container } = render(<ColorPickerControl {...defaultProps} />);
+  const trigger = container.querySelector('.ant-color-picker-trigger');
+  expect(trigger).toHaveAttribute('tabindex', '0');

Review Comment:
   This test only asserts `tabindex` and `role`, so deleting the new Enter/Space `keydown` handler would leave it green while keyboard users could no longer open the picker. Could it focus the trigger, press Enter and Space and assert the popup opens, and also assert a `disabled` trigger stays closed?



##########
superset-frontend/src/explore/store.test.tsx:
##########
@@ -373,3 +385,108 @@ test('handleDeprecatedControls is idempotent — no-op when matrixify_enable alr
   expect(formData.matrixify_mode_rows).toBe('dimensions');
   expect(formData.matrixify_mode_columns).toBe('metrics');
 });
+
+// ============================================================
+// #42910: legacy color-picker field migrations. `comparison_color_scheme`
+// and `interval_color_indices` are no longer registered controls, so
+// `getFormDataFromControls` drops them on the next save — these migrations
+// carry their value forward into the controls that replaced them before
+// that can happen.
+// ============================================================
+
+test('handleDeprecatedControls migrates comparison_color_scheme Green into increase/decrease colors', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Green',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('Green');
+  expect(formData.decrease_color).toBe('Red');
+});
+
+test('handleDeprecatedControls migrates the reversed comparison_color_scheme Red choice', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Red',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('Red');
+  expect(formData.decrease_color).toBe('Green');
+});
+
+test('handleDeprecatedControls does not override already-set increase/decrease colors', () => {
+  const formData: any = {
+    viz_type: VizType.BigNumberPeriodOverPeriod,
+    comparison_color_scheme: 'Red',
+    increase_color: '#336699',
+  };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBe('#336699');
+  expect(formData.decrease_color).toBeUndefined();
+});
+
+test('handleDeprecatedControls is a no-op for BigNumberPeriodOverPeriod without a legacy scheme', () => {
+  const formData: any = { viz_type: VizType.BigNumberPeriodOverPeriod };
+  handleDeprecatedControls(formData);
+
+  expect(formData.increase_color).toBeUndefined();
+  expect(formData.decrease_color).toBeUndefined();
+});
+
+test('handleDeprecatedControls migrates interval_color_indices into interval_colors', () => {
+  const formData: any = {
+    viz_type: VizType.Gauge,
+    intervals: '20,40,60',
+    interval_color_indices: '2,3',
+    color_scheme: 'testScheme',
+  };
+  handleDeprecatedControls(formData);
+
+  // The uncovered third bound keeps its positional scheme color, matching
+  // what the Gauge renderer drew before the migration.
+  expect(formData.interval_colors).toEqual(['#ff7f0e', '#2ca02c', '#2ca02c']);
+});
+
+test('applyDefaultFormData keeps legacy interval_color_indices unresolved for dashboards', () => {
+  // The dashboard's color_scheme override is applied after hydration, so the
+  // legacy palette-relative indices must not be frozen into literal colors.
+  const result = applyDefaultFormData({
+    viz_type: VizType.Gauge,
+    datasource: '1__table',
+    intervals: '20,40,60',
+    interval_color_indices: '2,3',
+    color_scheme: 'testScheme',
+  } as any);
+
+  expect(result.interval_color_indices).toBe('2,3');
+  expect(result.interval_colors).toEqual([]);

Review Comment:
   This assertion fails as written (CI shows `Expected: []`, `Received: undefined` at this line). The test only registers `test-chart`, so `getAllControlsState` never sees Gauge's control panel and cannot supply the `interval_colors: []` default. Can the real Gauge control panel be registered (and removed afterward) so the hydration case exercises the actual default?



##########
superset-frontend/plugins/plugin-chart-echarts/test/utils/colors.test.ts:
##########
@@ -0,0 +1,48 @@
+/**
+ * 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 { normalizeRgbaColor } from '../../src/utils/colors';
+
+const fallback = { r: 1, g: 2, b: 3, a: 1 };
+
+test('passes through an rgb object', () => {
+  expect(
+    normalizeRgbaColor({ r: 10, g: 20, b: 30, a: 0.5 }, fallback),
+  ).toEqual({ r: 10, g: 20, b: 30, a: 0.5 });
+});
+
+test('parses 6 and 3 digit hex strings', () => {

Review Comment:
   These tests cover `normalizeRgbaColor` in isolation, but not the Waterfall/Candlestick fix itself: the existing transform tests pass RGB objects, so reverting either chart's `normalizeRgbaColor` call would still pass. Could a test feed a carried-over hex value such as `increase_color: '#336699'` through each chart's `transformProps` and assert the generated series color?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4362720626


##########
superset/translations/messages.pot:
##########
@@ -10274,6 +10323,13 @@ msgstr ""
 msgid "Physical (table or view)"
 msgstr ""
 
+msgid ""
+"Pick a color for each interval band defined above by its upper bound. "
+"Charts saved with the legacy 1-indexed \"Interval colors\" text field are"

Review Comment:
   This msgid reads 'text field are keep following', so it no longer matches the Gauge help text in `controlPanel.tsx` and that string has no catalog entry for translators.
   
   ```suggestion
   "Charts saved with the legacy 1-indexed \"Interval colors\" text field"
   ```



##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -163,6 +198,12 @@ export default function ColorPickerControl({
       return customPresets.map(item => ({
         label: item.label,
         colors: item.colors.map(color => {
+          if (resolveThemeTokens && color in SPECIAL_COLORS) {
+            // Share the trigger's lookup so the preset chip matches it.
+            return (
+              toDisplayHex(color as string, themeColors, true) ?? String(color)
+            );
+          }

Review Comment:
   Clicking the Green/Red chip now yields the theme hex, which `handleChange` still doesn't map back, so it stores `'colorSuccess'` instead of `'Green'`. Table/AG Grid trend colors can't be picked and PopKPI loses its paired tokens; compare against `toDisplayHex(key, themeColors, true)` there.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4365587076


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -163,6 +198,12 @@ export default function ColorPickerControl({
       return customPresets.map(item => ({
         label: item.label,
         colors: item.colors.map(color => {
+          if (resolveThemeTokens && color in SPECIAL_COLORS) {
+            // Share the trigger's lookup so the preset chip matches it.
+            return (
+              toDisplayHex(color as string, themeColors, true) ?? String(color)
+            );
+          }

Review Comment:
   Good catch. handleChange now compares against toDisplayHex(key, themeColors, true), so the chip maps back to Green/Red. Updated the FormattingPopoverContent test to match.



##########
superset/translations/messages.pot:
##########
@@ -10274,6 +10323,13 @@ msgstr ""
 msgid "Physical (table or view)"
 msgstr ""
 
+msgid ""
+"Pick a color for each interval band defined above by its upper bound. "
+"Charts saved with the legacy 1-indexed \"Interval colors\" text field are"

Review Comment:
   Fixed the msgid typo in messages.pot so it matches the Gauge help text.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4373045986


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/index.tsx:
##########
@@ -0,0 +1,125 @@
+/**
+ * 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 '@apache-superset/core/translation';
+import { getCategoricalSchemeRegistry } from '@superset-ui/core';
+import {
+  isRangesInputComplete,
+  hasTrailingBlankToken,
+} from '@superset-ui/plugin-chart-echarts';
+import ControlHeader from '../../ControlHeader';
+import ColorPickerControl from '../ColorPickerControl';
+import type { ColorPickerValue } from '../ColorPickerControl';
+import {
+  RangeRow as IntervalRow,
+  RangeLabel as BoundLabel,
+  replaceColorAtIndex,
+} from '../shared/RangeColorRow';
+import { IntervalColorsControlProps } from './types';
+import {
+  parseIntervalBounds as parseBounds,
+  resolveLegacyIntervalColors as resolveLegacyColors,
+} from './legacyColors';
+
+/**
+ * Per-interval color editor for the Gauge chart. Row *count* is driven by
+ * the sibling `intervals` control (one row per parsed upper bound) so bound
+ * values keep a single source of truth; this control only owns colors,
+ * stored as an array of hex strings positionally matched to those bounds.
+ */
+export default function IntervalColorsControl({
+  value,
+  onChange,
+  intervals,
+  legacyIntervalColorIndices,
+  colorScheme,
+  ...headerProps
+}: IntervalColorsControlProps) {
+  const bounds = parseBounds(intervals);
+  // `parseBounds` leniently drops blank tokens (e.g. mid-edit "20,,60"),
+  // which would otherwise shift colors to the wrong bound once the blank is
+  // filled back in -- same hazard `BulletRangeColorsControl` guards against.
+  const boundsComplete = isRangesInputComplete(intervals);
+  // Once `interval_colors` is populated the renderer ignores the legacy
+  // indices and falls back to the scheme's positional color for any missing
+  // entry, so the display must do the same to avoid persisting a different
+  // color into an untouched band.
+  const hasExplicitColors = Array.isArray(value) && value.length > 0;
+  // See the identical guard in `BulletRangeColorsControl`: while `intervals`
+  // ends in the one tolerated trailing blank (the last bound is being
+  // retyped), `bounds` is transiently one entry short. Writing a color at
+  // that moment must not truncate the array to that shorter length, or the
+  // color for the bound being retyped is permanently lost. For a legacy
+  // chart with no explicit colors yet, the legacy indices are the only
+  // record of how many bands there were, so they set that floor too;
+  // otherwise the first color edit in this window would persist a shorter
+  // explicit array, and the bound being retyped would come back with the
+  // positional scheme color instead of its legacy one.
+  const legacyIndexCount = legacyIntervalColorIndices?.trim()
+    ? legacyIntervalColorIndices.split(',').length
+    : 0;
+  const writeLength = hasTrailingBlankToken(intervals)
+    ? Math.max(
+        bounds.length,
+        value?.length ?? 0,
+        hasExplicitColors ? 0 : legacyIndexCount,
+      )
+    : bounds.length;
+  const legacyColors = resolveLegacyColors(
+    writeLength,
+    legacyIntervalColorIndices,
+    colorScheme,
+  );
+  const schemeColors =
+    getCategoricalSchemeRegistry().get(colorScheme)?.colors ?? [];
+  const colorAt = (index: number): string =>
+    value?.[index] ||
+    (hasExplicitColors ? '' : legacyColors[index]) ||
+    schemeColors[index % (schemeColors.length || 1)] ||

Review Comment:
   Good catch @sadpandajoe. When legacy indices can't be resolved against the static palette and there are no explicit colors yet, the control now stays read-only, so editing a row can no longer rewrite an untouched band. Added a test for it.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4373816497


##########
superset-frontend/src/explore/components/controls/IntervalColorsControl/IntervalColorsControl.test.tsx:
##########
@@ -0,0 +1,350 @@
+/**
+ * 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 {
+  render,
+  screen,
+  userEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import {
+  CategoricalScheme,
+  getCategoricalSchemeRegistry,
+} from '@superset-ui/core';
+import IntervalColorsControl from 'src/explore/components/controls/IntervalColorsControl';
+
+beforeAll(() => {
+  getCategoricalSchemeRegistry().registerValue(
+    'testScheme',
+    new CategoricalScheme({
+      id: 'testScheme',
+      colors: ['#1f77b4', '#ff7f0e', '#2ca02c'],
+    }),
+  );
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('renders a hint and no rows when no interval bounds are configured', () => {
+  render(<IntervalColorsControl onChange={jest.fn()} />);
+
+  expect(
+    screen.getByText('Add interval bounds above to configure colors here.'),
+  ).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    0,
+  );
+});
+
+test('renders one color picker row per parsed interval bound', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60,100"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText(/Up to 20/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 60/)).toBeInTheDocument();
+  expect(screen.getByText(/Up to 100/)).toBeInTheDocument();
+  expect(document.querySelectorAll('.ant-color-picker-trigger')).toHaveLength(
+    3,
+  );
+});
+
+test('falls back to the categorical scheme when there is no explicit value or legacy data', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+});
+
+test('resolves legacy interval_color_indices against the color scheme for display', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // 1-indexed: index 2 -> '#ff7f0e', index 3 -> '#2ca02c'
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.getByText('#2CA02C')).toBeInTheDocument();
+});
+
+test('disables editing while intervals has a blank token between numbers', async () => {
+  const onChange = jest.fn();
+  render(
+    <IntervalColorsControl
+      onChange={onChange}
+      intervals="20,,60"
+      colorScheme="testScheme"
+    />,
+  );
+
+  // The blank middle token means the list is still being edited; don't
+  // allow commits that would misalign colors once the blank is filled in.
+  // Pin the row count first so the per-row assertions can't pass vacuously.
+  const triggers = document.querySelectorAll('.ant-color-picker-trigger');
+  expect(triggers).toHaveLength(2);
+  triggers.forEach(trigger => {
+    expect(trigger).toHaveClass('ant-color-picker-trigger-disabled');
+  });
+
+  // A change attempt on a disabled picker must neither open it nor commit.
+  await userEvent.click(triggers[0]);
+  expect(
+    document.querySelector('.ant-color-picker-presets-color'),
+  ).not.toBeInTheDocument();
+  expect(onChange).not.toHaveBeenCalled();
+});
+
+test('explicit value takes precedence over legacy indices', () => {
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      value={['#ff0000', '#00ff00']}
+      legacyIntervalColorIndices="2,3"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#FF0000')).toBeInTheDocument();
+  expect(screen.getByText('#00FF00')).toBeInTheDocument();
+});
+
+test('a non-positive legacy index falls back to the positional scheme color like the renderer', () => {
+  // The renderer computes `colors[(ind - 1) % n]`, which is `undefined` for
+  // index 0, so that band uses `colors[idx]`; it must not wrap to the last
+  // scheme color.
+  render(
+    <IntervalColorsControl
+      onChange={jest.fn()}
+      intervals="20,60"
+      legacyIntervalColorIndices="0,2"
+      colorScheme="testScheme"
+    />,
+  );
+
+  expect(screen.getByText('#1F77B4')).toBeInTheDocument();
+  expect(screen.getByText('#FF7F0E')).toBeInTheDocument();
+  expect(screen.queryByText('#2CA02C')).not.toBeInTheDocument();
+});
+
+test('stays read-only when legacy indices cannot be resolved so untouched bands are never rewritten', async () => {

Review Comment:
   Added: `value={["#ff0000"]}` with legacy indices `10,3` now asserts the pickers stay enabled and a preset pick fires `onChange`, so dropping `!hasExplicitColors` would fail it.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(color-picker): consolidate remaining legacy color selectors onto ColorPickerControl [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #42910:
URL: https://github.com/apache/superset/pull/42910#discussion_r4391045509


##########
superset-frontend/src/explore/components/controls/ColorPickerControl.tsx:
##########
@@ -193,10 +234,21 @@ export default function ColorPickerControl({
     const rgb = color.toRgb();
     const hex = rgbaToHex(rgb).toLowerCase();
 
+    // Match against the same hex the preset chip and trigger display, so a
+    // special color picked from a theme-resolved chip maps back to its key.
+    // Only keys the caller actually offered as presets are candidates;
+    // otherwise an ordinary theme swatch that shares a hex with Green/Red
+    // (e.g. `colorSuccess`) would be misread as the special key.
+    const offeredSpecialKeys = new Set(
+      (customPresets ?? []).flatMap(item =>
+        item.colors.filter(color => color in SPECIAL_COLORS),
+      ),
+    );
     const specialEntry = resolveThemeTokens
-      ? Object.entries(SPECIAL_COLORS).find(
-          ([, rgba]) => rgbaToHex(rgba).toLowerCase() === hex,
-        )
+      ? Object.keys(SPECIAL_COLORS)
+          .filter(key => offeredSpecialKeys.has(key))
+          .map(key => [key, toDisplayHex(key, themeColors, true)] as const)

Review Comment:
   With a theme whose `colorSuccess` is a non-hex string (e.g. `rgb(82, 196, 26)`), picking the Green chip stores `colorSuccess` instead of `Green`. `toDisplayHex('Green', ...)` returns the raw lower-cased `rgb(...)` token while `hex` here is `#52c41a`, so the `.find` never matches and the `reverseMap` branch takes over. In Table time comparison that turns a trend rule into ordinary conditional formatting. Could the special-key comparison normalize both sides to hex the way `reverseMap` does?



##########
superset-frontend/src/explore/store.ts:
##########
@@ -89,6 +110,60 @@ export function handleDeprecatedControls(formData: FormData): void {
       formData.matrixify_mode_columns = 'disabled';
     }
   }
+
+  // #42910: migrate the legacy BigNumberPeriodOverPeriod
+  // `comparison_color_scheme` ('Green' | 'Red', where 'Red' reverses
+  // increase/decrease colors) into the `increase_color` / `decrease_color`
+  // ColorPickerControls that replaced it. `comparison_color_scheme` is no
+  // longer a registered control, so `getFormDataFromControls` drops it the
+  // next time the chart is saved -- without this migration, that silently
+  // discards a reversed-color choice the first time an old chart is resaved.
+  if (
+    formData.viz_type === VizType.BigNumberPeriodOverPeriod &&
+    formData.comparison_color_scheme &&
+    formData.increase_color === undefined &&
+    formData.decrease_color === undefined
+  ) {
+    const legacyReversed =
+      formData.comparison_color_scheme === ColorSchemeEnum.Red;
+    formData.increase_color = legacyReversed
+      ? ColorSchemeEnum.Red
+      : ColorSchemeEnum.Green;
+    formData.decrease_color = legacyReversed
+      ? ColorSchemeEnum.Green
+      : ColorSchemeEnum.Red;
+  }
+
+  // #42910: on request, migrate the legacy Gauge `interval_color_indices`
+  // (comma-separated, 1-indexed positions into `color_scheme`) into real hex
+  // colors stored by `interval_colors`, which replaced it. Defaults to off,
+  // unlike the comparison_color_scheme migration above: the Gauge control
+  // panel keeps `interval_color_indices` registered as a hidden control, so
+  // Explore (which builds the chart's query and save form data from
+  // registered controls) still hands an untouched legacy string to the
+  // renderer and keeps it on save, where it stays palette-relative. Eagerly
+  // freezing it into literal colors merely from opening the chart would
+  // silently break a dashboard override of this chart's `color_scheme`
+  // applied afterward -- the exact hazard the dashboard hydration path
+  // below was already written to avoid. `IntervalColorsControl` shows the
+  // resolved colors for display on its own (see its
+  // `legacyIntervalColorIndices` prop), and only the user's first edit of
+  // an interval color makes them literal.
+  if (
+    resolveLegacyGaugeColors &&

Review Comment:
   `resolveLegacyGaugeColors` defaults to `false`, and every call site either omits it or passes `false`, so this branch (and the `resolveLegacyIntervalColors`/`parseIntervalBounds` imports it pulls into the store) only runs under tests. Is there a caller meant to opt in, or can the option and its dedicated tests be dropped?



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org