[Lens] Reverse colors should not reverse palette picker previews (#110455)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marco Liberati 2021-09-06 14:27:53 +02:00 committed by GitHub
parent 2e2b451162
commit 1a88d34ea2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 414 additions and 28 deletions

View file

@ -14,6 +14,7 @@ import { ReactWrapper } from 'enzyme';
import type { CustomPaletteParams } from '../../../common';
import { applyPaletteParams } from './utils';
import { CustomizablePalette } from './palette_configuration';
import { CUSTOM_PALETTE } from './constants';
import { act } from 'react-dom/test-utils';
// mocking random id generator function
@ -129,6 +130,21 @@ describe('palette panel', () => {
});
});
it('should restore the reverse initial state on transitioning', () => {
const instance = mountWithIntl(<CustomizablePalette {...props} />);
changePaletteIn(instance, 'negative');
expect(props.setPalette).toHaveBeenCalledWith({
type: 'palette',
name: 'negative',
params: expect.objectContaining({
name: 'negative',
reverse: false,
}),
});
});
it('should rewrite the min/max range values on palette change', () => {
const instance = mountWithIntl(<CustomizablePalette {...props} />);
@ -175,6 +191,20 @@ describe('palette panel', () => {
})
);
});
it('should transition a predefined palette to a custom one on reverse click', () => {
const instance = mountWithIntl(<CustomizablePalette {...props} />);
toggleReverse(instance, true);
expect(props.setPalette).toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({
name: CUSTOM_PALETTE,
}),
})
);
});
});
describe('percentage / number modes', () => {

View file

@ -106,6 +106,7 @@ export function CustomizablePalette({
...activePalette.params,
name: newPalette.name,
colorStops: undefined,
reverse: false, // restore the reverse flag
};
const newColorStops = getColorStops(palettes, [], activePalette, dataBounds);
@ -317,28 +318,20 @@ export function CustomizablePalette({
className="lnsPalettePanel__reverseButton"
data-test-subj="lnsPalettePanel_dynamicColoring_reverse"
onClick={() => {
const params: CustomPaletteParams = { reverse: !activePalette.params?.reverse };
if (isCurrentPaletteCustom) {
params.colorStops = reversePalette(colorStopsToShow);
params.stops = getPaletteStops(
palettes,
{
...(activePalette?.params || {}),
colorStops: params.colorStops,
},
{ dataBounds }
);
} else {
params.stops = reversePalette(
activePalette?.params?.stops ||
getPaletteStops(
palettes,
{ ...activePalette.params, ...params },
{ prevPalette: activePalette.name, dataBounds }
)
);
}
setPalette(mergePaletteParams(activePalette, params));
// when reversing a palette, the palette is automatically transitioned to a custom palette
const newParams = getSwitchToCustomParams(
palettes,
activePalette,
{
colorStops: reversePalette(colorStopsToShow),
steps: activePalette.params?.steps || DEFAULT_COLOR_STEPS,
reverse: !activePalette.params?.reverse, // Store the reverse state
rangeMin: colorStopsToShow[0]?.stop,
rangeMax: colorStopsToShow[colorStopsToShow.length - 1]?.stop,
},
dataBounds
);
setPalette(newParams);
}}
>
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>

View file

@ -83,7 +83,7 @@ export function PalettePicker({
value: id,
title,
type: FIXED_PROGRESSION,
palette: activePalette?.params?.reverse ? colors.reverse() : colors,
palette: colors,
'data-test-subj': `${id}-palette`,
};
});

View file

@ -9,6 +9,7 @@ import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server';
import type { SerializableRecord } from '@kbn/utility-types';
import { DOC_TYPE } from '../../common';
import {
commonMakeReversePaletteAsCustom,
commonRemoveTimezoneDateHistogramParam,
commonRenameOperationsForFormula,
commonUpdateVisLayerType,
@ -17,6 +18,7 @@ import {
LensDocShape713,
LensDocShape715,
LensDocShapePre712,
VisState716,
VisStatePre715,
} from '../migrations/types';
import { extract, inject } from '../../common/embeddable_factory';
@ -50,6 +52,14 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => {
attributes: migratedLensState,
} as unknown) as SerializableRecord;
},
'7.16.0': (state) => {
const lensState = (state as unknown) as { attributes: LensDocShape715<VisState716> };
const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes);
return ({
...lensState,
attributes: migratedLensState,
} as unknown) as SerializableRecord;
},
},
extract,
inject,

View file

@ -6,6 +6,7 @@
*/
import { cloneDeep } from 'lodash';
import { PaletteOutput } from 'src/plugins/charts/common';
import {
LensDocShapePre712,
OperationTypePre712,
@ -15,8 +16,9 @@ import {
LensDocShape715,
VisStatePost715,
VisStatePre715,
VisState716,
} from './types';
import { layerTypes } from '../../common';
import { CustomPaletteParams, layerTypes } from '../../common';
export const commonRenameOperationsForFormula = (
attributes: LensDocShapePre712
@ -98,3 +100,56 @@ export const commonUpdateVisLayerType = (
}
return newAttributes as LensDocShape715<VisStatePost715>;
};
function moveDefaultPaletteToPercentCustomInPlace(palette?: PaletteOutput<CustomPaletteParams>) {
if (palette?.params?.reverse && palette.params.name !== 'custom' && palette.params.stops) {
// change to palette type to custom and migrate to a percentage type of mode
palette.name = 'custom';
palette.params.name = 'custom';
// we can make strong assumptions here:
// because it was a default palette reversed it means that stops were the default ones
// so when migrating, because there's no access to active data, we could leverage the
// percent rangeType to define colorStops in percent.
//
// Stops should be defined, but reversed, as the previous code was rewriting them on reverse.
//
// The only change the user should notice should be the mode changing from number to percent
// but the final result *must* be identical
palette.params.rangeType = 'percent';
const steps = palette.params.stops.length;
palette.params.rangeMin = 0;
palette.params.rangeMax = 80;
palette.params.steps = steps;
palette.params.colorStops = palette.params.stops.map(({ color }, index) => ({
color,
stop: (index * 100) / steps,
}));
palette.params.stops = palette.params.stops.map(({ color }, index) => ({
color,
stop: ((1 + index) * 100) / steps,
}));
}
}
export const commonMakeReversePaletteAsCustom = (
attributes: LensDocShape715<VisState716>
): LensDocShape715<VisState716> => {
const newAttributes = cloneDeep(attributes);
const vizState = (newAttributes as LensDocShape715<VisState716>).state.visualization;
if (
attributes.visualizationType !== 'lnsDatatable' &&
attributes.visualizationType !== 'lnsHeatmap'
) {
return newAttributes;
}
if ('columns' in vizState) {
for (const column of vizState.columns) {
if (column.colorMode && column.colorMode !== 'none') {
moveDefaultPaletteToPercentCustomInPlace(column.palette);
}
}
} else {
moveDefaultPaletteToPercentCustomInPlace(vizState.palette);
}
return newAttributes;
};

View file

@ -12,8 +12,9 @@ import {
SavedObjectMigrationFn,
SavedObjectUnsanitizedDoc,
} from 'src/core/server';
import { LensDocShape715, VisStatePost715, VisStatePre715 } from './types';
import { layerTypes } from '../../common';
import { LensDocShape715, VisState716, VisStatePost715, VisStatePre715 } from './types';
import { CustomPaletteParams, layerTypes } from '../../common';
import { PaletteOutput } from 'src/plugins/charts/common';
describe('Lens migrations', () => {
describe('7.7.0 missing dimensions in XY', () => {
@ -1129,4 +1130,276 @@ describe('Lens migrations', () => {
}
});
});
describe('7.16.0 move reversed default palette to custom palette', () => {
const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext;
const example = ({
type: 'lens',
id: 'mocked-saved-object-id',
attributes: {
savedObjectId: '1',
title: 'MyRenamedOps',
description: '',
visualizationType: null,
state: {
datasourceMetaData: {
filterableIndexPatterns: [],
},
datasourceStates: {
indexpattern: {
currentIndexPatternId: 'logstash-*',
layers: {
'2': {
columns: {
'3': {
label: '@timestamp',
dataType: 'date',
operationType: 'date_histogram',
sourceField: '@timestamp',
isBucketed: true,
scale: 'interval',
params: { interval: 'auto', timeZone: 'Europe/Berlin' },
},
'4': {
label: '@timestamp',
dataType: 'date',
operationType: 'date_histogram',
sourceField: '@timestamp',
isBucketed: true,
scale: 'interval',
params: { interval: 'auto' },
},
'5': {
label: '@timestamp',
dataType: 'date',
operationType: 'my_unexpected_operation',
isBucketed: true,
scale: 'interval',
params: { timeZone: 'do not delete' },
},
},
columnOrder: ['3', '4', '5'],
},
},
},
},
visualization: {},
query: { query: '', language: 'kuery' },
filters: [],
},
},
} as unknown) as SavedObjectUnsanitizedDoc<LensDocShape715<unknown>>;
it('should just return the same document for XY, partition and metric visualization types', () => {
for (const vizType of ['lnsXY', 'lnsPie', 'lnsMetric']) {
const exampleCopy = cloneDeep(example);
exampleCopy.attributes.visualizationType = vizType;
// add datatable state here, even with another viz (manual change?)
(exampleCopy.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' },
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' },
{
palette: { type: 'palette', name: 'temperature', params: { reverse: false } },
colorMode: 'cell',
},
],
} as unknown) as VisState716;
const result = migrations['7.16.0'](exampleCopy, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
expect(result).toEqual(exampleCopy);
}
});
it('should not change non reversed default palettes in datatable', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsDatatable';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' },
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' },
{
palette: { type: 'palette', name: 'temperature', params: { reverse: false } },
colorMode: 'cell',
},
],
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
expect(result).toEqual(datatableExample);
});
it('should not change custom palettes in datatable', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsDatatable';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [
{ palette: { type: 'palette', name: 'custom' }, colorMode: 'cell' },
{ palette: { type: 'palette', name: 'custom' }, colorMode: 'text' },
{
palette: { type: 'palette', name: 'custom', params: { reverse: true } },
colorMode: 'cell',
},
],
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
expect(result).toEqual(datatableExample);
});
it('should not change a datatable with no conditional coloring', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsDatatable';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [{ colorMode: 'none' }, {}],
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
expect(result).toEqual(datatableExample);
});
it('should not change default palette if the colorMode is set to "none" in datatable', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsDatatable';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' },
{ palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' },
{
palette: { type: 'palette', name: 'temperature', params: { reverse: true } },
colorMode: 'cell',
},
],
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
expect(result).toEqual(datatableExample);
});
it('should change a default palette reversed in datatable', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsDatatable';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
columns: [
{
colorMode: 'cell',
palette: {
type: 'palette',
name: 'temperature1',
params: {
reverse: true,
rangeType: 'number',
stops: [
{ color: 'red', stop: 10 },
{ color: 'blue', stop: 20 },
{ color: 'pink', stop: 50 },
{ color: 'green', stop: 60 },
{ color: 'yellow', stop: 70 },
],
},
},
},
{
colorMode: 'text',
palette: {
type: 'palette',
name: 'temperature2',
params: {
reverse: true,
rangeType: 'number',
stops: [
{ color: 'red', stop: 10 },
{ color: 'blue', stop: 20 },
{ color: 'pink', stop: 50 },
{ color: 'green', stop: 60 },
{ color: 'yellow', stop: 70 },
],
},
},
},
],
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
const state = (result.attributes as LensDocShape715<
Extract<VisState716, { columns: unknown[] }>
>).state.visualization;
for (const column of state.columns) {
expect(column.palette!.name).toBe('custom');
expect(column.palette!.params!.name).toBe('custom');
expect(column.palette!.params!.rangeMin).toBe(0);
expect(column.palette!.params!.rangeMax).toBe(80);
expect(column.palette!.params!.reverse).toBeTruthy(); // still true
expect(column.palette!.params!.rangeType).toBe('percent');
expect(column.palette!.params!.stops).toEqual([
{ color: 'red', stop: 20 },
{ color: 'blue', stop: 40 },
{ color: 'pink', stop: 60 },
{ color: 'green', stop: 80 },
{ color: 'yellow', stop: 100 },
]);
expect(column.palette!.params!.colorStops).toEqual([
{ color: 'red', stop: 0 },
{ color: 'blue', stop: 20 },
{ color: 'pink', stop: 40 },
{ color: 'green', stop: 60 },
{ color: 'yellow', stop: 80 },
]);
}
});
it('should change a default palette reversed in heatmap', () => {
const datatableExample = cloneDeep(example);
datatableExample.attributes.visualizationType = 'lnsHeatmap';
(datatableExample.attributes as LensDocShape715<VisState716>).state.visualization = ({
palette: {
type: 'palette',
name: 'temperature1',
params: {
reverse: true,
rangeType: 'number',
stops: [
{ color: 'red', stop: 10 },
{ color: 'blue', stop: 20 },
{ color: 'pink', stop: 50 },
{ color: 'green', stop: 60 },
{ color: 'yellow', stop: 70 },
],
},
},
} as unknown) as VisState716;
const result = migrations['7.16.0'](datatableExample, context) as ReturnType<
SavedObjectMigrationFn<LensDocShape, LensDocShape>
>;
const state = (result.attributes as LensDocShape715<
Extract<VisState716, { palette?: PaletteOutput<CustomPaletteParams> }>
>).state.visualization;
expect(state.palette!.name).toBe('custom');
expect(state.palette!.params!.name).toBe('custom');
expect(state.palette!.params!.rangeMin).toBe(0);
expect(state.palette!.params!.rangeMax).toBe(80);
expect(state.palette!.params!.reverse).toBeTruthy(); // still true
expect(state.palette!.params!.rangeType).toBe('percent');
expect(state.palette!.params!.stops).toEqual([
{ color: 'red', stop: 20 },
{ color: 'blue', stop: 40 },
{ color: 'pink', stop: 60 },
{ color: 'green', stop: 80 },
{ color: 'yellow', stop: 100 },
]);
expect(state.palette!.params!.colorStops).toEqual([
{ color: 'red', stop: 0 },
{ color: 'blue', stop: 20 },
{ color: 'pink', stop: 40 },
{ color: 'green', stop: 60 },
{ color: 'yellow', stop: 80 },
]);
});
});
});

View file

@ -23,11 +23,13 @@ import {
LensDocShape715,
VisStatePost715,
VisStatePre715,
VisState716,
} from './types';
import {
commonRenameOperationsForFormula,
commonRemoveTimezoneDateHistogramParam,
commonUpdateVisLayerType,
commonMakeReversePaletteAsCustom,
} from './common_migrations';
interface LensDocShapePre710<VisualizationState = unknown> {
@ -430,6 +432,14 @@ const addLayerTypeToVisualization: SavedObjectMigrationFn<
return { ...newDoc, attributes: commonUpdateVisLayerType(newDoc.attributes) };
};
const moveDefaultReversedPaletteToCustom: SavedObjectMigrationFn<
LensDocShape715<VisState716>,
LensDocShape715<VisState716>
> = (doc) => {
const newDoc = cloneDeep(doc);
return { ...newDoc, attributes: commonMakeReversePaletteAsCustom(newDoc.attributes) };
};
export const migrations: SavedObjectMigrationMap = {
'7.7.0': removeInvalidAccessors,
// The order of these migrations matter, since the timefield migration relies on the aggConfigs
@ -442,4 +452,5 @@ export const migrations: SavedObjectMigrationMap = {
'7.13.1': renameOperationsForFormula, // duplicate this migration in case a broken by value panel is added to the library
'7.14.0': removeTimezoneDateHistogramParam,
'7.15.0': addLayerTypeToVisualization,
'7.16.0': moveDefaultReversedPaletteToCustom,
};

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import type { PaletteOutput } from 'src/plugins/charts/common';
import { Query, Filter } from 'src/plugins/data/public';
import type { LayerType } from '../../common';
import type { CustomPaletteParams, LayerType } from '../../common';
export type OperationTypePre712 =
| 'avg'
@ -192,3 +193,16 @@ export interface LensDocShape715<VisualizationState = unknown> {
filters: Filter[];
};
}
export type VisState716 =
// Datatable
| {
columns: Array<{
palette?: PaletteOutput<CustomPaletteParams>;
colorMode?: 'none' | 'cell' | 'text';
}>;
}
// Heatmap
| {
palette?: PaletteOutput<CustomPaletteParams>;
};

View file

@ -76,7 +76,7 @@ export default function ({ getService }) {
}
expect(panels.length).to.be(1);
expect(panels[0].type).to.be('map');
expect(panels[0].version).to.be('7.15.0');
expect(panels[0].version).to.be('7.16.0');
});
});
});