Compare commits

...

2 Commits

Author SHA1 Message Date
Evan Rusackas
f62b02da27 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-06 10:42:34 -07:00
Claude Code
907a62269e refactor: remove the deprecated AVOID_COLORS_COLLISION feature flag
The flag has defaulted to True and been marked deprecated; the
color-collision avoidance in CategoricalColorScale is now permanently
enabled. Removes the flag from config.py (docs feature-flags.json
regenerated), the test config override, the frontend FeatureFlag enum,
and both gated branches in getColor.

Tests: flag-off cases are removed or rewritten for the always-on
behavior, the two getNextAvailableColor tests seed usage counts
directly (getColor itself now redistributes collisions during setup),
and the suite-level beforeEach resets the labels color map singleton so
dashboard-sourced tests can no longer leak state between tests, which
the flag previously masked.

Closes #41044

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:32:10 -07:00
7 changed files with 31 additions and 73 deletions

View File

@@ -24,6 +24,8 @@ assists people when migrating to a new version.
## Next
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
- [39925](https://github.com/apache/superset/pull/39925): URL prefixing for `SUPERSET_APP_ROOT` subdirectory deployments is now handled automatically by helpers in `src/utils/navigationUtils` (`openInNewTab`, `redirect`, `getShareableUrl`, `<AppLink>`). Direct imports of `ensureAppRoot` / `makeUrl` from `src/utils/pathUtils` are forbidden outside `navigationUtils.ts` (enforced by a static-invariant test); contributors writing new code should use the focused helpers instead. No runtime behaviour change for existing callers — all 19 prior call sites have been migrated and four pre-existing double-prefix and missing-prefix bugs are fixed as part of the migration.
- [39925](https://github.com/apache/superset/pull/39925): `SupersetClient.getUrl()` now strips a single leading application-root segment from the supplied `endpoint` before building the request URL, so a caller that accidentally pre-prefixes its endpoint (for example by wrapping it with `ensureAppRoot` before passing it to the client) no longer produces a doubled `/superset/superset/...` URL under subdirectory deployment. The strip is **single-pass** — a genuine `/superset/superset/<slug>` route is preserved, not collapsed — and **silent** (no console warning); the static-invariant test remains the primary signal for pre-prefixing at the call site, and this runtime strip is a safety net beneath it. Code that intentionally targeted a literal `/<app_root>/<app_root>/...` endpoint through `getUrl` (a configuration that has no legitimate use under the prefixing model) would have its first redundant segment removed.

View File

@@ -401,12 +401,6 @@
}
],
"deprecated": [
{
"name": "AVOID_COLORS_COLLISION",
"default": true,
"lifecycle": "deprecated",
"description": "Avoid color collisions in charts by using distinct colors"
},
{
"name": "DRILL_TO_DETAIL",
"default": true,

View File

@@ -154,18 +154,13 @@ class CategoricalColorScale extends ExtensibleFunction {
this.incrementColorRange();
}
if (
// feature flag to be deprecated (will become standard behaviour)
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
this.isColorUsed(color)
) {
if (this.isColorUsed(color)) {
// fallback to least used color
color = this.getNextAvailableColor(cleanedValue, color);
}
}
if (
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
source === LabelsColorMapSource.Dashboard &&
(forcedColor || isExistingLabel)
) {

View File

@@ -29,7 +29,6 @@ export enum FeatureFlag {
AlertReportWebhook = 'ALERT_REPORT_WEBHOOK',
AlertReportsFilter = 'ALERT_REPORTS_FILTER',
AllowFullCsvExport = 'ALLOW_FULL_CSV_EXPORT',
AvoidColorsCollision = 'AVOID_COLORS_COLLISION',
ChartPluginsExperimental = 'CHART_PLUGINS_EXPERIMENTAL',
ConfirmDashboardDiff = 'CONFIRM_DASHBOARD_DIFF',
CssTemplates = 'CSS_TEMPLATES',

View File

@@ -28,6 +28,11 @@ import {
describe('CategoricalColorScale', () => {
beforeEach(() => {
window.featureFlags = {};
// the labels color map is a singleton: start every test from a clean
// explore-sourced state so dashboard-mode tests cannot leak into others
const labelsColorMap = getLabelsColorMap();
labelsColorMap.reset();
labelsColorMap.source = LabelsColorMapSource.Explore;
});
test('exists', () => {
@@ -159,9 +164,14 @@ describe('CategoricalColorScale', () => {
colorSet[color] = 1;
}
});
// collision avoidance redistributes recycled colors, so counts are
// not uniform; every color from the palette is still used and no
// color outside the palette appears
expect(Object.keys(colorSet)).toHaveLength(3);
const total = Object.values(colorSet).reduce((sum, n) => sum + n, 0);
expect(total).toBe(6);
['blue', 'red', 'green'].forEach(color => {
expect(colorSet[color]).toBe(2);
expect(colorSet[color]).toBeGreaterThanOrEqual(1);
});
});
test('get analogous colors when number of items exceed available colors', () => {
@@ -201,9 +211,6 @@ describe('CategoricalColorScale', () => {
expect(returnedColor).toBe(expectedColor);
});
test('reassigns colliding colors when no sliceId is provided', () => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: true,
};
const PALETTE = ['red', 'blue', 'green'];
const chartAScale = new CategoricalColorScale(PALETTE);
@@ -228,13 +235,10 @@ describe('CategoricalColorScale', () => {
expect(classicCarsColor).not.toBe('red');
} finally {
labelsColorMap.reset();
labelsColorMap.source = LabelsColorMapSource.Dashboard;
labelsColorMap.source = LabelsColorMapSource.Explore;
}
});
test('conditionally calls getNextAvailableColor', () => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: true,
};
test('calls getNextAvailableColor for new labels with used colors', () => {
scale.labelsColorMapInstance.source = LabelsColorMapSource.Explore;
scale.getColor('testValue1');
@@ -250,19 +254,12 @@ describe('CategoricalColorScale', () => {
getNextAvailableColorSpy.mockClear();
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: false,
};
// existing labels reuse their assigned color without reassignment
scale.getColor('testValue3');
expect(getNextAvailableColorSpy).not.toHaveBeenCalled();
});
test('reassigns non-forced labels when a dashboard-synced label would duplicate their color', () => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: true,
};
const dashScale = new CategoricalColorScale(['red', 'blue', 'green']);
const sliceId = 501;
const colorScheme = 'preset';
@@ -391,11 +388,12 @@ describe('CategoricalColorScale', () => {
test('returns the least used color among all', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
scale.getColor('cat'); // blue
scale.getColor('dog'); // red
scale.getColor('fish'); // green
scale.getColor('puppy'); // blue
scale.getColor('teddy'); // red
// seed usage directly: getColor would itself redistribute collisions
scale.chartLabelsColorMap.set('cat', 'blue');
scale.chartLabelsColorMap.set('dog', 'red');
scale.chartLabelsColorMap.set('fish', 'green');
scale.chartLabelsColorMap.set('puppy', 'blue');
scale.chartLabelsColorMap.set('teddy', 'red');
// All colors used, so the function should return least used
expect(scale.getNextAvailableColor('darling', 'red')).toBe('green');
});
@@ -407,13 +405,14 @@ describe('CategoricalColorScale', () => {
'green',
'yellow',
]);
scale.getColor('cat'); // blue
scale.getColor('dog'); // red
scale.getColor('frog'); // green
scale.getColor('fish'); // yellow
scale.getColor('goat'); // blue
scale.getColor('horse'); // red
scale.getColor('pony'); // green
// seed usage directly: getColor would itself redistribute collisions
scale.chartLabelsColorMap.set('cat', 'blue');
scale.chartLabelsColorMap.set('dog', 'red');
scale.chartLabelsColorMap.set('frog', 'green');
scale.chartLabelsColorMap.set('fish', 'yellow');
scale.chartLabelsColorMap.set('goat', 'blue');
scale.chartLabelsColorMap.set('horse', 'red');
scale.chartLabelsColorMap.set('pony', 'green');
// Yellow is the least used color, so it should be returned.
expect(scale.getNextAvailableColor('pony', 'blue')).toBe('yellow');
@@ -537,9 +536,6 @@ describe('CategoricalColorScale', () => {
let labelsColorMap: ReturnType<typeof getLabelsColorMap>;
beforeEach(() => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: true,
};
const sentinel = new CategoricalColorScale(['red', 'blue', 'green']);
labelsColorMap = sentinel.labelsColorMapInstance;
labelsColorMap.reset();
@@ -551,29 +547,6 @@ describe('CategoricalColorScale', () => {
labelsColorMap.reset();
});
test('reproduces the bug without the fix: Classic Cars and Trains would both be red', () => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: false,
};
const PALETTE = ['red', 'blue', 'green'];
const chartAScale = new CategoricalColorScale(PALETTE);
chartAScale.getColor('Trains', 101, 'testScheme');
expect(labelsColorMap.getColorMap().get('Trains')).toBe('red');
const chartBScale = new CategoricalColorScale(PALETTE);
chartBScale.getColor('Classic Cars', 102, 'testScheme');
chartBScale.getColor('Trains', 102, 'testScheme');
const classicCarsColor =
chartBScale.chartLabelsColorMap.get('Classic Cars');
const trainsColor = chartBScale.chartLabelsColorMap.get('Trains');
expect(trainsColor).toBe('red');
expect(classicCarsColor).toBe('red');
});
test('fix: Classic Cars is reassigned when Trains locks red from the dashboard', () => {
const PALETTE = ['red', 'blue', 'green'];
@@ -612,7 +585,6 @@ describe('CategoricalColorScale', () => {
test('fix: increments analogous color range for dashboard collisions when UseAnalogousColors is enabled', () => {
window.featureFlags = {
[FeatureFlag.AvoidColorsCollision]: true,
[FeatureFlag.UseAnalogousColors]: true,
};

View File

@@ -905,9 +905,6 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
# These flags default to True and will be removed in a future major
# release. Set to True in your config to avoid unexpected changes.
# -----------------------------------------------------------------
# Avoid color collisions in charts by using distinct colors
# @lifecycle: deprecated
"AVOID_COLORS_COLLISION": True,
# Enable drill-to-detail functionality in charts
# @lifecycle: deprecated
"DRILL_TO_DETAIL": True,

View File

@@ -70,7 +70,6 @@ FEATURE_FLAGS = {
"foo": "bar",
"ENABLE_TEMPLATE_PROCESSING": True,
"ALERT_REPORTS": True,
"AVOID_COLORS_COLLISION": True,
"DRILL_TO_DETAIL": True,
"DRILL_BY": True,
"GLOBAL_TASK_FRAMEWORK": True,