From 853de830c2c908d7c3601e76896c4edc0a9c2ff4 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Fri, 18 Jun 2021 17:32:17 +0300 Subject: [PATCH] [TSVB] Index pattern select field disappear in Annotation tab (#102314) * [TSVB] Index pattern select field disappear in Annotation tab * Refactor AnnotationsEditor and move renderRow logic into AnnotationRow * Remove duplicated license, add useCallback to functions in AnnotationRow, remove cross refecrence and update types * Refactor AnnotationEditor and AnnotationRow * refactoring * remove extra props * fix nits Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alexey Antonov --- .../application/components/annotation_row.tsx | 280 +++++++++++++++ .../components/annotations_editor.js | 322 ------------------ .../components/annotations_editor.tsx | 113 ++++++ .../components/panel_config/timeseries.tsx | 4 +- .../public/application/components/yes_no.tsx | 8 +- 5 files changed, 398 insertions(+), 329 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js create mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx new file mode 100644 index 00000000000000..715cf4d6709dae --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx @@ -0,0 +1,280 @@ +/* + * 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, { useState, useEffect, useCallback, useMemo, ChangeEvent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiCode, + EuiComboBoxOptionOption, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + htmlIdGenerator, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getDataStart } from '../../services'; +import { KBN_FIELD_TYPES, Query } from '../../../../../plugins/data/public'; + +import { AddDeleteButtons } from './add_delete_buttons'; +import { ColorPicker } from './color_picker'; +import { FieldSelect } from './aggs/field_select'; +import { IndexPatternSelect } from './lib/index_pattern_select'; +import { QueryBarWrapper } from './query_bar_wrapper'; +import { YesNo } from './yes_no'; +import { fetchIndexPattern } from '../../../common/index_patterns_utils'; +import { getDefaultQueryLanguage } from './lib/get_default_query_language'; + +// @ts-expect-error not typed yet +import { IconSelect } from './icon_select/icon_select'; + +import type { Annotation, FetchedIndexPattern, IndexPatternValue } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; + +const INDEX_PATTERN_KEY = 'index_pattern'; +const TIME_FIELD_KEY = 'time_field'; + +export interface AnnotationRowProps { + annotation: Annotation; + fields: VisFields; + onChange: (partialModel: Partial) => void; + handleAdd: () => void; + handleDelete: () => void; +} + +const getAnnotationDefaults = () => ({ + fields: '', + template: '', + index_pattern: '', + query_string: { query: '', language: getDefaultQueryLanguage() }, +}); + +export const AnnotationRow = ({ + annotation, + fields, + onChange, + handleAdd, + handleDelete, +}: AnnotationRowProps) => { + const model = useMemo(() => ({ ...getAnnotationDefaults(), ...annotation }), [annotation]); + const htmlId = htmlIdGenerator(model.id); + + const [fetchedIndex, setFetchedIndex] = useState(null); + + useEffect(() => { + const updateFetchedIndex = async (index: IndexPatternValue) => { + const { indexPatterns } = getDataStart(); + + setFetchedIndex( + index + ? await fetchIndexPattern(index, indexPatterns) + : { + indexPattern: undefined, + indexPatternString: undefined, + } + ); + }; + + updateFetchedIndex(model.index_pattern); + }, [model.index_pattern]); + + const togglePanelActivation = useCallback( + () => + onChange({ + hidden: !model.hidden, + }), + [model.hidden, onChange] + ); + + const handleChange = useCallback( + (name: string) => ( + event: Array> | ChangeEvent + ) => + onChange({ + [name]: Array.isArray(event) ? event?.[0]?.value : event.target.value, + }), + [onChange] + ); + + const handleQueryChange = useCallback( + (filter: Query) => + onChange({ + query_string: filter, + }), + [onChange] + ); + + return ( +
+ + + + + + + + + + + + + } + restrict={RESTRICT_FIELDS} + value={model.time_field} + onChange={handleChange(TIME_FIELD_KEY)} + indexPattern={model.index_pattern} + fields={fields} + /> + + + + + + + + + } + fullWidth + > + + + + + + + + + + + + + + + + + + + + + } + > + + + + + + } + fullWidth + > + + + + + + } + helpText={ + + {'{{field}}'} }} + /> + + } + fullWidth + > + + + + + + + + + + +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js deleted file mode 100644 index 09ce57639b9523..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js +++ /dev/null @@ -1,322 +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 { i18n } from '@kbn/i18n'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import _ from 'lodash'; -import { collectionActions } from './lib/collection_actions'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; -import { AddDeleteButtons } from './add_delete_buttons'; -import { ColorPicker } from './color_picker'; -import { FieldSelect } from './aggs/field_select'; -import uuid from 'uuid'; -import { IconSelect } from './icon_select/icon_select'; -import { YesNo } from './yes_no'; -import { QueryBarWrapper } from './query_bar_wrapper'; -import { getDefaultQueryLanguage } from './lib/get_default_query_language'; -import { - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiSpacer, - EuiFieldText, - EuiTitle, - EuiButton, - EuiCode, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IndexPatternSelect } from './lib/index_pattern_select'; - -function newAnnotation() { - return { - id: uuid.v1(), - color: '#F00', - index_pattern: '', - time_field: '', - icon: 'fa-tag', - ignore_global_filters: 1, - ignore_panel_filters: 1, - }; -} - -const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; - -export class AnnotationsEditor extends Component { - constructor(props) { - super(props); - this.renderRow = this.renderRow.bind(this); - } - - handleChange(item, name) { - return (e) => { - const handleChange = collectionActions.handleChange.bind(null, this.props); - const part = {}; - part[name] = _.get(e, '[0].value', _.get(e, 'target.value')); - handleChange(_.assign({}, item, part)); - }; - } - - handleQueryChange = (model, filter) => { - const part = { query_string: filter }; - collectionActions.handleChange(this.props, { - ...model, - ...part, - }); - }; - renderRow(row) { - const defaults = { - fields: '', - template: '', - index_pattern: '', - query_string: { query: '', language: getDefaultQueryLanguage() }, - }; - const model = { ...defaults, ...row }; - const handleChange = (part) => { - const fn = collectionActions.handleChange.bind(null, this.props); - fn(_.assign({}, model, part)); - }; - const togglePanelActivation = () => { - handleChange({ - hidden: !model.hidden, - }); - }; - const htmlId = htmlIdGenerator(model.id); - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); - - return ( -
- - - - - - - - - - - - - } - restrict={RESTRICT_FIELDS} - value={model.time_field} - onChange={this.handleChange(model, 'time_field')} - indexPattern={model.index_pattern} - fields={this.props.fields} - fullWidth - /> - - - - - - - - - } - fullWidth - > - this.handleQueryChange(model, query)} - indexPatterns={[model.index_pattern]} - /> - - - - - - - - - - - - - - - - - - - - } - > - - - - - - } - fullWidth - > - - - - - - } - helpText={ - - {'{{field}}'} }} - /> - - } - fullWidth - > - - - - - - - - - - -
- ); - } - - render() { - const { model } = this.props; - let content; - if (!model.annotations || !model.annotations.length) { - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - content = ( - -

- -

- - - -
- ); - } else { - const annotations = model.annotations.map(this.renderRow); - content = ( -
- - - - - - - - {annotations} -
- ); - } - return
{content}
; - } -} - -AnnotationsEditor.defaultProps = { - name: 'annotations', -}; - -AnnotationsEditor.propTypes = { - fields: PropTypes.object, - model: PropTypes.object, - name: PropTypes.string, - onChange: PropTypes.func, - uiSettings: PropTypes.object, -}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx new file mode 100644 index 00000000000000..b3b4993d2ca061 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx @@ -0,0 +1,113 @@ +/* + * 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, { useCallback } from 'react'; +import uuid from 'uuid'; +import { EuiSpacer, EuiTitle, EuiButton, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { AnnotationRow } from './annotation_row'; +import { collectionActions, CollectionActionsProps } from './lib/collection_actions'; + +import type { Panel, Annotation } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +interface AnnotationsEditorProps { + fields: VisFields; + model: Panel; + onChange: (partialModel: Partial) => void; +} + +export const newAnnotation = () => ({ + id: uuid.v1(), + color: '#F00', + index_pattern: '', + time_field: '', + icon: 'fa-tag', + ignore_global_filters: 1, + ignore_panel_filters: 1, +}); + +const NoContent = ({ handleAdd }: { handleAdd: () => void }) => ( + +

+ +

+ + + +
+); + +const getCollectionActionsProps = (props: AnnotationsEditorProps) => + ({ + name: 'annotations', + ...props, + } as CollectionActionsProps); + +export const AnnotationsEditor = (props: AnnotationsEditorProps) => { + const { annotations } = props.model; + + const handleAdd = useCallback( + () => collectionActions.handleAdd(getCollectionActionsProps(props), newAnnotation), + [props] + ); + + const handleDelete = useCallback( + (annotation) => () => + collectionActions.handleDelete(getCollectionActionsProps(props), annotation), + [props] + ); + + const onChange = useCallback( + (annotation: Annotation) => { + return (part: Partial) => + collectionActions.handleChange(getCollectionActionsProps(props), { + ...annotation, + ...part, + }); + }, + [props] + ); + + return ( +
+ {annotations?.length ? ( +
+ + + + + + + {annotations.map((annotation) => ( + + ))} +
+ ) : ( + + )} +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index b57d72ea6bb30c..e1d94f3bf3ebef 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -28,9 +28,8 @@ import { // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; // @ts-expect-error not typed yet -import { AnnotationsEditor } from '../annotations_editor'; -// @ts-expect-error not typed yet import { IndexPattern } from '../index_pattern'; +import { AnnotationsEditor } from '../annotations_editor'; import { createSelectHandler } from '../lib/create_select_handler'; import { ColorPicker } from '../color_picker'; import { YesNo } from '../yes_no'; @@ -162,7 +161,6 @@ export class TimeseriesPanelConfig extends Component< ); diff --git a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx index 3c02deb177f9ec..81ce4f50b03139 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx @@ -11,21 +11,21 @@ import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TimeseriesVisParams } from '../../types'; -interface YesNoProps { - name: ParamName; +interface YesNoProps { + name: string; value: boolean | number | undefined; disabled?: boolean; 'data-test-subj'?: string; onChange: (partialModel: Partial) => void; } -export function YesNo({ +export function YesNo({ name, value, disabled, 'data-test-subj': dataTestSubj, onChange, -}: YesNoProps) { +}: YesNoProps) { const handleChange = useCallback( (val: number) => { return () => onChange({ [name]: val });