mirror of
https://github.com/apache/superset.git
synced 2026-07-08 15:55:33 +00:00
Compare commits
2 Commits
fix-databa
...
avoid-colo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f62b02da27 | ||
|
|
907a62269e |
@@ -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.
|
||||
|
||||
6
docs/static/feature-flags.json
vendored
6
docs/static/feature-flags.json
vendored
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user