[TSVB] Type public code. Step 2 - panel configs (#94403)
* Remove request facade and update search strategies * Use typescript * Type files * Update structure * Update tests * Type annotations * Fix type for infra * Type editor_controller * Type vis_editor * Type vis_picker * Fix types * Type panel_config * Fix vis data type * Enhance types * Remove generics * Use constant * Update docs * Use empty object as default data * Convert yes_no component to typescript * Type color rules * Type panel configs * Type helpers * Type color rules * Type collection actions * Get rid of create_text_handler * Fix collection actions types * Revert get_request_params changes, do some code refactoring, type create_number_handler and get rid of detect_ie Co-authored-by: Daniil Suleiman <daniil_suleiman@epam.com> Co-authored-by: Diana Derevyankina <dziyana_dzeraviankina@epam.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5e31f91614
commit
8961f8523e
|
@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { TypeOptions } from '@kbn/config-schema/target/types/types';
|
||||
|
||||
const stringOptionalNullable = schema.maybe(schema.nullable(schema.string()));
|
||||
const stringOptional = schema.maybe(schema.string());
|
||||
|
||||
const stringRequired = schema.string();
|
||||
|
||||
|
@ -205,23 +206,15 @@ export const panel = schema.object({
|
|||
background_color_rules: schema.maybe(schema.arrayOf(backgroundColorRulesItems)),
|
||||
default_index_pattern: stringOptionalNullable,
|
||||
default_timefield: stringOptionalNullable,
|
||||
drilldown_url: stringOptionalNullable,
|
||||
drilldown_url: stringOptional,
|
||||
drop_last_bucket: numberIntegerOptional,
|
||||
filter: schema.nullable(
|
||||
schema.oneOf([
|
||||
stringOptionalNullable,
|
||||
schema.object({
|
||||
language: stringOptionalNullable,
|
||||
query: stringOptionalNullable,
|
||||
}),
|
||||
])
|
||||
),
|
||||
filter: schema.maybe(queryObject),
|
||||
gauge_color_rules: schema.maybe(schema.arrayOf(gaugeColorRulesItems)),
|
||||
gauge_width: schema.nullable(schema.oneOf([stringOptionalNullable, numberOptional])),
|
||||
gauge_inner_color: stringOptionalNullable,
|
||||
gauge_inner_width: stringOrNumberOptionalNullable,
|
||||
gauge_style: stringOptionalNullable,
|
||||
gauge_max: stringOrNumberOptionalNullable,
|
||||
gauge_max: numberOptionalOrEmptyString,
|
||||
id: stringRequired,
|
||||
ignore_global_filters: numberOptional,
|
||||
ignore_global_filter: numberOptional,
|
||||
|
|
|
@ -13,7 +13,6 @@ import { EuiDraggable, EuiDroppable } from '@elastic/eui';
|
|||
import { Agg } from './agg';
|
||||
// @ts-ignore
|
||||
import { seriesChangeHandler } from '../lib/series_change_handler';
|
||||
// @ts-ignore
|
||||
import { handleAdd, handleDelete } from '../lib/collection_actions';
|
||||
import { newMetricAggFn } from '../lib/new_metric_agg_fn';
|
||||
import { PanelSchema, SeriesItemsSchema } from '../../../../common/types';
|
||||
|
@ -23,10 +22,12 @@ import { IFieldType } from '../../../../../data/common/index_patterns/fields';
|
|||
const DROPPABLE_ID = 'aggs_dnd';
|
||||
|
||||
export interface AggsProps {
|
||||
name: keyof SeriesItemsSchema;
|
||||
panel: PanelSchema;
|
||||
model: SeriesItemsSchema;
|
||||
fields: IFieldType[];
|
||||
uiRestrictions: TimeseriesUIRestrictions;
|
||||
onChange(): void;
|
||||
}
|
||||
|
||||
export class Aggs extends PureComponent<AggsProps> {
|
||||
|
|
|
@ -21,7 +21,7 @@ interface FieldSelectProps {
|
|||
type: string;
|
||||
fields: Record<string, SanitizedFieldType[]>;
|
||||
indexPattern: string;
|
||||
value: string;
|
||||
value?: string | null;
|
||||
onChange: (options: Array<EuiComboBoxOptionOption<string>>) => void;
|
||||
disabled?: boolean;
|
||||
restrict?: string[];
|
||||
|
|
|
@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
|
||||
export interface PercentileHdrProps {
|
||||
value: number | undefined;
|
||||
onChange: () => void;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const PercentileHdr = ({ value, onChange }: PercentileHdrProps) => (
|
||||
|
|
|
@ -17,19 +17,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AggSelect } from '../agg_select';
|
||||
// @ts-ignore
|
||||
import { FieldSelect } from '../field_select';
|
||||
// @ts-ignore
|
||||
import { createChangeHandler } from '../../lib/create_change_handler';
|
||||
// @ts-ignore
|
||||
import { createSelectHandler } from '../../lib/create_select_handler';
|
||||
// @ts-ignore
|
||||
import { createNumberHandler } from '../../lib/create_number_handler';
|
||||
|
||||
import { AggRow } from '../agg_row';
|
||||
import { PercentileRankValues } from './percentile_rank_values';
|
||||
|
||||
import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public';
|
||||
import { KBN_FIELD_TYPES } from '../../../../../../data/public';
|
||||
import { MetricsItemsSchema, PanelSchema, SanitizedFieldType } from '../../../../../common/types';
|
||||
import { DragHandleProps } from '../../../../types';
|
||||
import { PercentileHdr } from '../percentile_hdr';
|
||||
|
|
|
@ -26,7 +26,7 @@ interface ColorProps {
|
|||
|
||||
export interface ColorPickerProps {
|
||||
name: string;
|
||||
value: string | null;
|
||||
value?: string | null;
|
||||
disableTrash?: boolean;
|
||||
onChange: (props: ColorProps) => void;
|
||||
}
|
||||
|
@ -39,16 +39,12 @@ export function ColorPicker({ name, value, disableTrash = false, onChange }: Col
|
|||
|
||||
const handleColorChange: EuiColorPickerProps['onChange'] = (text: string, { rgba, hex }) => {
|
||||
setColor(text);
|
||||
const part: ColorProps = {};
|
||||
part[name] = hex ? `rgba(${rgba.join(',')})` : '';
|
||||
onChange(part);
|
||||
onChange({ [name]: hex ? `rgba(${rgba.join(',')})` : '' });
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setColor('');
|
||||
const part: ColorProps = {};
|
||||
part[name] = null;
|
||||
onChange(part);
|
||||
onChange({ [name]: null });
|
||||
};
|
||||
|
||||
const label = value
|
||||
|
|
|
@ -7,59 +7,58 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { collectionActions } from './lib/collection_actions';
|
||||
import { ColorRules } from './color_rules';
|
||||
import { keys } from '@elastic/eui';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
|
||||
import { collectionActions } from './lib/collection_actions';
|
||||
import { ColorRules, ColorRulesProps } from './color_rules';
|
||||
|
||||
describe('src/legacy/core_plugins/metrics/public/components/color_rules.test.js', () => {
|
||||
let defaultProps;
|
||||
beforeAll(() => {
|
||||
defaultProps = {
|
||||
name: 'gauge_color_rules',
|
||||
model: {
|
||||
gauge_color_rules: [
|
||||
{
|
||||
gauge: null,
|
||||
value: 0,
|
||||
id: 'unique value',
|
||||
},
|
||||
],
|
||||
},
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
});
|
||||
const defaultProps = ({
|
||||
name: 'gauge_color_rules',
|
||||
model: {
|
||||
gauge_color_rules: [
|
||||
{
|
||||
gauge: null,
|
||||
value: 0,
|
||||
id: 'unique value',
|
||||
},
|
||||
],
|
||||
},
|
||||
onChange: jest.fn(),
|
||||
} as unknown) as ColorRulesProps;
|
||||
|
||||
describe('ColorRules', () => {
|
||||
it('should render empty <div/> node', () => {
|
||||
const emptyProps = {
|
||||
const emptyProps = ({
|
||||
name: 'gauge_color_rules',
|
||||
model: {},
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
const wrapper = mountWithIntl(<ColorRules.WrappedComponent {...emptyProps} />);
|
||||
} as unknown) as ColorRulesProps;
|
||||
const wrapper = mountWithIntl(<ColorRules {...emptyProps} />);
|
||||
const isNode = wrapper.find('div').children().exists();
|
||||
expect(isNode).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render non-empty <div/> node', () => {
|
||||
const wrapper = mountWithIntl(<ColorRules.WrappedComponent {...defaultProps} />);
|
||||
const wrapper = mountWithIntl(<ColorRules {...defaultProps} />);
|
||||
const isNode = wrapper.find('div.tvbColorPicker').exists();
|
||||
|
||||
expect(isNode).toBeTruthy();
|
||||
});
|
||||
it('should handle change of operator and value correctly', () => {
|
||||
collectionActions.handleChange = jest.fn();
|
||||
const wrapper = mountWithIntl(<ColorRules.WrappedComponent {...defaultProps} />);
|
||||
const wrapper = mountWithIntl(<ColorRules {...defaultProps} />);
|
||||
const operatorInput = findTestSubject(wrapper, 'colorRuleOperator');
|
||||
operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN });
|
||||
operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN });
|
||||
operatorInput.simulate('keyDown', { key: keys.ENTER });
|
||||
expect(collectionActions.handleChange.mock.calls[0][1].operator).toEqual('gt');
|
||||
expect((collectionActions.handleChange as jest.Mock).mock.calls[0][1].operator).toEqual('gt');
|
||||
|
||||
const numberInput = findTestSubject(wrapper, 'colorRuleValue');
|
||||
numberInput.simulate('change', { target: { value: '123' } });
|
||||
expect(collectionActions.handleChange.mock.calls[1][1].value).toEqual(123);
|
||||
expect((collectionActions.handleChange as jest.Mock).mock.calls[1][1].value).toEqual(123);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,12 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { AddDeleteButtons } from './add_delete_buttons';
|
||||
import { collectionActions } from './lib/collection_actions';
|
||||
import { ColorPicker } from './color_picker';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiComboBox,
|
||||
|
@ -19,76 +14,117 @@ import {
|
|||
EuiFormLabel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiComboBoxOptionOption,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
class ColorRulesUI extends Component {
|
||||
constructor(props) {
|
||||
import { AddDeleteButtons } from './add_delete_buttons';
|
||||
import { collectionActions } from './lib/collection_actions';
|
||||
import { ColorPicker, ColorPickerProps } from './color_picker';
|
||||
import { TimeseriesVisParams } from '../../types';
|
||||
|
||||
export interface ColorRulesProps {
|
||||
name: keyof TimeseriesVisParams;
|
||||
model: TimeseriesVisParams;
|
||||
onChange: (partialModel: Partial<TimeseriesVisParams>) => void;
|
||||
primaryName?: string;
|
||||
primaryVarName?: string;
|
||||
secondaryName?: string;
|
||||
secondaryVarName?: string;
|
||||
hideSecondary?: boolean;
|
||||
}
|
||||
|
||||
interface ColorRule {
|
||||
value?: number;
|
||||
id: string;
|
||||
background_color?: string;
|
||||
color?: string;
|
||||
operator?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const defaultSecondaryName = i18n.translate(
|
||||
'visTypeTimeseries.colorRules.defaultSecondaryNameLabel',
|
||||
{
|
||||
defaultMessage: 'text',
|
||||
}
|
||||
);
|
||||
const defaultPrimaryName = i18n.translate('visTypeTimeseries.colorRules.defaultPrimaryNameLabel', {
|
||||
defaultMessage: 'background',
|
||||
});
|
||||
|
||||
const operatorOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.colorRules.greaterThanLabel', {
|
||||
defaultMessage: '> greater than',
|
||||
}),
|
||||
value: 'gt',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.colorRules.greaterThanOrEqualLabel', {
|
||||
defaultMessage: '>= greater than or equal',
|
||||
}),
|
||||
value: 'gte',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.colorRules.lessThanLabel', {
|
||||
defaultMessage: '< less than',
|
||||
}),
|
||||
value: 'lt',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.colorRules.lessThanOrEqualLabel', {
|
||||
defaultMessage: '<= less than or equal',
|
||||
}),
|
||||
value: 'lte',
|
||||
},
|
||||
];
|
||||
|
||||
export class ColorRules extends Component<ColorRulesProps> {
|
||||
constructor(props: ColorRulesProps) {
|
||||
super(props);
|
||||
this.renderRow = this.renderRow.bind(this);
|
||||
}
|
||||
|
||||
handleChange(item, name, cast = String) {
|
||||
return (e) => {
|
||||
const handleChange = collectionActions.handleChange.bind(null, this.props);
|
||||
const part = {};
|
||||
part[name] = cast(_.get(e, '[0].value', _.get(e, 'target.value')));
|
||||
if (part[name] === 'undefined') part[name] = undefined;
|
||||
if (cast === Number && isNaN(part[name])) part[name] = undefined;
|
||||
handleChange(_.assign({}, item, part));
|
||||
handleValueChange(item: ColorRule) {
|
||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let value: number | undefined = Number(e.target.value);
|
||||
if (isNaN(value)) value = undefined;
|
||||
collectionActions.handleChange(this.props, {
|
||||
...item,
|
||||
value,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
renderRow(row, i, items) {
|
||||
handleOperatorChange = (item: ColorRule) => {
|
||||
return (options: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
collectionActions.handleChange(this.props, {
|
||||
...item,
|
||||
operator: options[0].value,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
renderRow(row: ColorRule, i: number, items: ColorRule[]) {
|
||||
const defaults = { value: 0 };
|
||||
const model = { ...defaults, ...row };
|
||||
const handleAdd = () => collectionActions.handleAdd(this.props);
|
||||
const handleDelete = collectionActions.handleDelete.bind(null, this.props, model);
|
||||
const { intl } = this.props;
|
||||
const operatorOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.colorRules.greaterThanLabel',
|
||||
defaultMessage: '> greater than',
|
||||
}),
|
||||
value: 'gt',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.colorRules.greaterThanOrEqualLabel',
|
||||
defaultMessage: '>= greater than or equal',
|
||||
}),
|
||||
value: 'gte',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.colorRules.lessThanLabel',
|
||||
defaultMessage: '< less than',
|
||||
}),
|
||||
value: 'lt',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.colorRules.lessThanOrEqualLabel',
|
||||
defaultMessage: '<= less than or equal',
|
||||
}),
|
||||
value: 'lte',
|
||||
},
|
||||
];
|
||||
const handleColorChange = (part) => {
|
||||
const handleChange = collectionActions.handleChange.bind(null, this.props);
|
||||
handleChange(_.assign({}, model, part));
|
||||
const handleDelete = () => collectionActions.handleDelete(this.props, model);
|
||||
const handleColorChange: ColorPickerProps['onChange'] = (part) => {
|
||||
collectionActions.handleChange(this.props, { ...model, ...part });
|
||||
};
|
||||
const htmlId = htmlIdGenerator(model.id);
|
||||
const selectedOperatorOption = operatorOptions.find((option) => {
|
||||
return model.operator === option.value;
|
||||
});
|
||||
const selectedOperatorOption = operatorOptions.find(
|
||||
(option) => model.operator === option.value
|
||||
);
|
||||
|
||||
const labelStyle = { marginBottom: 0 };
|
||||
|
||||
let secondary;
|
||||
if (!this.props.hideSecondary) {
|
||||
const secondaryVarName = this.props.secondaryVarName ?? 'color';
|
||||
secondary = (
|
||||
<Fragment>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -96,7 +132,7 @@ class ColorRulesUI extends Component {
|
|||
<FormattedMessage
|
||||
id="visTypeTimeseries.colorRules.setSecondaryColorLabel"
|
||||
defaultMessage="and {secondaryName} to"
|
||||
values={{ secondaryName: this.props.secondaryName }}
|
||||
values={{ secondaryName: this.props.secondaryName ?? defaultSecondaryName }}
|
||||
description="Part of a larger string: Set {primaryName} to {color} and {secondaryName} to {color} if
|
||||
metric is {greaterOrLessThan} {value}."
|
||||
/>
|
||||
|
@ -105,8 +141,8 @@ class ColorRulesUI extends Component {
|
|||
<EuiFlexItem grow={false}>
|
||||
<ColorPicker
|
||||
onChange={handleColorChange}
|
||||
name={this.props.secondaryVarName}
|
||||
value={model[this.props.secondaryVarName]}
|
||||
name={secondaryVarName}
|
||||
value={model[secondaryVarName as keyof ColorRule] as string | undefined}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</Fragment>
|
||||
|
@ -126,7 +162,7 @@ class ColorRulesUI extends Component {
|
|||
<FormattedMessage
|
||||
id="visTypeTimeseries.colorRules.setPrimaryColorLabel"
|
||||
defaultMessage="Set {primaryName} to"
|
||||
values={{ primaryName: this.props.primaryName }}
|
||||
values={{ primaryName: this.props.primaryName ?? defaultPrimaryName }}
|
||||
description="Part of a larger string: Set {primaryName} to {color} and {secondaryName} to {color} if
|
||||
metric is {greaterOrLessThan} {value}."
|
||||
/>
|
||||
|
@ -135,8 +171,12 @@ class ColorRulesUI extends Component {
|
|||
<EuiFlexItem grow={false}>
|
||||
<ColorPicker
|
||||
onChange={handleColorChange}
|
||||
name={this.props.primaryVarName}
|
||||
value={model[this.props.primaryVarName]}
|
||||
name={this.props.primaryVarName ?? 'background_color'}
|
||||
value={
|
||||
model[(this.props.primaryVarName ?? 'background_color') as keyof ColorRule] as
|
||||
| string
|
||||
| undefined
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
@ -157,7 +197,7 @@ class ColorRulesUI extends Component {
|
|||
id={htmlId('ifMetricIs')}
|
||||
options={operatorOptions}
|
||||
selectedOptions={selectedOperatorOption ? [selectedOperatorOption] : []}
|
||||
onChange={this.handleChange(model, 'operator')}
|
||||
onChange={this.handleOperatorChange(model)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
data-test-subj="colorRuleOperator"
|
||||
fullWidth
|
||||
|
@ -166,12 +206,11 @@ class ColorRulesUI extends Component {
|
|||
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'visTypeTimeseries.colorRules.valueAriaLabel',
|
||||
aria-label={i18n.translate('visTypeTimeseries.colorRules.valueAriaLabel', {
|
||||
defaultMessage: 'Value',
|
||||
})}
|
||||
value={model.value}
|
||||
onChange={this.handleChange(model, 'value', Number)}
|
||||
value={model.value ?? ''}
|
||||
onChange={this.handleValueChange(model)}
|
||||
data-test-subj="colorRuleValue"
|
||||
fullWidth
|
||||
/>
|
||||
|
@ -191,34 +230,6 @@ class ColorRulesUI extends Component {
|
|||
|
||||
render() {
|
||||
const { model, name } = this.props;
|
||||
if (!model[name]) return <div />;
|
||||
const rows = model[name].map(this.renderRow);
|
||||
return <div>{rows}</div>;
|
||||
return !model[name] ? <div /> : <div>{(model[name] as ColorRule[]).map(this.renderRow)}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ColorRulesUI.defaultProps = {
|
||||
name: 'color_rules',
|
||||
primaryName: i18n.translate('visTypeTimeseries.colorRules.defaultPrimaryNameLabel', {
|
||||
defaultMessage: 'background',
|
||||
}),
|
||||
primaryVarName: 'background_color',
|
||||
secondaryName: i18n.translate('visTypeTimeseries.colorRules.defaultSecondaryNameLabel', {
|
||||
defaultMessage: 'text',
|
||||
}),
|
||||
secondaryVarName: 'color',
|
||||
hideSecondary: false,
|
||||
};
|
||||
|
||||
ColorRulesUI.propTypes = {
|
||||
name: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
primaryName: PropTypes.string,
|
||||
primaryVarName: PropTypes.string,
|
||||
secondaryName: PropTypes.string,
|
||||
secondaryVarName: PropTypes.string,
|
||||
hideSecondary: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const ColorRules = injectI18n(ColorRulesUI);
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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 uuid from 'uuid';
|
||||
|
||||
const newFn = () => ({ id: uuid.v1() });
|
||||
|
||||
export function handleChange(props, doc) {
|
||||
const { model, name } = props;
|
||||
const collection = model[name] || [];
|
||||
const part = {};
|
||||
part[name] = collection.map((row) => {
|
||||
if (row.id === doc.id) return doc;
|
||||
return row;
|
||||
});
|
||||
props.onChange?.({ ...model, ...part });
|
||||
}
|
||||
|
||||
export function handleDelete(props, doc) {
|
||||
const { model, name } = props;
|
||||
const collection = model[name] || [];
|
||||
const part = {};
|
||||
part[name] = collection.filter((row) => row.id !== doc.id);
|
||||
props.onChange?.({ ...model, ...part });
|
||||
}
|
||||
|
||||
export function handleAdd(props, fn = newFn) {
|
||||
const { model, name } = props;
|
||||
const collection = model[name] || [];
|
||||
const part = {};
|
||||
part[name] = collection.concat([fn()]);
|
||||
props.onChange?.({ ...model, ...part });
|
||||
}
|
||||
|
||||
export const collectionActions = { handleAdd, handleDelete, handleChange };
|
|
@ -6,50 +6,55 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { handleChange, handleAdd, handleDelete } from './collection_actions';
|
||||
import {
|
||||
handleChange,
|
||||
handleAdd,
|
||||
handleDelete,
|
||||
CollectionActionsProps,
|
||||
} from './collection_actions';
|
||||
|
||||
describe('collection actions', () => {
|
||||
test('handleChange() calls props.onChange() with updated collection', () => {
|
||||
const fn = jest.fn();
|
||||
const props = {
|
||||
model: { test: [{ id: 1, title: 'foo' }] },
|
||||
const props = ({
|
||||
model: { test: [{ id: '1', title: 'foo' }] },
|
||||
name: 'test',
|
||||
onChange: fn,
|
||||
};
|
||||
handleChange.call(null, props, { id: 1, title: 'bar' });
|
||||
} as unknown) as CollectionActionsProps<any>;
|
||||
handleChange.call(null, props, { id: '1', type: 'bar' });
|
||||
expect(fn.mock.calls.length).toEqual(1);
|
||||
expect(fn.mock.calls[0][0]).toEqual({
|
||||
test: [{ id: 1, title: 'bar' }],
|
||||
test: [{ id: '1', type: 'bar' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('handleAdd() calls props.onChange() with update collection', () => {
|
||||
const newItemFn = jest.fn(() => ({ id: 2, title: 'example' }));
|
||||
const newItemFn = jest.fn(() => ({ id: '2', text: 'example' }));
|
||||
const fn = jest.fn();
|
||||
const props = {
|
||||
model: { test: [{ id: 1, title: 'foo' }] },
|
||||
const props = ({
|
||||
model: { test: [{ id: '1', text: 'foo' }] },
|
||||
name: 'test',
|
||||
onChange: fn,
|
||||
};
|
||||
} as unknown) as CollectionActionsProps<any>;
|
||||
handleAdd.call(null, props, newItemFn);
|
||||
expect(fn.mock.calls.length).toEqual(1);
|
||||
expect(newItemFn.mock.calls.length).toEqual(1);
|
||||
expect(fn.mock.calls[0][0]).toEqual({
|
||||
test: [
|
||||
{ id: 1, title: 'foo' },
|
||||
{ id: 2, title: 'example' },
|
||||
{ id: '1', text: 'foo' },
|
||||
{ id: '2', text: 'example' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('handleDelete() calls props.onChange() with update collection', () => {
|
||||
const fn = jest.fn();
|
||||
const props = {
|
||||
model: { test: [{ id: 1, title: 'foo' }] },
|
||||
const props = ({
|
||||
model: { test: [{ id: '1', title: 'foo' }] },
|
||||
name: 'test',
|
||||
onChange: fn,
|
||||
};
|
||||
handleDelete.call(null, props, { id: 1 });
|
||||
} as unknown) as CollectionActionsProps<any>;
|
||||
handleDelete.call(null, props, { id: '1' });
|
||||
expect(fn.mock.calls.length).toEqual(1);
|
||||
expect(fn.mock.calls[0][0]).toEqual({
|
||||
test: [],
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 uuid from 'uuid';
|
||||
|
||||
interface DocType {
|
||||
id: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const newFn = (): DocType => ({ id: uuid.v1() });
|
||||
|
||||
export interface CollectionActionsProps<T> {
|
||||
model: T;
|
||||
name: keyof T;
|
||||
onChange: (partialModel: Partial<T>) => void;
|
||||
}
|
||||
|
||||
export function handleChange<T, P extends DocType>(props: CollectionActionsProps<T>, doc: P) {
|
||||
const { model, name } = props;
|
||||
const collection = ((model[name] as unknown) as DocType[]) || [];
|
||||
const part = { [name]: collection.map((row) => (row.id === doc.id ? doc : row)) };
|
||||
props.onChange({ ...model, ...part });
|
||||
}
|
||||
|
||||
export function handleDelete<T, P extends DocType>(props: CollectionActionsProps<T>, doc: P) {
|
||||
const { model, name } = props;
|
||||
const collection = ((model[name] as unknown) as DocType[]) || [];
|
||||
const part = { [name]: collection.filter((row) => row.id !== doc.id) };
|
||||
props.onChange?.({ ...model, ...part });
|
||||
}
|
||||
|
||||
export function handleAdd<T>(props: CollectionActionsProps<T>, fn = newFn) {
|
||||
const { model, name } = props;
|
||||
const collection = ((model[name] as unknown) as DocType[]) || [];
|
||||
const part = { [name]: collection.concat([fn()]) };
|
||||
props.onChange?.({ ...model, ...part });
|
||||
}
|
||||
|
||||
export const collectionActions = { handleAdd, handleDelete, handleChange };
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import { detectIE } from './detect_ie';
|
||||
|
||||
export const createNumberHandler = (handleChange) => {
|
||||
return (name, defaultValue) => (e) => {
|
||||
if (!detectIE() || e.keyCode === 13) e.preventDefault();
|
||||
|
||||
const value = Number(_.get(e, 'target.value', defaultValue));
|
||||
return handleChange?.({ [name]: value });
|
||||
};
|
||||
};
|
|
@ -9,23 +9,25 @@
|
|||
import { createNumberHandler } from './create_number_handler';
|
||||
|
||||
describe('createNumberHandler()', () => {
|
||||
let handleChange;
|
||||
let changeHandler;
|
||||
let event;
|
||||
let handleChange: jest.Mock;
|
||||
let changeHandler: ReturnType<typeof createNumberHandler>;
|
||||
let event: React.ChangeEvent<HTMLInputElement>;
|
||||
|
||||
beforeEach(() => {
|
||||
handleChange = jest.fn();
|
||||
changeHandler = createNumberHandler(handleChange);
|
||||
event = { preventDefault: jest.fn(), target: { value: '1' } };
|
||||
const fn = changeHandler('test');
|
||||
event = ({
|
||||
preventDefault: jest.fn(),
|
||||
target: { value: '1' },
|
||||
} as unknown) as React.ChangeEvent<HTMLInputElement>;
|
||||
const fn = changeHandler('unit');
|
||||
fn(event);
|
||||
});
|
||||
|
||||
test('calls handleChange() function with partial', () => {
|
||||
expect(event.preventDefault.mock.calls.length).toEqual(1);
|
||||
expect(handleChange.mock.calls.length).toEqual(1);
|
||||
expect(handleChange.mock.calls[0][0]).toEqual({
|
||||
test: 1,
|
||||
unit: 1,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { MetricsItemsSchema } from '../../../../common/types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
export const createNumberHandler = (
|
||||
handleChange: (partialModel: Partial<TimeseriesVisParams>) => void
|
||||
) => {
|
||||
return (name: keyof MetricsItemsSchema, defaultValue?: string) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => handleChange?.({ [name]: Number(e.target.value ?? defaultValue) });
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import { detectIE } from './detect_ie';
|
||||
|
||||
export const createTextHandler = (handleChange) => {
|
||||
return (name, defaultValue) => (e) => {
|
||||
// IE preventDefault breaks input, but we still need top prevent enter from being pressed
|
||||
if (!detectIE() || e.keyCode === 13) e.preventDefault();
|
||||
|
||||
const value = _.get(e, 'target.value', defaultValue);
|
||||
return handleChange?.({ [name]: value });
|
||||
};
|
||||
};
|
|
@ -9,23 +9,25 @@
|
|||
import { createTextHandler } from './create_text_handler';
|
||||
|
||||
describe('createTextHandler()', () => {
|
||||
let handleChange;
|
||||
let changeHandler;
|
||||
let event;
|
||||
let handleChange: jest.Mock;
|
||||
let changeHandler: ReturnType<typeof createTextHandler>;
|
||||
let event: React.ChangeEvent<HTMLInputElement>;
|
||||
|
||||
beforeEach(() => {
|
||||
handleChange = jest.fn();
|
||||
changeHandler = createTextHandler(handleChange);
|
||||
event = { preventDefault: jest.fn(), target: { value: 'foo' } };
|
||||
const fn = changeHandler('test');
|
||||
event = ({
|
||||
preventDefault: jest.fn(),
|
||||
target: { value: 'foo' },
|
||||
} as unknown) as React.ChangeEvent<HTMLInputElement>;
|
||||
const fn = changeHandler('axis_scale');
|
||||
fn(event);
|
||||
});
|
||||
|
||||
test('calls handleChange() function with partial', () => {
|
||||
expect(event.preventDefault.mock.calls.length).toEqual(1);
|
||||
expect(handleChange.mock.calls.length).toEqual(1);
|
||||
expect(handleChange.mock.calls[0][0]).toEqual({
|
||||
test: 'foo',
|
||||
axis_scale: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
// TODO: replace with explicit callback in each component
|
||||
export const createTextHandler = (
|
||||
handleChange: (partialModel: Partial<TimeseriesVisParams>) => void
|
||||
) => {
|
||||
return (name: keyof TimeseriesVisParams, defaultValue?: string) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => handleChange({ [name]: e.target.value ?? defaultValue });
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function detectIE() {
|
||||
const ua = window.navigator.userAgent;
|
||||
|
||||
const msie = ua.indexOf('MSIE ');
|
||||
if (msie > 0) {
|
||||
// IE 10 or older => return version number
|
||||
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
|
||||
}
|
||||
|
||||
const trident = ua.indexOf('Trident/');
|
||||
if (trident > 0) {
|
||||
// IE 11 => return version number
|
||||
const rv = ua.indexOf('rv:');
|
||||
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
|
||||
}
|
||||
|
||||
const edge = ua.indexOf('Edge/');
|
||||
if (edge > 0) {
|
||||
// Edge (IE 12+) => return version number
|
||||
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
|
||||
}
|
||||
|
||||
// other browser
|
||||
return false;
|
||||
}
|
|
@ -7,34 +7,35 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from '@kbn/test/jest';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
jest.mock('../lib/get_default_query_language', () => ({
|
||||
getDefaultQueryLanguage: () => 'kuery',
|
||||
}));
|
||||
|
||||
import { GaugePanelConfig } from './gauge';
|
||||
import { PanelConfigProps } from './types';
|
||||
|
||||
describe('GaugePanelConfig', () => {
|
||||
it('call switch tab onChange={handleChange}', () => {
|
||||
const props = {
|
||||
const props = ({
|
||||
fields: {},
|
||||
model: {},
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
const wrapper = shallowWithIntl(<GaugePanelConfig.WrappedComponent {...props} />);
|
||||
} as unknown) as PanelConfigProps;
|
||||
const wrapper = shallow(<GaugePanelConfig {...props} />);
|
||||
|
||||
wrapper.find('EuiTab').first().simulate('onClick');
|
||||
expect(props.onChange).toBeCalled();
|
||||
});
|
||||
|
||||
it('call onChange={handleChange}', () => {
|
||||
const props = {
|
||||
const props = ({
|
||||
fields: {},
|
||||
model: {},
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
const wrapper = shallowWithIntl(<GaugePanelConfig.WrappedComponent {...props} />);
|
||||
} as unknown) as PanelConfigProps;
|
||||
const wrapper = shallow(<GaugePanelConfig {...props} />);
|
||||
|
||||
wrapper.simulate('onClick');
|
||||
expect(props.onChange).toBeCalled();
|
|
@ -6,16 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { createTextHandler } from '../lib/create_text_handler';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import uuid from 'uuid';
|
||||
import { YesNo } from '../yes_no';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiComboBox,
|
||||
|
@ -31,26 +23,40 @@ import {
|
|||
EuiTitle,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Writable } from '@kbn/utility-types';
|
||||
|
||||
// @ts-ignore
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { YesNo } from '../yes_no';
|
||||
|
||||
import { limitOfSeries } from '../../../../common/ui_restrictions';
|
||||
import { PANEL_TYPES } from '../../../../common/panel_types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
|
||||
class GaugePanelConfigUi extends Component {
|
||||
constructor(props) {
|
||||
export class GaugePanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'data' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.DATA };
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
const { model } = this.props;
|
||||
const parts = {};
|
||||
if (
|
||||
!model.gauge_color_rules ||
|
||||
(model.gauge_color_rules && model.gauge_color_rules.length === 0)
|
||||
) {
|
||||
const parts: Writable<Partial<TimeseriesVisParams>> = {};
|
||||
if (!model.gauge_color_rules || !model.gauge_color_rules.length) {
|
||||
parts.gauge_color_rules = [{ id: uuid.v1() }];
|
||||
}
|
||||
if (model.gauge_width == null) parts.gauge_width = 10;
|
||||
|
@ -59,14 +65,17 @@ class GaugePanelConfigUi extends Component {
|
|||
this.props.onChange(parts);
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
handleTextChange = (name: keyof TimeseriesVisParams) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => this.props.onChange({ [name]: e.target.value });
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
const { intl } = this.props;
|
||||
const defaults = {
|
||||
const defaults: Partial<TimeseriesVisParams> = {
|
||||
gauge_max: '',
|
||||
filter: { query: '', language: getDefaultQueryLanguage() },
|
||||
gauge_style: 'circle',
|
||||
|
@ -75,41 +84,34 @@ class GaugePanelConfigUi extends Component {
|
|||
};
|
||||
const model = { ...defaults, ...this.props.model };
|
||||
const handleSelectChange = createSelectHandler(this.props.onChange);
|
||||
const handleTextChange = createTextHandler(this.props.onChange);
|
||||
const styleOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.gauge.styleOptions.circleLabel',
|
||||
label: i18n.translate('visTypeTimeseries.gauge.styleOptions.circleLabel', {
|
||||
defaultMessage: 'Circle',
|
||||
}),
|
||||
value: 'circle',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.gauge.styleOptions.halfCircleLabel',
|
||||
label: i18n.translate('visTypeTimeseries.gauge.styleOptions.halfCircleLabel', {
|
||||
defaultMessage: 'Half Circle',
|
||||
}),
|
||||
value: 'half',
|
||||
},
|
||||
];
|
||||
const htmlId = htmlIdGenerator();
|
||||
const selectedGaugeStyleOption = styleOptions.find((option) => {
|
||||
return model.gauge_style === option.value;
|
||||
});
|
||||
let view;
|
||||
if (selectedTab === 'data') {
|
||||
view = (
|
||||
const selectedGaugeStyleOption = styleOptions.find(
|
||||
(option) => model.gauge_style === option.value
|
||||
);
|
||||
const view =
|
||||
selectedTab === PANEL_CONFIG_TABS.DATA ? (
|
||||
<SeriesEditor
|
||||
colorPicker={true}
|
||||
fields={this.props.fields}
|
||||
limit={limitOfSeries[PANEL_TYPES.GAUGE]}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
view = (
|
||||
) : (
|
||||
<div className="tvbPanelConfig__container">
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s">
|
||||
|
@ -144,10 +146,12 @@ class GaugePanelConfigUi extends Component {
|
|||
>
|
||||
<QueryBarWrapper
|
||||
query={{
|
||||
language: model.filter.language || getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
language: model.filter?.language || getDefaultQueryLanguage(),
|
||||
query: model.filter?.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -193,15 +197,8 @@ class GaugePanelConfigUi extends Component {
|
|||
/>
|
||||
}
|
||||
>
|
||||
{/*
|
||||
EUITODO: The following input couldn't be converted to EUI because of type mis-match.
|
||||
It accepts a null value, but is passed a empty string.
|
||||
*/}
|
||||
<input
|
||||
id={htmlId('gaugeMax')}
|
||||
className="tvbAgg__input"
|
||||
type="number"
|
||||
onChange={handleTextChange('gauge_max')}
|
||||
<EuiFieldNumber
|
||||
onChange={this.handleTextChange('gauge_max')}
|
||||
value={model.gauge_max}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -236,7 +233,7 @@ class GaugePanelConfigUi extends Component {
|
|||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
onChange={handleTextChange('gauge_inner_width')}
|
||||
onChange={this.handleTextChange('gauge_inner_width')}
|
||||
value={Number(model.gauge_inner_width)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -252,7 +249,7 @@ class GaugePanelConfigUi extends Component {
|
|||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
onChange={handleTextChange('gauge_width')}
|
||||
onChange={this.handleTextChange('gauge_width')}
|
||||
value={Number(model.gauge_width)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -317,17 +314,23 @@ class GaugePanelConfigUi extends Component {
|
|||
</EuiPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab isSelected={selectedTab === 'data'} onClick={() => this.switchTab('data')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.gauge.dataTab.dataButtonLabel"
|
||||
defaultMessage="Data"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={selectedTab === 'options'} onClick={() => this.switchTab('options')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.gauge.optionsTab.panelOptionsButtonLabel"
|
||||
defaultMessage="Panel options"
|
||||
|
@ -339,11 +342,3 @@ class GaugePanelConfigUi extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
GaugePanelConfigUi.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export const GaugePanelConfig = injectI18n(GaugePanelConfigUi);
|
|
@ -6,25 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// these are not typed yet
|
||||
// @ts-expect-error
|
||||
import { TimeseriesPanelConfig as timeseries } from './timeseries';
|
||||
// @ts-expect-error
|
||||
import { MetricPanelConfig as metric } from './metric';
|
||||
// @ts-expect-error
|
||||
import { TopNPanelConfig as topN } from './top_n';
|
||||
// @ts-expect-error
|
||||
import { TablePanelConfig as table } from './table';
|
||||
// @ts-expect-error
|
||||
import { GaugePanelConfig as gauge } from './gauge';
|
||||
// @ts-expect-error
|
||||
import { MarkdownPanelConfig as markdown } from './markdown';
|
||||
|
||||
export const panelConfigTypes = {
|
||||
timeseries,
|
||||
table,
|
||||
metric,
|
||||
top_n: topN,
|
||||
gauge,
|
||||
markdown,
|
||||
};
|
||||
export { PanelConfig } from './panel_config';
|
||||
|
|
|
@ -6,16 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import 'brace/mode/less';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import { YesNo } from '../yes_no';
|
||||
import { MarkdownEditor } from '../markdown_editor';
|
||||
import less from 'less/lib/less-browser';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiComboBox,
|
||||
|
@ -31,35 +22,61 @@ import {
|
|||
EuiHorizontalRule,
|
||||
EuiCodeEditor,
|
||||
} from '@elastic/eui';
|
||||
const lessC = less(window, { env: 'production' });
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
// @ts-expect-error
|
||||
import less from 'less/lib/less-browser';
|
||||
import 'brace/mode/less';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Writable } from '@kbn/utility-types';
|
||||
|
||||
// @ts-expect-error not typed yet
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import { YesNo } from '../yes_no';
|
||||
// @ts-expect-error not typed yet
|
||||
import { MarkdownEditor } from '../markdown_editor';
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
class MarkdownPanelConfigUi extends Component {
|
||||
constructor(props) {
|
||||
const lessC = less(window, { env: 'production' });
|
||||
|
||||
export class MarkdownPanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'markdown' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.MARKDOWN };
|
||||
this.handleCSSChange = this.handleCSSChange.bind(this);
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
handleCSSChange(value) {
|
||||
handleCSSChange(value: string) {
|
||||
const { model } = this.props;
|
||||
const lessSrc = `#markdown-${model.id} {
|
||||
${value}
|
||||
}`;
|
||||
lessC.render(lessSrc, { compress: true, javascriptEnabled: false }, (e, output) => {
|
||||
const parts = { markdown_less: value };
|
||||
if (output) {
|
||||
parts.markdown_css = output.css;
|
||||
const lessSrc = `#markdown-${model.id} {${value}}`;
|
||||
lessC.render(
|
||||
lessSrc,
|
||||
{ compress: true, javascriptEnabled: false },
|
||||
(e: unknown, output: any) => {
|
||||
const parts: Writable<Pick<TimeseriesVisParams, 'markdown_less' | 'markdown_css'>> = {
|
||||
markdown_less: value,
|
||||
};
|
||||
if (output) {
|
||||
parts.markdown_css = output.css;
|
||||
}
|
||||
this.props.onChange(parts);
|
||||
}
|
||||
this.props.onChange(parts);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -67,28 +84,23 @@ class MarkdownPanelConfigUi extends Component {
|
|||
const model = { ...defaults, ...this.props.model };
|
||||
const { selectedTab } = this.state;
|
||||
const handleSelectChange = createSelectHandler(this.props.onChange);
|
||||
const { intl } = this.props;
|
||||
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
||||
const alignOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.markdown.alignOptions.topLabel',
|
||||
label: i18n.translate('visTypeTimeseries.markdown.alignOptions.topLabel', {
|
||||
defaultMessage: 'Top',
|
||||
}),
|
||||
value: 'top',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.markdown.alignOptions.middleLabel',
|
||||
label: i18n.translate('visTypeTimeseries.markdown.alignOptions.middleLabel', {
|
||||
defaultMessage: 'Middle',
|
||||
}),
|
||||
value: 'middle',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.markdown.alignOptions.bottomLabel',
|
||||
label: i18n.translate('visTypeTimeseries.markdown.alignOptions.bottomLabel', {
|
||||
defaultMessage: 'Bottom',
|
||||
}),
|
||||
value: 'bottom',
|
||||
|
@ -98,19 +110,18 @@ class MarkdownPanelConfigUi extends Component {
|
|||
return model.markdown_vertical_align === option.value;
|
||||
});
|
||||
let view;
|
||||
if (selectedTab === 'markdown') {
|
||||
if (selectedTab === PANEL_CONFIG_TABS.MARKDOWN) {
|
||||
view = (
|
||||
<VisDataContext.Consumer>
|
||||
{(visData) => <MarkdownEditor visData={visData} {...this.props} />}
|
||||
</VisDataContext.Consumer>
|
||||
);
|
||||
} else if (selectedTab === 'data') {
|
||||
} else if (selectedTab === PANEL_CONFIG_TABS.DATA) {
|
||||
view = (
|
||||
<SeriesEditor
|
||||
colorPicker={false}
|
||||
fields={this.props.fields}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
|
@ -150,12 +161,12 @@ class MarkdownPanelConfigUi extends Component {
|
|||
>
|
||||
<QueryBarWrapper
|
||||
query={{
|
||||
language: model.filter.language
|
||||
? model.filter.language
|
||||
: getDefaultQueryLanguage(),
|
||||
language: model.filter.language || getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -275,7 +286,7 @@ class MarkdownPanelConfigUi extends Component {
|
|||
width="100%"
|
||||
name={`ace-css-${model.id}`}
|
||||
setOptions={{ fontSize: '14px' }}
|
||||
value={model.markdown_less}
|
||||
value={model.markdown_less ?? ''}
|
||||
onChange={this.handleCSSChange}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
@ -286,16 +297,16 @@ class MarkdownPanelConfigUi extends Component {
|
|||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab
|
||||
isSelected={selectedTab === 'markdown'}
|
||||
onClick={() => this.switchTab('markdown')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.MARKDOWN}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.MARKDOWN)}
|
||||
data-test-subj="markdown-subtab"
|
||||
>
|
||||
Markdown
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
data-test-subj="data-subtab"
|
||||
isSelected={selectedTab === 'data'}
|
||||
onClick={() => this.switchTab('data')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.markdown.dataTab.dataButtonLabel"
|
||||
|
@ -303,8 +314,8 @@ class MarkdownPanelConfigUi extends Component {
|
|||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === 'options'}
|
||||
onClick={() => this.switchTab('options')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="options-subtab"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -318,11 +329,3 @@ class MarkdownPanelConfigUi extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownPanelConfigUi.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export const MarkdownPanelConfig = injectI18n(MarkdownPanelConfigUi);
|
|
@ -6,12 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { YesNo } from '../yes_no';
|
||||
import uuid from 'uuid';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
|
@ -28,15 +23,27 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
// @ts-expect-error
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { YesNo } from '../yes_no';
|
||||
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { limitOfSeries } from '../../../../common/ui_restrictions';
|
||||
import { PANEL_TYPES } from '../../../../common/panel_types';
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
|
||||
export class MetricPanelConfig extends Component {
|
||||
constructor(props) {
|
||||
export class MetricPanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'data' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.DATA };
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
|
@ -51,7 +58,7 @@ export class MetricPanelConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
|
@ -60,20 +67,16 @@ export class MetricPanelConfig extends Component {
|
|||
const defaults = { filter: { query: '', language: getDefaultQueryLanguage() } };
|
||||
const model = { ...defaults, ...this.props.model };
|
||||
const htmlId = htmlIdGenerator();
|
||||
let view;
|
||||
if (selectedTab === 'data') {
|
||||
view = (
|
||||
const view =
|
||||
selectedTab === PANEL_CONFIG_TABS.DATA ? (
|
||||
<SeriesEditor
|
||||
colorPicker={false}
|
||||
fields={this.props.fields}
|
||||
limit={limitOfSeries[PANEL_TYPES.METRIC]}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
view = (
|
||||
) : (
|
||||
<div className="tvbPanelConfig__container">
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s">
|
||||
|
@ -111,7 +114,9 @@ export class MetricPanelConfig extends Component {
|
|||
language: model.filter.language || getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -154,19 +159,22 @@ export class MetricPanelConfig extends Component {
|
|||
</EuiPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab isSelected={selectedTab === 'data'} onClick={() => this.switchTab('data')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.metric.dataTab.dataButtonLabel"
|
||||
defaultMessage="Data"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === 'options'}
|
||||
onClick={() => this.switchTab('options')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="metricEditorPanelOptionsBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -180,9 +188,3 @@ export class MetricPanelConfig extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
MetricPanelConfig.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
|
@ -8,28 +8,31 @@
|
|||
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { TimeseriesVisData } from '../../../common/types';
|
||||
import { FormValidationContext } from '../contexts/form_validation_context';
|
||||
import { VisDataContext } from '../contexts/vis_data_context';
|
||||
import { panelConfigTypes } from './panel_config/index';
|
||||
import { TimeseriesVisParams } from '../../types';
|
||||
import { VisFields } from '../lib/fetch_fields';
|
||||
import { TimeseriesVisData } from '../../../../common/types';
|
||||
import { FormValidationContext } from '../../contexts/form_validation_context';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
import { PanelConfigProps } from './types';
|
||||
import { TimeseriesPanelConfig as timeseries } from './timeseries';
|
||||
import { MetricPanelConfig as metric } from './metric';
|
||||
import { TopNPanelConfig as topN } from './top_n';
|
||||
import { TablePanelConfig as table } from './table';
|
||||
import { GaugePanelConfig as gauge } from './gauge';
|
||||
import { MarkdownPanelConfig as markdown } from './markdown';
|
||||
|
||||
const panelConfigTypes = {
|
||||
timeseries,
|
||||
table,
|
||||
metric,
|
||||
top_n: topN,
|
||||
gauge,
|
||||
markdown,
|
||||
};
|
||||
|
||||
interface FormValidationResults {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
interface PanelConfigProps {
|
||||
fields?: VisFields;
|
||||
model: TimeseriesVisParams;
|
||||
visData$: Observable<TimeseriesVisData | undefined>;
|
||||
getConfig: IUiSettingsClient['get'];
|
||||
onChange: (partialModel: Partial<TimeseriesVisParams>) => void;
|
||||
}
|
||||
|
||||
const checkModelValidity = (validationResults: FormValidationResults) =>
|
||||
Object.values(validationResults).every((isValid) => isValid);
|
||||
|
|
@ -7,14 +7,8 @@
|
|||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FieldSelect } from '../aggs/field_select';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createTextHandler } from '../lib/create_text_handler';
|
||||
import { get } from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
import { YesNo } from '../yes_no';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiTabs,
|
||||
|
@ -30,36 +24,49 @@ import {
|
|||
EuiHorizontalRule,
|
||||
EuiCode,
|
||||
EuiText,
|
||||
EuiComboBoxOptionOption,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { FieldSelect } from '../aggs/field_select';
|
||||
// @ts-expect-error not typed yet
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { YesNo } from '../yes_no';
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
import { BUCKET_TYPES } from '../../../../common/metric_types';
|
||||
export class TablePanelConfig extends Component {
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
export class TablePanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
static contextType = VisDataContext;
|
||||
constructor(props) {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'data' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.DATA };
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
const { model } = this.props;
|
||||
const parts = {};
|
||||
if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) {
|
||||
parts.bar_color_rules = [{ id: uuid.v1() }];
|
||||
if (!model.bar_color_rules || !model.bar_color_rules.length) {
|
||||
this.props.onChange({ bar_color_rules: [{ id: uuid.v1() }] });
|
||||
}
|
||||
this.props.onChange(parts);
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
handlePivotChange = (selectedOption) => {
|
||||
handlePivotChange = (selectedOption: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
const { fields, model } = this.props;
|
||||
const pivotId = get(selectedOption, '[0].value', null);
|
||||
const field = fields[model.index_pattern].find((field) => field.name === pivotId);
|
||||
const field = fields[model.index_pattern].find((f) => f.name === pivotId);
|
||||
const pivotType = get(field, 'type', model.pivot_type);
|
||||
|
||||
this.props.onChange({
|
||||
|
@ -68,6 +75,10 @@ export class TablePanelConfig extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
handleTextChange = (name: keyof TimeseriesVisParams) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => this.props.onChange({ [name]: e.target.value });
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
const defaults = {
|
||||
|
@ -78,11 +89,9 @@ export class TablePanelConfig extends Component {
|
|||
pivot_type: '',
|
||||
};
|
||||
const model = { ...defaults, ...this.props.model };
|
||||
const handleTextChange = createTextHandler(this.props.onChange);
|
||||
const htmlId = htmlIdGenerator();
|
||||
let view;
|
||||
if (selectedTab === 'data') {
|
||||
view = (
|
||||
const view =
|
||||
selectedTab === PANEL_CONFIG_TABS.DATA ? (
|
||||
<div>
|
||||
<div className="tvbPanelConfig__container">
|
||||
<EuiPanel>
|
||||
|
@ -114,7 +123,6 @@ export class TablePanelConfig extends Component {
|
|||
onChange={this.handlePivotChange}
|
||||
uiRestrictions={this.context.uiRestrictions}
|
||||
type={BUCKET_TYPES.TERMS}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
@ -131,8 +139,8 @@ export class TablePanelConfig extends Component {
|
|||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="columnLabelName"
|
||||
onChange={handleTextChange('pivot_label')}
|
||||
value={model.pivot_label}
|
||||
onChange={this.handleTextChange('pivot_label')}
|
||||
value={model.pivot_label ?? ''}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -154,8 +162,8 @@ export class TablePanelConfig extends Component {
|
|||
<input
|
||||
className="tvbAgg__input"
|
||||
type="number"
|
||||
onChange={handleTextChange('pivot_rows')}
|
||||
value={model.pivot_rows}
|
||||
onChange={this.handleTextChange('pivot_rows')}
|
||||
value={model.pivot_rows ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
@ -166,13 +174,10 @@ export class TablePanelConfig extends Component {
|
|||
<SeriesEditor
|
||||
fields={this.props.fields}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
view = (
|
||||
) : (
|
||||
<div className="tvbPanelConfig__container">
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s">
|
||||
|
@ -203,8 +208,8 @@ export class TablePanelConfig extends Component {
|
|||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
onChange={handleTextChange('drilldown_url')}
|
||||
value={model.drilldown_url}
|
||||
onChange={this.handleTextChange('drilldown_url')}
|
||||
value={model.drilldown_url ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -237,7 +242,9 @@ export class TablePanelConfig extends Component {
|
|||
: getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -251,7 +258,6 @@ export class TablePanelConfig extends Component {
|
|||
</EuiFormLabel>
|
||||
<EuiSpacer size="m" />
|
||||
<YesNo
|
||||
id={htmlId('globalFilterOption')}
|
||||
value={model.ignore_global_filter}
|
||||
name="ignore_global_filter"
|
||||
onChange={this.props.onChange}
|
||||
|
@ -261,17 +267,23 @@ export class TablePanelConfig extends Component {
|
|||
</EuiPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab isSelected={selectedTab === 'data'} onClick={() => this.switchTab('data')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.dataTab.columnsButtonLabel"
|
||||
defaultMessage="Columns"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={selectedTab === 'options'} onClick={() => this.switchTab('options')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.optionsTab.panelOptionsButtonLabel"
|
||||
defaultMessage="Panel options"
|
||||
|
@ -283,9 +295,3 @@ export class TablePanelConfig extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePanelConfig.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
|
@ -6,15 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { AnnotationsEditor } from '../annotations_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { createTextHandler } from '../lib/create_text_handler';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import { YesNo } from '../yes_no';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiComboBox,
|
||||
|
@ -30,20 +22,104 @@ import {
|
|||
EuiTitle,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
class TimeseriesPanelConfigUi extends Component {
|
||||
constructor(props) {
|
||||
// @ts-expect-error not typed yet
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { AnnotationsEditor } from '../annotations_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import { YesNo } from '../yes_no';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
const positionOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.positionOptions.rightLabel', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.positionOptions.leftLabel', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
value: 'left',
|
||||
},
|
||||
];
|
||||
const tooltipModeOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.tooltipOptions.showAll', {
|
||||
defaultMessage: 'Show all values',
|
||||
}),
|
||||
value: 'show_all',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.tooltipOptions.showFocused', {
|
||||
defaultMessage: 'Show focused values',
|
||||
}),
|
||||
value: 'show_focused',
|
||||
},
|
||||
];
|
||||
const scaleOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.scaleOptions.normalLabel', {
|
||||
defaultMessage: 'Normal',
|
||||
}),
|
||||
value: 'normal',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.scaleOptions.logLabel', {
|
||||
defaultMessage: 'Log',
|
||||
}),
|
||||
value: 'log',
|
||||
},
|
||||
];
|
||||
const legendPositionOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.legendPositionOptions.rightLabel', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.legendPositionOptions.leftLabel', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.timeseries.legendPositionOptions.bottomLabel', {
|
||||
defaultMessage: 'Bottom',
|
||||
}),
|
||||
value: 'bottom',
|
||||
},
|
||||
];
|
||||
|
||||
export class TimeseriesPanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'data' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.DATA };
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
handleTextChange = (name: keyof TimeseriesVisParams) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => this.props.onChange({ [name]: e.target.value });
|
||||
|
||||
render() {
|
||||
const defaults = {
|
||||
filter: { query: '', language: getDefaultQueryLanguage() },
|
||||
|
@ -56,106 +132,31 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
const model = { ...defaults, ...this.props.model };
|
||||
const { selectedTab } = this.state;
|
||||
const handleSelectChange = createSelectHandler(this.props.onChange);
|
||||
const handleTextChange = createTextHandler(this.props.onChange);
|
||||
const htmlId = htmlIdGenerator();
|
||||
const { intl } = this.props;
|
||||
|
||||
const positionOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.positionOptions.rightLabel',
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.positionOptions.leftLabel',
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
value: 'left',
|
||||
},
|
||||
];
|
||||
const tooltipModeOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.tooltipOptions.showAll',
|
||||
defaultMessage: 'Show all values',
|
||||
}),
|
||||
value: 'show_all',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.tooltipOptions.showFocused',
|
||||
defaultMessage: 'Show focused values',
|
||||
}),
|
||||
value: 'show_focused',
|
||||
},
|
||||
];
|
||||
const selectedPositionOption = positionOptions.find((option) => {
|
||||
return model.axis_position === option.value;
|
||||
});
|
||||
const scaleOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.scaleOptions.normalLabel',
|
||||
defaultMessage: 'Normal',
|
||||
}),
|
||||
value: 'normal',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.scaleOptions.logLabel',
|
||||
defaultMessage: 'Log',
|
||||
}),
|
||||
value: 'log',
|
||||
},
|
||||
];
|
||||
const selectedAxisScaleOption = scaleOptions.find((option) => {
|
||||
return model.axis_scale === option.value;
|
||||
});
|
||||
const legendPositionOptions = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.legendPositionOptions.rightLabel',
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.legendPositionOptions.leftLabel',
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'visTypeTimeseries.timeseries.legendPositionOptions.bottomLabel',
|
||||
defaultMessage: 'Bottom',
|
||||
}),
|
||||
value: 'bottom',
|
||||
},
|
||||
];
|
||||
const selectedLegendPosOption = legendPositionOptions.find((option) => {
|
||||
return model.legend_position === option.value;
|
||||
});
|
||||
|
||||
const selectedTooltipMode = tooltipModeOptions.find((option) => {
|
||||
return model.tooltip_mode === option.value;
|
||||
});
|
||||
const selectedPositionOption = positionOptions.find(
|
||||
(option) => model.axis_position === option.value
|
||||
);
|
||||
const selectedAxisScaleOption = scaleOptions.find(
|
||||
(option) => model.axis_scale === option.value
|
||||
);
|
||||
const selectedLegendPosOption = legendPositionOptions.find(
|
||||
(option) => model.legend_position === option.value
|
||||
);
|
||||
const selectedTooltipMode = tooltipModeOptions.find(
|
||||
(option) => model.tooltip_mode === option.value
|
||||
);
|
||||
|
||||
let view;
|
||||
if (selectedTab === 'data') {
|
||||
if (selectedTab === PANEL_CONFIG_TABS.DATA) {
|
||||
view = (
|
||||
<SeriesEditor
|
||||
fields={this.props.fields}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
} else if (selectedTab === 'annotations') {
|
||||
} else if (selectedTab === PANEL_CONFIG_TABS.ANNOTATIONS) {
|
||||
view = (
|
||||
<AnnotationsEditor
|
||||
fields={this.props.fields}
|
||||
|
@ -204,7 +205,9 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
language: model.filter.language || getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -250,7 +253,10 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText onChange={handleTextChange('axis_min')} value={model.axis_min} />
|
||||
<EuiFieldText
|
||||
onChange={this.handleTextChange('axis_min')}
|
||||
value={model.axis_min ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -263,7 +269,10 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText onChange={handleTextChange('axis_max')} value={model.axis_max} />
|
||||
<EuiFieldText
|
||||
onChange={this.handleTextChange('axis_max')}
|
||||
value={model.axis_max ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -394,15 +403,18 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
return (
|
||||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab isSelected={selectedTab === 'data'} onClick={() => this.switchTab('data')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.timeseries.dataTab.dataButtonLabel"
|
||||
defaultMessage="Data"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === 'options'}
|
||||
onClick={() => this.switchTab('options')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="timeSeriesEditorPanelOptionsBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -411,8 +423,8 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === 'annotations'}
|
||||
onClick={() => this.switchTab('annotations')}
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.ANNOTATIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.ANNOTATIONS)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.timeseries.annotationsTab.annotationsButtonLabel"
|
||||
|
@ -425,11 +437,3 @@ class TimeseriesPanelConfigUi extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
TimeseriesPanelConfigUi.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export const TimeseriesPanelConfig = injectI18n(TimeseriesPanelConfigUi);
|
|
@ -6,15 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { createTextHandler } from '../lib/create_text_handler';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import uuid from 'uuid';
|
||||
import { YesNo } from '../yes_no';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiTabs,
|
||||
|
@ -31,28 +24,44 @@ import {
|
|||
EuiCode,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
|
||||
export class TopNPanelConfig extends Component {
|
||||
constructor(props) {
|
||||
// @ts-expect-error not typed yet
|
||||
import { SeriesEditor } from '../series_editor';
|
||||
// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
|
||||
import { IndexPattern } from '../index_pattern';
|
||||
import { ColorRules } from '../color_rules';
|
||||
import { ColorPicker } from '../color_picker';
|
||||
import { YesNo } from '../yes_no';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
|
||||
export class TopNPanelConfig extends Component<
|
||||
PanelConfigProps,
|
||||
{ selectedTab: PANEL_CONFIG_TABS }
|
||||
> {
|
||||
constructor(props: PanelConfigProps) {
|
||||
super(props);
|
||||
this.state = { selectedTab: 'data' };
|
||||
this.state = { selectedTab: PANEL_CONFIG_TABS.DATA };
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
const { model } = this.props;
|
||||
const parts = {};
|
||||
if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) {
|
||||
parts.bar_color_rules = [{ id: uuid.v1() }];
|
||||
if (!model.bar_color_rules || !model.bar_color_rules.length) {
|
||||
this.props.onChange({ bar_color_rules: [{ id: uuid.v1() }] });
|
||||
}
|
||||
this.props.onChange(parts);
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab(selectedTab: PANEL_CONFIG_TABS) {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
|
||||
handleTextChange = (name: keyof TimeseriesVisParams) => (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => this.props.onChange({ [name]: e.target.value });
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
const defaults = {
|
||||
|
@ -61,20 +70,15 @@ export class TopNPanelConfig extends Component {
|
|||
};
|
||||
const model = { ...defaults, ...this.props.model };
|
||||
const htmlId = htmlIdGenerator();
|
||||
const handleTextChange = createTextHandler(this.props.onChange);
|
||||
let view;
|
||||
if (selectedTab === 'data') {
|
||||
view = (
|
||||
const view =
|
||||
selectedTab === PANEL_CONFIG_TABS.DATA ? (
|
||||
<SeriesEditor
|
||||
colorPicker={false}
|
||||
fields={this.props.fields}
|
||||
model={this.props.model}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
view = (
|
||||
) : (
|
||||
<div className="tvbPanelConfig__container">
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s">
|
||||
|
@ -105,8 +109,8 @@ export class TopNPanelConfig extends Component {
|
|||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
onChange={handleTextChange('drilldown_url')}
|
||||
value={model.drilldown_url}
|
||||
onChange={this.handleTextChange('drilldown_url')}
|
||||
value={model.drilldown_url ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -134,12 +138,12 @@ export class TopNPanelConfig extends Component {
|
|||
>
|
||||
<QueryBarWrapper
|
||||
query={{
|
||||
language: model.filter.language
|
||||
? model.filter.language
|
||||
: getDefaultQueryLanguage(),
|
||||
language: model.filter.language || getDefaultQueryLanguage(),
|
||||
query: model.filter.query || '',
|
||||
}}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
onChange={(filter: PanelConfigProps['model']['filter']) =>
|
||||
this.props.onChange({ filter })
|
||||
}
|
||||
indexPatterns={[model.index_pattern || model.default_index_pattern]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -214,17 +218,23 @@ export class TopNPanelConfig extends Component {
|
|||
</EuiPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabs size="s">
|
||||
<EuiTab isSelected={selectedTab === 'data'} onClick={() => this.switchTab('data')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.topN.dataTab.dataButtonLabel"
|
||||
defaultMessage="Data"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={selectedTab === 'options'} onClick={() => this.switchTab('options')}>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.topN.optionsTab.panelOptionsButtonLabel"
|
||||
defaultMessage="Panel options"
|
||||
|
@ -236,9 +246,3 @@ export class TopNPanelConfig extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
TopNPanelConfig.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { Observable } from 'rxjs';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { TimeseriesVisData } from '../../../../common/types';
|
||||
import { TimeseriesVisParams } from '../../../types';
|
||||
import { VisFields } from '../../lib/fetch_fields';
|
||||
|
||||
export interface PanelConfigProps {
|
||||
fields: VisFields;
|
||||
model: TimeseriesVisParams;
|
||||
visData$: Observable<TimeseriesVisData | undefined>;
|
||||
getConfig: IUiSettingsClient['get'];
|
||||
onChange: (partialModel: Partial<TimeseriesVisParams>) => void;
|
||||
}
|
||||
|
||||
export enum PANEL_CONFIG_TABS {
|
||||
DATA = 'data',
|
||||
OPTIONS = 'options',
|
||||
ANNOTATIONS = 'annotations',
|
||||
MARKDOWN = 'markdown',
|
||||
}
|
|
@ -7,29 +7,29 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expect } from 'chai';
|
||||
import { shallowWithIntl } from '@kbn/test/jest';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { YesNo } from './yes_no';
|
||||
|
||||
describe('YesNo', () => {
|
||||
it('call onChange={handleChange} on yes', () => {
|
||||
const handleChange = sinon.spy();
|
||||
const wrapper = shallowWithIntl(<YesNo name="test" onChange={handleChange} />);
|
||||
const handleChange = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<YesNo name="ignore_global_filters" onChange={handleChange} value={0} />
|
||||
);
|
||||
wrapper.find('EuiRadio').first().simulate('change');
|
||||
expect(handleChange.calledOnce).to.equal(true);
|
||||
expect(handleChange.firstCall.args[0]).to.eql({
|
||||
test: 1,
|
||||
expect(handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(handleChange).toHaveBeenCalledWith({
|
||||
ignore_global_filters: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('call onChange={handleChange} on no', () => {
|
||||
const handleChange = sinon.spy();
|
||||
const wrapper = shallowWithIntl(<YesNo name="test" onChange={handleChange} />);
|
||||
const handleChange = jest.fn();
|
||||
const wrapper = shallow(<YesNo name="show_legend" onChange={handleChange} value={1} />);
|
||||
wrapper.find('EuiRadio').last().simulate('change');
|
||||
expect(handleChange.calledOnce).to.equal(true);
|
||||
expect(handleChange.firstCall.args[0]).to.eql({
|
||||
test: 0,
|
||||
expect(handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(handleChange).toHaveBeenCalledWith({
|
||||
show_legend: 0,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,23 +6,35 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiRadio, htmlIdGenerator } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { TimeseriesVisParams } from '../../types';
|
||||
|
||||
export function YesNo(props) {
|
||||
const { name, value, disabled, 'data-test-subj': dataTestSubj } = props;
|
||||
const handleChange = (value) => {
|
||||
const { name } = props;
|
||||
return () => {
|
||||
const parts = { [name]: value };
|
||||
props.onChange(parts);
|
||||
};
|
||||
};
|
||||
interface YesNoProps<ParamName extends keyof TimeseriesVisParams> {
|
||||
name: ParamName;
|
||||
value: TimeseriesVisParams[ParamName];
|
||||
disabled?: boolean;
|
||||
'data-test-subj'?: string;
|
||||
onChange: (partialModel: Partial<TimeseriesVisParams>) => void;
|
||||
}
|
||||
|
||||
export function YesNo<ParamName extends keyof TimeseriesVisParams>({
|
||||
name,
|
||||
value,
|
||||
disabled,
|
||||
'data-test-subj': dataTestSubj,
|
||||
onChange,
|
||||
}: YesNoProps<ParamName>) {
|
||||
const handleChange = useCallback(
|
||||
(val: number) => {
|
||||
return () => onChange({ [name]: val });
|
||||
},
|
||||
[onChange, name]
|
||||
);
|
||||
const htmlId = htmlIdGenerator();
|
||||
const inputName = name + _.uniqueId();
|
||||
const inputName = htmlId(name);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiRadio
|
||||
|
@ -63,12 +75,3 @@ export function YesNo(props) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
YesNo.propTypes = {
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
};
|
||||
|
||||
YesNo.defaultProps = {
|
||||
disabled: false,
|
||||
};
|
Loading…
Reference in a new issue