Skip to content

Commit

Permalink
[TSVB] Adds a color picker in percentiles and percentiles rank aggs (#…
Browse files Browse the repository at this point in the history
…107390)

* WIP - Improve the way that percentiles are rendered in TSVB

* Adds color picker to percentile and percentile ranks

* initialize color

* Be backwards compatible

* Fixes unit tests

* Add a unit test for percentile rank

* Fix unit tests

* Address PR comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
stratoula and kibanamachine committed Aug 12, 2021
1 parent de9d784 commit 2ebdf3e
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/plugins/vis_type_timeseries/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export const ROUTES = {
FIELDS: '/api/metrics/fields',
};
export const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes';
export const TSVB_DEFAULT_COLOR = '#68BC00';
2 changes: 2 additions & 0 deletions src/plugins/vis_type_timeseries/common/types/panel_model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface Percentile {
shade?: number | string;
value?: number | string;
percentile?: string;
color?: string;
}

export interface Metric {
Expand Down Expand Up @@ -52,6 +53,7 @@ export interface Metric {
type: string;
value?: string;
values?: string[];
colors?: string[];
size?: string | number;
agg_with?: string;
order?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const runTest = (aggType, name, test, additionalProps = {}) => {
...additionalProps,
};
const series = { ...SERIES, metrics: [metric] };
const panel = { ...PANEL, series };
const panel = PANEL;

it(name, () => {
const wrapper = mountWithIntl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ export function PercentileAgg(props) {
/>
}
>
<Percentiles onChange={handleChange} name="percentiles" model={model} panel={panel} />
<Percentiles
onChange={handleChange}
name="percentiles"
model={model}
panel={panel}
seriesId={series.id}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { shallowWithIntl } from '@kbn/test/jest';
import { MultiValueRow } from './multi_value_row';
import { ColorPicker } from '../../color_picker';

describe('MultiValueRow', () => {
const model = {
id: 95,
value: '95',
color: '#00028',
};
const props = {
model,
enableColorPicker: true,
onChange: jest.fn(),
onDelete: jest.fn(),
onAdd: jest.fn(),
disableAdd: false,
disableDelete: false,
};

const wrapper = shallowWithIntl(<MultiValueRow {...props} />);

it('displays a color picker if the enableColorPicker prop is true', () => {
expect(wrapper.find(ColorPicker).length).toEqual(1);
});

it('not displays a color picker if the enableColorPicker prop is false', () => {
const newWrapper = shallowWithIntl(<MultiValueRow {...props} enableColorPicker={false} />);
expect(newWrapper.find(ColorPicker).length).toEqual(0);
});

it('sets the picker color to the model color', () => {
expect(wrapper.find(ColorPicker).prop('value')).toEqual('#00028');
});

it('should have called the onChange function on color change', () => {
wrapper.find(ColorPicker).simulate('change');
expect(props.onChange).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import React, { ChangeEvent } from 'react';
import { get } from 'lodash';

import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { TSVB_DEFAULT_COLOR } from '../../../../../common/constants';

import { AddDeleteButtons } from '../../add_delete_buttons';
import { ColorPicker, ColorProps } from '../../color_picker';

interface MultiValueRowProps {
model: {
id: number;
value: string;
color: string;
};
disableAdd: boolean;
disableDelete: boolean;
onChange: ({ value, id }: { id: number; value: string }) => void;
enableColorPicker: boolean;
onChange: ({ value, id, color }: { id: number; value: string; color: string }) => void;
onDelete: (model: { id: number; value: string }) => void;
onAdd: () => void;
}
Expand All @@ -32,16 +36,33 @@ export const MultiValueRow = ({
onAdd,
disableAdd,
disableDelete,
enableColorPicker,
}: MultiValueRowProps) => {
const onFieldNumberChange = (event: ChangeEvent<HTMLInputElement>) =>
onChange({
...model,
value: get(event, 'target.value'),
});

const onColorPickerChange = (props: ColorProps) =>
onChange({
...model,
color: props?.color || TSVB_DEFAULT_COLOR,
});

return (
<EuiPanel paddingSize="s" className="tvbAggRow__multiValueRow">
<EuiFlexGroup alignItems="center" gutterSize="s">
{enableColorPicker && (
<EuiFlexItem grow={false}>
<ColorPicker
disableTrash={true}
onChange={onColorPickerChange}
value={model.color}
name="color"
/>
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiFieldNumber
value={model.value === '' ? '' : Number(model.value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import { AggRow } from '../agg_row';
import { PercentileRankValues } from './percentile_rank_values';

import { KBN_FIELD_TYPES } from '../../../../../../data/public';
import type { Metric, Panel, SanitizedFieldType } from '../../../../../common/types';
import type { Metric, Panel, SanitizedFieldType, Series } from '../../../../../common/types';
import { TSVB_DEFAULT_COLOR } from '../../../../../common/constants';

import { DragHandleProps } from '../../../../types';
import { PercentileHdr } from '../percentile_hdr';

Expand All @@ -40,6 +42,7 @@ interface PercentileRankAggProps {
model: Metric;
panel: Panel;
siblings: Metric[];
series: Series;
dragHandleProps: DragHandleProps;
onAdd(): void;
onChange(): void;
Expand All @@ -48,19 +51,25 @@ interface PercentileRankAggProps {

export const PercentileRankAgg = (props: PercentileRankAggProps) => {
const { panel, fields, indexPattern } = props;
const defaults = { values: [''] };
const defaults = { values: [''], colors: [TSVB_DEFAULT_COLOR] };
const model = { ...defaults, ...props.model };

const htmlId = htmlIdGenerator();
const isTablePanel = panel.type === 'table';
const handleChange = createChangeHandler(props.onChange, model);
const handleSelectChange = createSelectHandler(handleChange);
const handleNumberChange = createNumberHandler(handleChange);
const percentileRankSeries =
panel.series.find((s) => s.id === props.series.id) || panel.series[0];
// If the series is grouped by, then these colors are not respected, no need to display the color picker */
const isGroupedBy = panel.series.length > 0 && percentileRankSeries.split_mode !== 'everything';
const enableColorPicker = !isGroupedBy && !['table', 'metric', 'markdown'].includes(panel.type);

const handlePercentileRankValuesChange = (values: Metric['values']) => {
const handlePercentileRankValuesChange = (values: Metric['values'], colors: Metric['colors']) => {
handleChange({
...model,
values,
colors,
});
};
return (
Expand Down Expand Up @@ -119,8 +128,10 @@ export const PercentileRankAgg = (props: PercentileRankAggProps) => {
disableAdd={isTablePanel}
disableDelete={isTablePanel}
showOnlyLastRow={isTablePanel}
model={model.values!}
values={model.values!}
colors={model.colors!}
onChange={handlePercentileRankValuesChange}
enableColorPicker={enableColorPicker}
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,43 @@ import React from 'react';
import { last } from 'lodash';

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { TSVB_DEFAULT_COLOR } from '../../../../../common/constants';
import { MultiValueRow } from './multi_value_row';

interface PercentileRankValuesProps {
model: Array<string | null>;
values: string[];
colors: string[];
disableDelete: boolean;
disableAdd: boolean;
showOnlyLastRow: boolean;
onChange: (values: any[]) => void;
enableColorPicker: boolean;
onChange: (values: string[], colors: string[]) => void;
}

export const PercentileRankValues = (props: PercentileRankValuesProps) => {
const model = props.model || [];
const { onChange, disableAdd, disableDelete, showOnlyLastRow } = props;
const values = props.values || [];
const colors = props.colors || [];
const { onChange, disableAdd, disableDelete, showOnlyLastRow, enableColorPicker } = props;

const onChangeValue = ({ value, id }: { value: string; id: number }) => {
model[id] = value;
const onChangeValue = ({ value, id, color }: { value: string; id: number; color: string }) => {
values[id] = value;
colors[id] = color;

onChange(model);
onChange(values, colors);
};
const onDeleteValue = ({ id }: { id: number }) =>
onChange(model.filter((item, currentIndex) => id !== currentIndex));
const onAddValue = () => onChange([...model, '']);
onChange(
values.filter((item, currentIndex) => id !== currentIndex),
colors.filter((item, currentIndex) => id !== currentIndex)
);
const onAddValue = () => onChange([...values, ''], [...colors, TSVB_DEFAULT_COLOR]);

const renderRow = ({
rowModel,
disableDeleteRow,
disableAddRow,
}: {
rowModel: { id: number; value: string };
rowModel: { id: number; value: string; color: string };
disableDeleteRow: boolean;
disableAddRow: boolean;
}) => (
Expand All @@ -50,6 +58,7 @@ export const PercentileRankValues = (props: PercentileRankValuesProps) => {
disableDelete={disableDeleteRow}
disableAdd={disableAddRow}
model={rowModel}
enableColorPicker={enableColorPicker}
/>
</EuiFlexItem>
);
Expand All @@ -59,19 +68,21 @@ export const PercentileRankValues = (props: PercentileRankValuesProps) => {
{showOnlyLastRow &&
renderRow({
rowModel: {
id: model.length - 1,
value: last(model) || '',
id: values.length - 1,
value: last(values) || '',
color: last(colors) || TSVB_DEFAULT_COLOR,
},
disableAddRow: true,
disableDeleteRow: true,
})}

{!showOnlyLastRow &&
model.map((value, id, array) =>
values.map((value, id, array) =>
renderRow({
rowModel: {
id,
value: value || '',
color: colors[id] || TSVB_DEFAULT_COLOR,
},
disableAddRow: disableAdd,
disableDeleteRow: disableDelete || array.length < 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import _ from 'lodash';
import { TSVB_DEFAULT_COLOR } from '../../../../common/constants';
import { collectionActions } from '../lib/collection_actions';
import { AddDeleteButtons } from '../add_delete_buttons';
import uuid from 'uuid';
Expand All @@ -23,10 +24,11 @@ import {
EuiFlexGrid,
EuiPanel,
} from '@elastic/eui';
import { ColorPicker } from '../color_picker';
import { FormattedMessage } from '@kbn/i18n/react';

export const newPercentile = (opts) => {
return _.assign({ id: uuid.v1(), mode: 'line', shade: 0.2 }, opts);
return _.assign({ id: uuid.v1(), mode: 'line', shade: 0.2, color: TSVB_DEFAULT_COLOR }, opts);
};

export class Percentiles extends Component {
Expand All @@ -39,11 +41,20 @@ export class Percentiles extends Component {
};
}

handleColorChange(item) {
return (val) => {
const handleChange = collectionActions.handleChange.bind(null, this.props);
handleChange(_.assign({}, item, val));
};
}

renderRow = (row, i, items) => {
const defaults = { value: '', percentile: '', shade: '' };
const defaults = { value: '', percentile: '', shade: '', color: TSVB_DEFAULT_COLOR };
const model = { ...defaults, ...row };
const { panel } = this.props;
const { panel, seriesId } = this.props;
const flexItemStyle = { minWidth: 100 };
const percentileSeries = panel.series.find((s) => s.id === seriesId) || panel.series[0];
const isGroupedBy = panel.series.length > 0 && percentileSeries.split_mode !== 'everything';

const percentileFieldNumber = (
<EuiFlexItem style={flexItemStyle}>
Expand Down Expand Up @@ -106,7 +117,19 @@ export class Percentiles extends Component {
<EuiPanel>
<EuiFlexGroup key={model.id} alignItems="center">
<EuiFlexItem>
<EuiFlexGrid columns={2}>
<EuiFlexGrid columns={3}>
{/* If the series is grouped by, then these colors are not respected,
no need to display the color picker */}
{!isGroupedBy && !['table', 'metric', 'markdown'].includes(panel.type) && (
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
<ColorPicker
disableTrash={true}
onChange={this.handleColorChange(model)}
value={model.color}
name="color"
/>
</EuiFlexItem>
)}
{percentileFieldNumber}
<EuiFlexItem style={flexItemStyle}>
<EuiFormRow
Expand Down Expand Up @@ -206,4 +229,5 @@ Percentiles.propTypes = {
model: PropTypes.object,
panel: PropTypes.object,
onChange: PropTypes.func,
seriesId: PropTypes.string,
};
Loading

0 comments on commit 2ebdf3e

Please sign in to comment.