feat(color): color consistency enhancements (#21507)
This commit is contained in:
parent
7ec136fec2
commit
7a7181a244
|
|
@ -51,7 +51,7 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||
* @param {*} parentForcedColors optional parameter that comes from parent
|
||||
* (usually CategoricalColorNamespace) and supersede this.forcedColors
|
||||
*/
|
||||
constructor(colors: string[], parentForcedColors?: ColorsLookup) {
|
||||
constructor(colors: string[], parentForcedColors: ColorsLookup = {}) {
|
||||
super((value: string, sliceId?: number) => this.getColor(value, sliceId));
|
||||
|
||||
this.originColors = colors;
|
||||
|
|
@ -67,17 +67,11 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||
const cleanedValue = stringifyAndTrim(value);
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
|
||||
const parentColor = this.parentForcedColors?.[cleanedValue];
|
||||
if (parentColor) {
|
||||
sharedLabelColor.addSlice(cleanedValue, parentColor, sliceId);
|
||||
return parentColor;
|
||||
}
|
||||
|
||||
const forcedColor = this.forcedColors[cleanedValue];
|
||||
if (forcedColor) {
|
||||
sharedLabelColor.addSlice(cleanedValue, forcedColor, sliceId);
|
||||
return forcedColor;
|
||||
}
|
||||
// priority: parentForcedColors > forcedColors > labelColors
|
||||
let color =
|
||||
this.parentForcedColors?.[cleanedValue] ||
|
||||
this.forcedColors?.[cleanedValue] ||
|
||||
sharedLabelColor.getColorMap().get(cleanedValue);
|
||||
|
||||
if (isFeatureEnabled(FeatureFlag.USE_ANALAGOUS_COLORS)) {
|
||||
const multiple = Math.floor(
|
||||
|
|
@ -89,8 +83,10 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||
this.range(this.originColors.concat(newRange));
|
||||
}
|
||||
}
|
||||
|
||||
const color = this.scale(cleanedValue);
|
||||
const newColor = this.scale(cleanedValue);
|
||||
if (!color) {
|
||||
color = newColor;
|
||||
}
|
||||
sharedLabelColor.addSlice(cleanedValue, color, sliceId);
|
||||
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -18,113 +18,81 @@
|
|||
*/
|
||||
|
||||
import { CategoricalColorNamespace } from '.';
|
||||
import { FeatureFlag, isFeatureEnabled, makeSingleton } from '../utils';
|
||||
import { getAnalogousColors } from './utils';
|
||||
import { makeSingleton } from '../utils';
|
||||
|
||||
export enum SharedLabelColorSource {
|
||||
dashboard,
|
||||
explore,
|
||||
}
|
||||
export class SharedLabelColor {
|
||||
sliceLabelColorMap: Record<number, Record<string, string | undefined>>;
|
||||
sliceLabelMap: Map<number, string[]>;
|
||||
|
||||
colorMap: Map<string, string>;
|
||||
|
||||
source: SharedLabelColorSource;
|
||||
|
||||
constructor() {
|
||||
// { sliceId1: { label1: color1 }, sliceId2: { label2: color2 } }
|
||||
this.sliceLabelColorMap = {};
|
||||
// { sliceId1: [label1, label2, ...], sliceId2: [label1, label2, ...] }
|
||||
this.sliceLabelMap = new Map();
|
||||
this.colorMap = new Map();
|
||||
this.source = SharedLabelColorSource.dashboard;
|
||||
}
|
||||
|
||||
getColorMap(
|
||||
colorNamespace?: string,
|
||||
colorScheme?: string,
|
||||
updateColorScheme?: boolean,
|
||||
) {
|
||||
if (colorScheme) {
|
||||
const categoricalNamespace =
|
||||
CategoricalColorNamespace.getNamespace(colorNamespace);
|
||||
const sharedLabels = this.getSharedLabels();
|
||||
let generatedColors: string[] = [];
|
||||
let sharedLabelMap;
|
||||
updateColorMap(colorNamespace?: string, colorScheme?: string) {
|
||||
const categoricalNamespace =
|
||||
CategoricalColorNamespace.getNamespace(colorNamespace);
|
||||
const newColorMap = new Map();
|
||||
this.colorMap.clear();
|
||||
this.sliceLabelMap.forEach(labels => {
|
||||
const colorScale = categoricalNamespace.getScale(colorScheme);
|
||||
labels.forEach(label => {
|
||||
const newColor = colorScale(label);
|
||||
newColorMap.set(label, newColor);
|
||||
});
|
||||
});
|
||||
this.colorMap = newColorMap;
|
||||
}
|
||||
|
||||
if (sharedLabels.length) {
|
||||
const colorScale = categoricalNamespace.getScale(colorScheme);
|
||||
const colors = colorScale.range();
|
||||
if (isFeatureEnabled(FeatureFlag.USE_ANALAGOUS_COLORS)) {
|
||||
const multiple = Math.ceil(sharedLabels.length / colors.length);
|
||||
generatedColors = getAnalogousColors(colors, multiple);
|
||||
sharedLabelMap = sharedLabels.reduce(
|
||||
(res, label, index) => ({
|
||||
...res,
|
||||
[label.toString()]: generatedColors[index],
|
||||
}),
|
||||
{},
|
||||
);
|
||||
} else {
|
||||
// reverse colors to reduce color conflicts
|
||||
colorScale.range(colors.reverse());
|
||||
sharedLabelMap = sharedLabels.reduce(
|
||||
(res, label) => ({
|
||||
...res,
|
||||
[label.toString()]: colorScale(label),
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const labelMap = Object.keys(this.sliceLabelColorMap).reduce(
|
||||
(res, sliceId) => {
|
||||
// get new color scale instance
|
||||
const colorScale = categoricalNamespace.getScale(colorScheme);
|
||||
return {
|
||||
...res,
|
||||
...Object.keys(this.sliceLabelColorMap[sliceId]).reduce(
|
||||
(res, label) => ({
|
||||
...res,
|
||||
[label]: updateColorScheme
|
||||
? colorScale(label)
|
||||
: this.sliceLabelColorMap[sliceId][label],
|
||||
}),
|
||||
{},
|
||||
),
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
...labelMap,
|
||||
...sharedLabelMap,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
getColorMap() {
|
||||
return this.colorMap;
|
||||
}
|
||||
|
||||
addSlice(label: string, color: string, sliceId?: number) {
|
||||
if (!sliceId) return;
|
||||
this.sliceLabelColorMap[sliceId] = {
|
||||
...this.sliceLabelColorMap[sliceId],
|
||||
[label]: color,
|
||||
};
|
||||
if (
|
||||
this.source !== SharedLabelColorSource.dashboard ||
|
||||
sliceId === undefined
|
||||
)
|
||||
return;
|
||||
const labels = this.sliceLabelMap.get(sliceId) || [];
|
||||
if (!labels.includes(label)) {
|
||||
labels.push(label);
|
||||
this.sliceLabelMap.set(sliceId, labels);
|
||||
}
|
||||
this.colorMap.set(label, color);
|
||||
}
|
||||
|
||||
removeSlice(sliceId: number) {
|
||||
delete this.sliceLabelColorMap[sliceId];
|
||||
if (this.source !== SharedLabelColorSource.dashboard) return;
|
||||
this.sliceLabelMap.delete(sliceId);
|
||||
const newColorMap = new Map();
|
||||
this.sliceLabelMap.forEach(labels => {
|
||||
labels.forEach(label => {
|
||||
newColorMap.set(label, this.colorMap.get(label));
|
||||
});
|
||||
});
|
||||
this.colorMap = newColorMap;
|
||||
}
|
||||
|
||||
reset() {
|
||||
const copyColorMap = new Map(this.colorMap);
|
||||
copyColorMap.forEach((_, label) => {
|
||||
this.colorMap.set(label, '');
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.sliceLabelColorMap = {};
|
||||
}
|
||||
|
||||
getSharedLabels() {
|
||||
const tempLabels = new Set<string>();
|
||||
const result = new Set<string>();
|
||||
Object.keys(this.sliceLabelColorMap).forEach(sliceId => {
|
||||
const colorMap = this.sliceLabelColorMap[sliceId];
|
||||
Object.keys(colorMap).forEach(label => {
|
||||
if (tempLabels.has(label) && !result.has(label)) {
|
||||
result.add(label);
|
||||
} else {
|
||||
tempLabels.add(label);
|
||||
}
|
||||
});
|
||||
});
|
||||
return [...result];
|
||||
this.sliceLabelMap.clear();
|
||||
this.colorMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export * from './utils';
|
|||
export {
|
||||
default as getSharedLabelColor,
|
||||
SharedLabelColor,
|
||||
SharedLabelColorSource,
|
||||
} from './SharedLabelColorSingleton';
|
||||
|
||||
export const BRAND_COLOR = '#00A699';
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ describe('CategoricalColorScale', () => {
|
|||
expect(scale2.getColorMap()).toEqual({
|
||||
cow: 'black',
|
||||
pig: 'pink',
|
||||
horse: 'blue',
|
||||
horse: 'green',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
getCategoricalSchemeRegistry,
|
||||
getSharedLabelColor,
|
||||
SharedLabelColor,
|
||||
SharedLabelColorSource,
|
||||
} from '@superset-ui/core';
|
||||
import { getAnalogousColors } from '../../src/color/utils';
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ describe('SharedLabelColor', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
getSharedLabelColor().source = SharedLabelColorSource.dashboard;
|
||||
getSharedLabelColor().clear();
|
||||
});
|
||||
|
||||
|
|
@ -60,18 +62,48 @@ describe('SharedLabelColor', () => {
|
|||
});
|
||||
|
||||
describe('.addSlice(value, color, sliceId)', () => {
|
||||
it('should add to valueSliceMap when first adding label', () => {
|
||||
it('should add to sliceLabelColorMap when first adding label', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
expect(sharedLabelColor.sliceLabelColorMap).toHaveProperty('1', {
|
||||
a: 'red',
|
||||
});
|
||||
expect(sharedLabelColor.sliceLabelMap.has(1)).toEqual(true);
|
||||
const labels = sharedLabelColor.sliceLabelMap.get(1);
|
||||
expect(labels?.includes('a')).toEqual(true);
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ a: 'red' });
|
||||
});
|
||||
|
||||
it('should add to sliceLabelColorMap when slice exist', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 1);
|
||||
const labels = sharedLabelColor.sliceLabelMap.get(1);
|
||||
expect(labels?.includes('b')).toEqual(true);
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ a: 'red', b: 'blue' });
|
||||
});
|
||||
|
||||
it('should use last color if adding label repeatedly', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('b', 'blue', 1);
|
||||
sharedLabelColor.addSlice('b', 'green', 1);
|
||||
const labels = sharedLabelColor.sliceLabelMap.get(1);
|
||||
expect(labels?.includes('b')).toEqual(true);
|
||||
expect(labels?.length).toEqual(1);
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ b: 'green' });
|
||||
});
|
||||
|
||||
it('should do nothing when source is not dashboard', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.source = SharedLabelColorSource.explore;
|
||||
sharedLabelColor.addSlice('a', 'red');
|
||||
expect(Object.fromEntries(sharedLabelColor.sliceLabelMap)).toEqual({});
|
||||
});
|
||||
|
||||
it('should do nothing when sliceId is undefined', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red');
|
||||
expect(sharedLabelColor.sliceLabelColorMap).toEqual({});
|
||||
expect(Object.fromEntries(sharedLabelColor.sliceLabelMap)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -80,55 +112,92 @@ describe('SharedLabelColor', () => {
|
|||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.removeSlice(1);
|
||||
expect(sharedLabelColor.sliceLabelColorMap).toEqual({});
|
||||
expect(sharedLabelColor.sliceLabelMap.has(1)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should update colorMap', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
sharedLabelColor.removeSlice(1);
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ b: 'blue' });
|
||||
});
|
||||
|
||||
it('should do nothing when source is not dashboard', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.source = SharedLabelColorSource.explore;
|
||||
sharedLabelColor.removeSlice(1);
|
||||
expect(sharedLabelColor.sliceLabelMap.has(1)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getColorMap(namespace, scheme, updateColorScheme)', () => {
|
||||
it('should be undefined when scheme is undefined', () => {
|
||||
describe('.updateColorMap(namespace, scheme)', () => {
|
||||
it('should update color map', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'pink', 1);
|
||||
sharedLabelColor.addSlice('b', 'green', 2);
|
||||
sharedLabelColor.addSlice('c', 'blue', 2);
|
||||
sharedLabelColor.updateColorMap('', 'testColors2');
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(colorMap).toBeUndefined();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({
|
||||
a: 'yellow',
|
||||
b: 'yellow',
|
||||
c: 'green',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update color value if passing updateColorScheme', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
const colorMap = sharedLabelColor.getColorMap('', 'testColors2', true);
|
||||
expect(colorMap).toEqual({ a: 'yellow', b: 'yellow' });
|
||||
});
|
||||
|
||||
it('should get origin color value if not pass updateColorScheme', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
const colorMap = sharedLabelColor.getColorMap('', 'testColors');
|
||||
expect(colorMap).toEqual({ a: 'red', b: 'blue' });
|
||||
});
|
||||
|
||||
it('should use recycle colors if shared label exit', () => {
|
||||
it('should use recycle colors', () => {
|
||||
window.featureFlags = {
|
||||
[FeatureFlag.USE_ANALAGOUS_COLORS]: false,
|
||||
};
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('a', 'blue', 2);
|
||||
const colorMap = sharedLabelColor.getColorMap('', 'testColors');
|
||||
expect(colorMap).not.toEqual({});
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
sharedLabelColor.addSlice('c', 'green', 3);
|
||||
sharedLabelColor.addSlice('d', 'red', 4);
|
||||
sharedLabelColor.updateColorMap('', 'testColors');
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
||||
expect(getAnalogousColors).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should use analagous colors if shared label exit', () => {
|
||||
it('should use analagous colors', () => {
|
||||
window.featureFlags = {
|
||||
[FeatureFlag.USE_ANALAGOUS_COLORS]: true,
|
||||
};
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('a', 'blue', 2);
|
||||
const colorMap = sharedLabelColor.getColorMap('', 'testColors');
|
||||
expect(colorMap).not.toEqual({});
|
||||
sharedLabelColor.addSlice('b', 'blue', 1);
|
||||
sharedLabelColor.addSlice('c', 'green', 1);
|
||||
sharedLabelColor.addSlice('d', 'red', 1);
|
||||
sharedLabelColor.updateColorMap('', 'testColors');
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
||||
expect(getAnalogousColors).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getColorMap()', () => {
|
||||
it('should get color map', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ a: 'red', b: 'blue' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('.reset()', () => {
|
||||
it('should reset color map', () => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.addSlice('a', 'red', 1);
|
||||
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||
sharedLabelColor.reset();
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).toEqual({ a: '', b: '' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,11 +72,6 @@ export function removeSlice(sliceId) {
|
|||
return { type: REMOVE_SLICE, sliceId };
|
||||
}
|
||||
|
||||
export const RESET_SLICE = 'RESET_SLICE';
|
||||
export function resetSlice() {
|
||||
return { type: RESET_SLICE };
|
||||
}
|
||||
|
||||
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
|
||||
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
|
||||
export function toggleFaveStar(isStarred) {
|
||||
|
|
@ -506,28 +501,6 @@ export function addSliceToDashboard(id, component) {
|
|||
};
|
||||
}
|
||||
|
||||
export function postAddSliceFromDashboard() {
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
dashboardInfo: { metadata },
|
||||
dashboardState,
|
||||
} = getState();
|
||||
|
||||
if (dashboardState?.updateSlice && dashboardState?.editMode) {
|
||||
metadata.shared_label_colors = getSharedLabelColor().getColorMap(
|
||||
metadata?.color_namespace,
|
||||
metadata?.color_scheme,
|
||||
);
|
||||
dispatch(
|
||||
dashboardInfoChanged({
|
||||
metadata,
|
||||
}),
|
||||
);
|
||||
dispatch(resetSlice());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function removeSliceFromDashboard(id) {
|
||||
return (dispatch, getState) => {
|
||||
const sliceEntity = getState().sliceEntities.slices[id];
|
||||
|
|
@ -537,20 +510,7 @@ export function removeSliceFromDashboard(id) {
|
|||
|
||||
dispatch(removeSlice(id));
|
||||
dispatch(removeChart(id));
|
||||
|
||||
const {
|
||||
dashboardInfo: { metadata },
|
||||
} = getState();
|
||||
getSharedLabelColor().removeSlice(id);
|
||||
metadata.shared_label_colors = getSharedLabelColor().getColorMap(
|
||||
metadata?.color_namespace,
|
||||
metadata?.color_scheme,
|
||||
);
|
||||
dispatch(
|
||||
dashboardInfoChanged({
|
||||
metadata,
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,6 @@ describe('dashboardState actions', () => {
|
|||
|
||||
const removeFilter = dispatch.getCall(0).args[0];
|
||||
removeFilter(dispatch, getState);
|
||||
expect(dispatch.getCall(4).args[0].type).toBe(REMOVE_FILTER);
|
||||
expect(dispatch.getCall(3).args[0].type).toBe(REMOVE_FILTER);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ const createProps = () => ({
|
|||
setEditMode: jest.fn(),
|
||||
showBuilderPane: jest.fn(),
|
||||
updateCss: jest.fn(),
|
||||
setColorSchemeAndUnsavedChanges: jest.fn(),
|
||||
setColorScheme: jest.fn(),
|
||||
setUnsavedChanges: jest.fn(),
|
||||
logEvent: jest.fn(),
|
||||
setRefreshFrequency: jest.fn(),
|
||||
hasUnsavedChanges: false,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ const propTypes = {
|
|||
customCss: PropTypes.string.isRequired,
|
||||
colorNamespace: PropTypes.string,
|
||||
colorScheme: PropTypes.string,
|
||||
setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired,
|
||||
setColorScheme: PropTypes.func.isRequired,
|
||||
setUnsavedChanges: PropTypes.func.isRequired,
|
||||
isStarred: PropTypes.bool.isRequired,
|
||||
isPublished: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
|
|
@ -371,9 +372,8 @@ class Header extends React.PureComponent {
|
|||
dashboardInfo?.metadata?.color_scheme || colorScheme;
|
||||
const currentColorNamespace =
|
||||
dashboardInfo?.metadata?.color_namespace || colorNamespace;
|
||||
const currentSharedLabelColors = getSharedLabelColor().getColorMap(
|
||||
currentColorNamespace,
|
||||
currentColorScheme,
|
||||
const currentSharedLabelColors = Object.fromEntries(
|
||||
getSharedLabelColor().getColorMap(),
|
||||
);
|
||||
|
||||
const data = {
|
||||
|
|
@ -439,7 +439,8 @@ class Header extends React.PureComponent {
|
|||
customCss,
|
||||
colorNamespace,
|
||||
dataMask,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
setColorScheme,
|
||||
setUnsavedChanges,
|
||||
colorScheme,
|
||||
onUndo,
|
||||
onRedo,
|
||||
|
|
@ -480,6 +481,8 @@ class Header extends React.PureComponent {
|
|||
|
||||
const handleOnPropertiesChange = updates => {
|
||||
const { dashboardInfoChanged, dashboardTitleChanged } = this.props;
|
||||
|
||||
setColorScheme(updates.colorScheme);
|
||||
dashboardInfoChanged({
|
||||
slug: updates.slug,
|
||||
metadata: JSON.parse(updates.jsonMetadata || '{}'),
|
||||
|
|
@ -488,7 +491,7 @@ class Header extends React.PureComponent {
|
|||
owners: updates.owners,
|
||||
roles: updates.roles,
|
||||
});
|
||||
setColorSchemeAndUnsavedChanges(updates.colorScheme);
|
||||
setUnsavedChanges(true);
|
||||
dashboardTitleChanged(updates.title);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ export interface HeaderProps {
|
|||
user: Object | undefined;
|
||||
dashboardInfo: DashboardInfo;
|
||||
dashboardTitle: string;
|
||||
setColorSchemeAndUnsavedChanges: () => void;
|
||||
setColorScheme: () => void;
|
||||
setUnsavedChanges: () => void;
|
||||
isStarred: boolean;
|
||||
isPublished: boolean;
|
||||
onChange: () => void;
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ test('submitting with onlyApply:false', async () => {
|
|||
certificationDetails: 'Sample certification',
|
||||
certifiedBy: 'John Doe',
|
||||
colorScheme: 'supersetColors',
|
||||
colorNamespace: '',
|
||||
colorNamespace: undefined,
|
||||
id: 26,
|
||||
jsonMetadata: expect.anything(),
|
||||
owners: [],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import Button from 'src/components/Button';
|
|||
import { AntdForm, AsyncSelect, Col, Row } from 'src/components';
|
||||
import rison from 'rison';
|
||||
import {
|
||||
CategoricalColorNamespace,
|
||||
ensureIsArray,
|
||||
getCategoricalSchemeRegistry,
|
||||
getSharedLabelColor,
|
||||
|
|
@ -57,7 +58,6 @@ type PropertiesModalProps = {
|
|||
show?: boolean;
|
||||
onHide?: () => void;
|
||||
colorScheme?: string;
|
||||
setColorSchemeAndUnsavedChanges?: () => void;
|
||||
onSubmit?: (params: Record<string, any>) => void;
|
||||
addSuccessToast: (message: string) => void;
|
||||
addDangerToast: (message: string) => void;
|
||||
|
|
@ -181,9 +181,7 @@ const PropertiesModal = ({
|
|||
}
|
||||
const metaDataCopy = { ...metadata };
|
||||
|
||||
if (metaDataCopy?.shared_label_colors) {
|
||||
delete metaDataCopy.shared_label_colors;
|
||||
}
|
||||
delete metaDataCopy.shared_label_colors;
|
||||
|
||||
delete metaDataCopy.color_scheme_domain;
|
||||
|
||||
|
|
@ -268,7 +266,7 @@ const PropertiesModal = ({
|
|||
};
|
||||
|
||||
const onColorSchemeChange = (
|
||||
colorScheme?: string,
|
||||
colorScheme = '',
|
||||
{ updateMetadata = true } = {},
|
||||
) => {
|
||||
// check that color_scheme is valid
|
||||
|
|
@ -319,7 +317,7 @@ const PropertiesModal = ({
|
|||
|
||||
// color scheme in json metadata has precedence over selection
|
||||
currentColorScheme = metadata?.color_scheme || colorScheme;
|
||||
colorNamespace = metadata?.color_namespace || '';
|
||||
colorNamespace = metadata?.color_namespace;
|
||||
|
||||
// filter shared_label_color from user input
|
||||
if (metadata?.shared_label_colors) {
|
||||
|
|
@ -329,16 +327,20 @@ const PropertiesModal = ({
|
|||
delete metadata.color_scheme_domain;
|
||||
}
|
||||
|
||||
metadata.shared_label_colors = getSharedLabelColor().getColorMap(
|
||||
colorNamespace,
|
||||
currentColorScheme,
|
||||
true,
|
||||
);
|
||||
|
||||
if (metadata?.color_scheme) {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
const categoricalNamespace =
|
||||
CategoricalColorNamespace.getNamespace(colorNamespace);
|
||||
categoricalNamespace.resetColors();
|
||||
if (currentColorScheme) {
|
||||
sharedLabelColor.updateColorMap(colorNamespace, currentColorScheme);
|
||||
metadata.shared_label_colors = Object.fromEntries(
|
||||
sharedLabelColor.getColorMap(),
|
||||
);
|
||||
metadata.color_scheme_domain =
|
||||
categoricalSchemeRegistry.get(colorScheme)?.colors || [];
|
||||
} else {
|
||||
sharedLabelColor.reset();
|
||||
metadata.shared_label_colors = {};
|
||||
metadata.color_scheme_domain = [];
|
||||
}
|
||||
|
||||
|
|
@ -367,9 +369,9 @@ const PropertiesModal = ({
|
|||
...moreOnSubmitProps,
|
||||
};
|
||||
if (onlyApply) {
|
||||
addSuccessToast(t('Dashboard properties updated'));
|
||||
onSubmit(onSubmitProps);
|
||||
onHide();
|
||||
addSuccessToast(t('Dashboard properties updated'));
|
||||
} else {
|
||||
SupersetClient.put({
|
||||
endpoint: `/api/v1/dashboard/${dashboardId}`,
|
||||
|
|
@ -385,9 +387,9 @@ const PropertiesModal = ({
|
|||
...morePutProps,
|
||||
}),
|
||||
}).then(() => {
|
||||
addSuccessToast(t('The dashboard has been saved'));
|
||||
onSubmit(onSubmitProps);
|
||||
onHide();
|
||||
addSuccessToast(t('The dashboard has been saved'));
|
||||
}, handleErrorResponse);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -200,6 +200,15 @@ class Chart extends React.Component {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
// chart should re-render if color scheme or label color was changed
|
||||
nextProps.formData?.color_scheme !== this.props.formData?.color_scheme ||
|
||||
!areObjectsEqual(
|
||||
nextProps.formData?.label_colors,
|
||||
this.props.formData?.label_colors,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// `cacheBusterProp` is jected by react-hot-loader
|
||||
|
|
|
|||
|
|
@ -66,8 +66,6 @@ interface ChartHolderProps {
|
|||
updateComponents: Function;
|
||||
handleComponentDrop: (...args: unknown[]) => unknown;
|
||||
setFullSizeChartId: (chartId: number | null) => void;
|
||||
postAddSliceFromDashboard?: () => void;
|
||||
|
||||
isInView: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +90,6 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
|
|||
updateComponents,
|
||||
handleComponentDrop,
|
||||
setFullSizeChartId,
|
||||
postAddSliceFromDashboard,
|
||||
isInView,
|
||||
}) => {
|
||||
const { chartId } = component.meta;
|
||||
|
|
@ -236,14 +233,6 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
|
|||
}));
|
||||
}, []);
|
||||
|
||||
const handlePostTransformProps = useCallback(
|
||||
(props: unknown) => {
|
||||
postAddSliceFromDashboard?.();
|
||||
return props;
|
||||
},
|
||||
[postAddSliceFromDashboard],
|
||||
);
|
||||
|
||||
return (
|
||||
<DragDroppable
|
||||
component={component}
|
||||
|
|
@ -316,7 +305,6 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
|
|||
isFullSize={isFullSize}
|
||||
setControlValue={handleExtraControl}
|
||||
extraControls={extraControls}
|
||||
postTransformProps={handlePostTransformProps}
|
||||
isInView={isInView}
|
||||
/>
|
||||
{editMode && (
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import {
|
|||
setDirectPathToChild,
|
||||
setActiveTabs,
|
||||
setFullSizeChartId,
|
||||
postAddSliceFromDashboard,
|
||||
} from 'src/dashboard/actions/dashboardState';
|
||||
|
||||
const propTypes = {
|
||||
|
|
@ -112,7 +111,6 @@ function mapDispatchToProps(dispatch) {
|
|||
setFullSizeChartId,
|
||||
setActiveTabs,
|
||||
logEvent,
|
||||
postAddSliceFromDashboard,
|
||||
},
|
||||
dispatch,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ import {
|
|||
fetchFaveStar,
|
||||
saveFaveStar,
|
||||
savePublished,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
setColorScheme,
|
||||
setUnsavedChanges,
|
||||
fetchCharts,
|
||||
updateCss,
|
||||
onChange,
|
||||
|
|
@ -112,7 +113,8 @@ function mapDispatchToProps(dispatch) {
|
|||
onRedo: redoLayoutAction,
|
||||
setEditMode,
|
||||
showBuilderPane,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
setColorScheme,
|
||||
setUnsavedChanges,
|
||||
fetchFaveStar,
|
||||
saveFaveStar,
|
||||
savePublished,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
FeatureFlag,
|
||||
getSharedLabelColor,
|
||||
isFeatureEnabled,
|
||||
SharedLabelColorSource,
|
||||
t,
|
||||
useTheme,
|
||||
} from '@superset-ui/core';
|
||||
|
|
@ -336,17 +337,18 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
return () => {};
|
||||
}, [css]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
useEffect(() => {
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.source = SharedLabelColorSource.dashboard;
|
||||
return () => {
|
||||
// clean up label color
|
||||
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
|
||||
metadata?.color_namespace,
|
||||
);
|
||||
categoricalNamespace.resetColors();
|
||||
getSharedLabelColor().clear();
|
||||
},
|
||||
[metadata?.color_namespace],
|
||||
);
|
||||
sharedLabelColor.clear();
|
||||
};
|
||||
}, [metadata?.color_namespace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetsApiError) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import {
|
|||
UNSET_FOCUSED_FILTER_FIELD,
|
||||
SET_ACTIVE_TABS,
|
||||
SET_FULL_SIZE_CHART_ID,
|
||||
RESET_SLICE,
|
||||
ON_FILTERS_REFRESH,
|
||||
ON_FILTERS_REFRESH_SUCCESS,
|
||||
SET_DATASETS_STATUS,
|
||||
|
|
@ -60,7 +59,6 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
return {
|
||||
...state,
|
||||
sliceIds: Array.from(updatedSliceIds),
|
||||
updateSlice: true,
|
||||
};
|
||||
},
|
||||
[REMOVE_SLICE]() {
|
||||
|
|
@ -73,12 +71,6 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
sliceIds: Array.from(updatedSliceIds),
|
||||
};
|
||||
},
|
||||
[RESET_SLICE]() {
|
||||
return {
|
||||
...state,
|
||||
updateSlice: false,
|
||||
};
|
||||
},
|
||||
[TOGGLE_FAVE_STAR]() {
|
||||
return { ...state, isStarred: action.isStarred };
|
||||
},
|
||||
|
|
@ -125,7 +117,6 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
maxUndoHistoryExceeded: false,
|
||||
editMode: false,
|
||||
updatedColorScheme: false,
|
||||
updateSlice: false,
|
||||
// server-side returns last_modified_time for latest change
|
||||
lastModifiedTime: action.lastModifiedTime,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import {
|
|||
TOGGLE_EXPAND_SLICE,
|
||||
TOGGLE_FAVE_STAR,
|
||||
UNSET_FOCUSED_FILTER_FIELD,
|
||||
RESET_SLICE,
|
||||
} from 'src/dashboard/actions/dashboardState';
|
||||
|
||||
import dashboardStateReducer from 'src/dashboard/reducers/dashboardState';
|
||||
|
|
@ -44,7 +43,7 @@ describe('dashboardState reducer', () => {
|
|||
{ sliceIds: [1] },
|
||||
{ type: ADD_SLICE, slice: { slice_id: 2 } },
|
||||
),
|
||||
).toEqual({ sliceIds: [1, 2], updateSlice: true });
|
||||
).toEqual({ sliceIds: [1, 2] });
|
||||
});
|
||||
|
||||
it('should remove a slice', () => {
|
||||
|
|
@ -56,12 +55,6 @@ describe('dashboardState reducer', () => {
|
|||
).toEqual({ sliceIds: [1], filters: {} });
|
||||
});
|
||||
|
||||
it('should reset updateSlice', () => {
|
||||
expect(
|
||||
dashboardStateReducer({ updateSlice: true }, { type: RESET_SLICE }),
|
||||
).toEqual({ updateSlice: false });
|
||||
});
|
||||
|
||||
it('should toggle fav star', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,14 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { isDefined, JsonObject, makeApi, t } from '@superset-ui/core';
|
||||
import {
|
||||
getSharedLabelColor,
|
||||
isDefined,
|
||||
JsonObject,
|
||||
makeApi,
|
||||
SharedLabelColorSource,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
|
|
@ -139,6 +146,7 @@ export default function ExplorePage() {
|
|||
isExploreInitialized.current = true;
|
||||
});
|
||||
}
|
||||
getSharedLabelColor().source = SharedLabelColorSource.explore;
|
||||
}, [dispatch, location]);
|
||||
|
||||
if (!isLoaded) {
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
|
|||
"UX_BETA": False,
|
||||
"GENERIC_CHART_AXES": False,
|
||||
"ALLOW_ADHOC_SUBQUERY": False,
|
||||
"USE_ANALAGOUS_COLORS": True,
|
||||
"USE_ANALAGOUS_COLORS": False,
|
||||
"DASHBOARD_EDIT_CHART_IN_NEW_TAB": False,
|
||||
# Apply RLS rules to SQL Lab queries. This requires parsing and manipulating the
|
||||
# query, and might break queries and/or allow users to bypass RLS. Use with care!
|
||||
|
|
|
|||
Loading…
Reference in New Issue