diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts index 92dfc6d522957..4f24bc273e265 100644 --- a/src/plugins/vis_type_timeseries/common/constants.ts +++ b/src/plugins/vis_type_timeseries/common/constants.ts @@ -19,3 +19,7 @@ export const MAX_BUCKETS_SETTING = 'metrics:max_buckets'; export const INDEXES_SEPARATOR = ','; + +export const ROUTES = { + VIS_DATA: '/api/metrics/vis/data', +}; diff --git a/src/plugins/vis_type_timeseries/common/panel_types.ts b/src/plugins/vis_type_timeseries/common/panel_types.ts index 6a9420ab6e4c1..6aed5528c23a5 100644 --- a/src/plugins/vis_type_timeseries/common/panel_types.ts +++ b/src/plugins/vis_type_timeseries/common/panel_types.ts @@ -17,11 +17,11 @@ * under the License. */ -export const PANEL_TYPES = { - TABLE: 'table', - GAUGE: 'gauge', - MARKDOWN: 'markdown', - TOP_N: 'top_n', - TIMESERIES: 'timeseries', - METRIC: 'metric', -}; +export enum PANEL_TYPES { + TABLE = 'table', + GAUGE = 'gauge', + MARKDOWN = 'markdown', + TOP_N = 'top_n', + TIMESERIES = 'timeseries', + METRIC = 'metric', +} diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 8973060848b41..f8e1b740fc646 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -19,8 +19,37 @@ import { TypeOf } from '@kbn/config-schema'; import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema'; +import { PANEL_TYPES } from './panel_types'; +import { TimeseriesUIRestrictions } from './ui_restrictions'; export type SeriesItemsSchema = TypeOf; export type MetricsItemsSchema = TypeOf; export type PanelSchema = TypeOf; export type VisPayload = TypeOf; + +interface PanelData { + id: string; + label: string; + data: Array<[number, number]>; +} + +// series data is not fully typed yet +interface SeriesData { + [key: string]: { + annotations: { + [key: string]: unknown[]; + }; + id: string; + series: PanelData[]; + error?: unknown; + }; +} + +export type TimeseriesVisData = SeriesData & { + type: PANEL_TYPES; + uiRestrictions: TimeseriesUIRestrictions; + /** + * series array is responsible only for "table" vis type + */ + series?: unknown[]; +}; diff --git a/src/plugins/vis_type_timeseries/common/ui_restrictions.ts b/src/plugins/vis_type_timeseries/common/ui_restrictions.ts index e2911eb2d70e3..1be428f1b9354 100644 --- a/src/plugins/vis_type_timeseries/common/ui_restrictions.ts +++ b/src/plugins/vis_type_timeseries/common/ui_restrictions.ts @@ -63,7 +63,7 @@ export const DEFAULT_UI_RESTRICTION: UIRestrictions = { * @constant * @public */ -export const limitOfSeries = { +export const limitOfSeries: Partial> = { [PANEL_TYPES.GAUGE]: 1, [PANEL_TYPES.METRIC]: 2, }; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 9ec5ae1424ae3..7f17a9c44298a 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -251,7 +251,14 @@ export const panel = schema.object({ ), time_field: stringOptionalNullable, time_range_mode: stringOptionalNullable, - type: stringRequired, + type: schema.oneOf([ + schema.literal('table'), + schema.literal('gauge'), + schema.literal('markdown'), + schema.literal('top_n'), + schema.literal('timeseries'), + schema.literal('metric'), + ]), }); export const visPayloadSchema = schema.object({ @@ -267,7 +274,6 @@ export const visPayloadSchema = schema.object({ }) ), }), - savedObjectId: schema.maybe(schema.string()), timerange: schema.object({ timezone: stringRequired, min: stringRequired, diff --git a/src/plugins/vis_type_timeseries/public/application/components/no_data.js b/src/plugins/vis_type_timeseries/public/application/components/no_data.js deleted file mode 100644 index 1968f1858a46c..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/no_data.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; - -export function NoDataComponent() { - return ( -
- - -
- ); -} diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx new file mode 100644 index 0000000000000..5b5c99b970854 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx @@ -0,0 +1,115 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useCallback, useEffect } from 'react'; + +import { IUiSettingsClient } from 'src/core/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { PersistedState } from 'src/plugins/visualizations/public'; + +// @ts-expect-error +import { ErrorComponent } from './error'; +import { TimeseriesVisTypes } from './vis_types'; +import { TimeseriesVisParams } from '../../metrics_fn'; +import { TimeseriesVisData } from '../../../common/types'; + +interface TimeseriesVisualizationProps { + className?: string; + getConfig: IUiSettingsClient['get']; + handlers: IInterpreterRenderHandlers; + model: TimeseriesVisParams; + visData: TimeseriesVisData; + uiState: PersistedState; +} + +function TimeseriesVisualization({ + className = 'tvbVis', + visData, + model, + handlers, + uiState, + getConfig, +}: TimeseriesVisualizationProps) { + const onBrush = useCallback( + (gte: string, lte: string) => { + handlers.event({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': { + gte, + lte, + }, + }, + }, + ], + }, + }); + }, + [handlers] + ); + + const handleUiState = useCallback( + (field: string, value: { column: string; order: string }) => { + uiState.set(field, value); + // reload visualization because data might need to be re-fetched + uiState.emit('reload'); + }, + [uiState] + ); + + useEffect(() => { + handlers.done(); + }); + + // Show the error panel + const error = visData[model.id]?.error; + if (error) { + return ( +
+ +
+ ); + } + + const VisComponent = TimeseriesVisTypes[model.type]; + + if (VisComponent) { + return ( + + ); + } + + return
; +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TimeseriesVisualization as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index 57adecd9d598b..083ccaf8e5073 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -23,10 +23,8 @@ import * as Rx from 'rxjs'; import { share } from 'rxjs/operators'; import { isEqual, isEmpty, debounce } from 'lodash'; import { VisEditorVisualization } from './vis_editor_visualization'; -import { Visualization } from './visualization'; import { VisPicker } from './vis_picker'; import { PanelConfig } from './panel_config'; -import { createBrushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../../common/extract_index_patterns'; import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services'; @@ -49,7 +47,6 @@ export class VisEditor extends Component { visFields: props.visFields, extractedIndexPatterns: [''], }; - this.onBrush = createBrushHandler((data) => props.vis.API.events.applyFilter(data)); this.visDataSubject = new Rx.BehaviorSubject(this.props.visData); this.visData$ = this.visDataSubject.asObservable().pipe(share()); @@ -71,12 +68,6 @@ export class VisEditor extends Component { return this.props.config.get(...args); }; - handleUiState = (field, value) => { - this.props.vis.uiState.set(field, value); - // reload visualization because data might need to be re-fetched - this.props.vis.uiState.emit('reload'); - }; - updateVisState = debounce(() => { this.props.vis.params = this.state.model; this.props.embeddableHandler.reload(); @@ -101,16 +92,14 @@ export class VisEditor extends Component { dirty = false; } - if (this.props.isEditorMode) { - const extractedIndexPatterns = extractIndexPatterns(nextModel); - if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) { - fetchFields(extractedIndexPatterns).then((visFields) => - this.setState({ - visFields, - extractedIndexPatterns, - }) - ); - } + const extractedIndexPatterns = extractIndexPatterns(nextModel); + if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) { + fetchFields(extractedIndexPatterns).then((visFields) => + this.setState({ + visFields, + extractedIndexPatterns, + }) + ); } this.setState({ @@ -141,23 +130,6 @@ export class VisEditor extends Component { }; render() { - if (!this.props.isEditorMode) { - if (!this.props.visParams || !this.props.visData) { - return null; - } - return ( - - ); - } - const { model } = this.state; if (model) { @@ -211,23 +183,12 @@ export class VisEditor extends Component { } componentDidMount() { - this.props.renderComplete(); - - if (this.props.isEditorMode && this.props.eventEmitter) { - this.props.eventEmitter.on('updateEditor', this.updateModel); - } - } - - componentDidUpdate() { - this.props.renderComplete(); + this.props.eventEmitter.on('updateEditor', this.updateModel); } componentWillUnmount() { this.updateVisState.cancel(); - - if (this.props.isEditorMode && this.props.eventEmitter) { - this.props.eventEmitter.off('updateEditor', this.updateModel); - } + this.props.eventEmitter.off('updateEditor', this.updateModel); } } @@ -241,7 +202,6 @@ VisEditor.propTypes = { visFields: PropTypes.object, renderComplete: PropTypes.func, config: PropTypes.object, - isEditorMode: PropTypes.bool, savedObj: PropTypes.object, timeRange: PropTypes.object, appState: PropTypes.object, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js index 325e9c8372736..5a6d5f39d0c97 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js @@ -101,4 +101,8 @@ GaugeVisualization.propTypes = { getConfig: PropTypes.func, }; -export const gauge = visWithSplits(GaugeVisualization); +const gauge = visWithSplits(GaugeVisualization); + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { gauge as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts b/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts new file mode 100644 index 0000000000000..56e58b4da3458 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/index.ts @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { lazy } from 'react'; + +import { IUiSettingsClient } from 'src/core/public'; +import { PersistedState } from 'src/plugins/visualizations/public'; + +import { TimeseriesVisParams } from '../../../metrics_fn'; +import { TimeseriesVisData } from '../../../../common/types'; + +/** + * Lazy load each visualization type, since the only one is presented on the screen at the same time. + * Disable typescript errors since the components are not typed yet. + */ + +// @ts-expect-error +const timeseries = lazy(() => import('./timeseries/vis')); +// @ts-expect-error +const metric = lazy(() => import('./metric/vis')); +// @ts-expect-error +const topN = lazy(() => import('./top_n/vis')); +// @ts-expect-error +const table = lazy(() => import('./table/vis')); +// @ts-expect-error +const gauge = lazy(() => import('./gauge/vis')); +// @ts-expect-error +const markdown = lazy(() => import('./markdown/vis')); + +export const TimeseriesVisTypes: Record> = { + timeseries, + metric, + top_n: topN, + table, + gauge, + markdown, +}; + +export interface TimeseriesVisProps { + model: TimeseriesVisParams; + onBrush: (gte: string, lte: string) => void; + onUiState: ( + field: string, + value: { + column: string; + order: string; + } + ) => void; + uiState: PersistedState; + visData: TimeseriesVisData; + dateFormat: string; + getConfig: IUiSettingsClient['get']; +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js index a4e7738e92318..e68b9e5ed8467 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js @@ -30,7 +30,7 @@ import { isBackgroundInverted } from '../../../lib/set_is_reversed'; const getMarkdownId = (id) => `markdown-${id}`; -export function MarkdownVisualization(props) { +function MarkdownVisualization(props) { const { backgroundColor, model, visData, dateFormat } = props; const series = get(visData, `${model.id}.series`, []); const variables = convertSeriesToVars(series, model, dateFormat, props.getConfig); @@ -106,3 +106,7 @@ MarkdownVisualization.propTypes = { dateFormat: PropTypes.string, getConfig: PropTypes.func, }; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { MarkdownVisualization as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js index 5fe7afe47df9b..7069a32fa2b50 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js @@ -95,4 +95,8 @@ MetricVisualization.propTypes = { getConfig: PropTypes.func, }; -export const metric = visWithSplits(MetricVisualization); +const metric = visWithSplits(MetricVisualization); + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { metric as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 92109e1a37426..a31be694cd172 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -46,7 +46,7 @@ function getColor(rules, colorKey, value) { return color; } -export class TableVis extends Component { +class TableVis extends Component { constructor(props) { super(props); @@ -260,3 +260,7 @@ TableVis.propTypes = { pageNumber: PropTypes.number, getConfig: PropTypes.func, }; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TableVis as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index f936710bf2b81..b752699fa1548 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -34,7 +34,7 @@ import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; import { STACKED_OPTIONS } from '../../../visualizations/constants'; import { getCoreStart } from '../../../../services'; -export class TimeseriesVisualization extends Component { +class TimeseriesVisualization extends Component { static propTypes = { model: PropTypes.object, onBrush: PropTypes.func, @@ -44,7 +44,8 @@ export class TimeseriesVisualization extends Component { }; xAxisFormatter = (interval) => (val) => { - const { scaledDataFormat, dateFormat } = this.props.visData; + const scaledDataFormat = this.props.getConfig('dateFormat:scaled'); + const { dateFormat } = this.props; if (!scaledDataFormat || !dateFormat) { return val; @@ -245,3 +246,7 @@ export class TimeseriesVisualization extends Component { ); } } + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TimeseriesVisualization as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js index 1c2ebb8264ef3..92311b3f33afb 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js @@ -48,7 +48,7 @@ function sortSeries(visData, model) { }, []); } -export function TopNVisualization(props) { +function TopNVisualization(props) { const { backgroundColor, model, visData } = props; const series = sortSeries(visData, model).map((item) => { @@ -111,3 +111,7 @@ TopNVisualization.propTypes = { visData: PropTypes.object, getConfig: PropTypes.func, }; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TopNVisualization as default }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/visualization.js b/src/plugins/vis_type_timeseries/public/application/components/visualization.js deleted file mode 100644 index 8b8218653f97c..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/visualization.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import PropTypes from 'prop-types'; -import React from 'react'; -import _ from 'lodash'; - -import { TimeseriesVisualization } from './vis_types/timeseries/vis'; -import { metric } from './vis_types/metric/vis'; -import { TopNVisualization as topN } from './vis_types/top_n/vis'; -import { TableVis as table } from './vis_types/table/vis'; -import { gauge } from './vis_types/gauge/vis'; -import { MarkdownVisualization as markdown } from './vis_types/markdown/vis'; -import { ErrorComponent } from './error'; -import { NoDataComponent } from './no_data'; - -const types = { - timeseries: TimeseriesVisualization, - metric, - top_n: topN, - table, - gauge, - markdown, -}; - -export function Visualization(props) { - const { visData, model } = props; - // Show the error panel - const error = _.get(visData, `${model.id}.error`); - if (error) { - return ( -
- -
- ); - } - - const path = visData.type === 'table' ? 'series' : `${model.id}.series`; - const noData = _.get(visData, path, []).length === 0; - if (noData) { - return ( -
- -
- ); - } - - const component = types[model.type]; - if (component) { - return React.createElement(component, { - dateFormat: props.dateFormat, - backgroundColor: props.backgroundColor, - model: props.model, - onBrush: props.onBrush, - onChange: props.onChange, - onUiState: props.onUiState, - uiState: props.uiState, - visData: visData.type === model.type ? visData : {}, - getConfig: props.getConfig, - }); - } - return
; -} - -Visualization.propTypes = { - backgroundColor: PropTypes.string, - className: PropTypes.string, - model: PropTypes.object, - onBrush: PropTypes.func, - onChange: PropTypes.func, - onUiState: PropTypes.func, - uiState: PropTypes.object, - visData: PropTypes.object, - dateFormat: PropTypes.string, - getConfig: PropTypes.func, -}; - -Visualization.defaultProps = { - className: 'tvbVis', -}; diff --git a/src/plugins/vis_type_timeseries/public/application/editor_controller.js b/src/plugins/vis_type_timeseries/public/application/editor_controller.js index f21b5f947bca7..548bf2623fc1a 100644 --- a/src/plugins/vis_type_timeseries/public/application/editor_controller.js +++ b/src/plugins/vis_type_timeseries/public/application/editor_controller.js @@ -70,7 +70,6 @@ export class EditorController { visParams={this.state.vis.params} timeRange={params.timeRange} renderComplete={() => {}} - isEditorMode={true} appState={params.appState} embeddableHandler={this.embeddableHandler} eventEmitter={this.eventEmitter} diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts deleted file mode 100644 index a9568b5be9d3f..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createBrushHandler } from './create_brush_handler'; -import { ExprVisAPIEvents } from '../../../../visualizations/public'; - -describe('brushHandler', () => { - let onBrush: ReturnType; - let applyFilter: ExprVisAPIEvents['applyFilter']; - - beforeEach(() => { - applyFilter = jest.fn(); - - onBrush = createBrushHandler(applyFilter); - }); - - test('returns brushHandler() should updates timefilter through vis.API.events.applyFilter', () => { - const gte = '2017-01-01T00:00:00Z'; - const lte = '2017-01-01T00:10:00Z'; - - onBrush(gte, lte); - - expect(applyFilter).toHaveBeenCalledWith({ - timeFieldName: '*', - filters: [ - { - range: { '*': { gte: '2017-01-01T00:00:00Z', lte: '2017-01-01T00:10:00Z' } }, - }, - ], - }); - }); -}); diff --git a/src/plugins/vis_type_timeseries/public/metrics_fn.ts b/src/plugins/vis_type_timeseries/public/metrics_fn.ts index 8652d703f963e..60acd35b22402 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_fn.ts @@ -17,38 +17,36 @@ * under the License. */ -import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { KibanaContext } from '../../data/public'; import { ExpressionFunctionDefinition, Render } from '../../expressions/public'; -// @ts-ignore +import { PanelSchema, TimeseriesVisData } from '../common/types'; import { metricsRequestHandler } from './request_handler'; type Input = KibanaContext | null; -type Output = Promise>; +type Output = Promise>; interface Arguments { params: string; uiState: string; - savedObjectId: string | null; } -type VisParams = Required; +export type TimeseriesVisParams = PanelSchema; -interface RenderValue { - visType: 'metrics'; - visData: Input; - visConfig: VisParams; - uiState: any; +export interface TimeseriesRenderValue { + visData: TimeseriesVisData | {}; + visParams: TimeseriesVisParams; } -export const createMetricsFn = (): ExpressionFunctionDefinition< +export type TimeseriesExpressionFunctionDefinition = ExpressionFunctionDefinition< 'tsvb', Input, Arguments, Output -> => ({ +>; + +export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({ name: 'tsvb', type: 'render', inputTypes: ['kibana_context', 'null'], @@ -66,37 +64,22 @@ export const createMetricsFn = (): ExpressionFunctionDefinition< default: '"{}"', help: '', }, - savedObjectId: { - types: ['null', 'string'], - default: null, - help: '', - }, }, async fn(input, args) { - const params = JSON.parse(args.params); - const uiStateParams = JSON.parse(args.uiState); - const savedObjectId = args.savedObjectId; - const { PersistedState } = await import('../../visualizations/public'); - const uiState = new PersistedState(uiStateParams); + const visParams: TimeseriesVisParams = JSON.parse(args.params); + const uiState = JSON.parse(args.uiState); const response = await metricsRequestHandler({ - timeRange: get(input, 'timeRange', null), - query: get(input, 'query', null), - filters: get(input, 'filters', null), - visParams: params, + input, + visParams, uiState, - savedObjectId, }); - response.visType = 'metrics'; - return { type: 'render', - as: 'visualization', + as: 'timeseries_vis', value: { - uiState, - visType: 'metrics', - visConfig: params, + visParams, visData: response, }, }; diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index 01b6ea0768338..2b75f69620629 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -19,12 +19,9 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { metricsRequestHandler } from './request_handler'; import { EditorController } from './application'; -// @ts-ignore import { PANEL_TYPES } from '../common/panel_types'; -import { VisEditor } from './application/components/vis_editor_lazy'; +import { toExpressionAst } from './to_ast'; import { VIS_EVENT_TO_TRIGGER, VisGroups, VisParams } from '../../visualizations/public'; import { getDataStart } from './services'; import { INDEXES_SEPARATOR } from '../common/constants'; @@ -73,7 +70,6 @@ export const metricsVisDefinition = { show_grid: 1, tooltip_mode: 'show_all', }, - component: VisEditor, }, editor: EditorController, options: { @@ -81,7 +77,7 @@ export const metricsVisDefinition = { showFilterBar: false, showIndexSelection: false, }, - requestHandler: metricsRequestHandler, + toExpressionAst, getSupportedTriggers: () => { return [VIS_EVENT_TO_TRIGGER.applyFilter]; }, @@ -102,5 +98,4 @@ export const metricsVisDefinition = { return []; }, - responseHandler: 'none', }; diff --git a/src/plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts index d98e55bdb340c..d36b3611680af 100644 --- a/src/plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -36,6 +36,7 @@ import { } from './services'; import { DataPublicPluginStart } from '../../data/public'; import { ChartsPluginSetup } from '../../charts/public'; +import { getTimeseriesVisRenderer } from './timeseries_vis_renderer'; /** @internal */ export interface MetricsPluginSetupDependencies { @@ -62,9 +63,14 @@ export class MetricsPlugin implements Plugin, void> { { expressions, visualizations, charts }: MetricsPluginSetupDependencies ) { expressions.registerFunction(createMetricsFn); + expressions.registerRenderer( + getTimeseriesVisRenderer({ + uiSettings: core.uiSettings, + }) + ); setUISettings(core.uiSettings); setChartsSetup(charts); - visualizations.createReactVisualization(metricsVisDefinition); + visualizations.createBaseVisualization(metricsVisDefinition); } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { diff --git a/src/plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.ts similarity index 54% rename from src/plugins/vis_type_timeseries/public/request_handler.js rename to src/plugins/vis_type_timeseries/public/request_handler.ts index 12b7f3d417ef6..aa45453515277 100644 --- a/src/plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.ts @@ -17,57 +17,52 @@ * under the License. */ +import { KibanaContext } from '../../data/public'; + import { getTimezone, validateInterval } from './application'; import { getUISettings, getDataStart, getCoreStart } from './services'; -import { MAX_BUCKETS_SETTING } from '../common/constants'; +import { MAX_BUCKETS_SETTING, ROUTES } from '../common/constants'; +import { TimeseriesVisParams } from './metrics_fn'; +import { TimeseriesVisData } from '../common/types'; + +interface MetricsRequestHandlerParams { + input: KibanaContext | null; + uiState: Record; + visParams: TimeseriesVisParams; +} export const metricsRequestHandler = async ({ + input, uiState, - timeRange, - filters, - query, visParams, - savedObjectId, -}) => { +}: MetricsRequestHandlerParams): Promise => { const config = getUISettings(); const timezone = getTimezone(config); - const uiStateObj = uiState.get(visParams.type, {}); + const uiStateObj = uiState[visParams.type] ?? {}; const dataSearch = getDataStart(); - const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange); - const scaledDataFormat = config.get('dateFormat:scaled'); - const dateFormat = config.get('dateFormat'); + const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(input?.timeRange!); if (visParams && visParams.id && !visParams.isModelInvalid) { - try { - const maxBuckets = config.get(MAX_BUCKETS_SETTING); + const maxBuckets = config.get(MAX_BUCKETS_SETTING); - validateInterval(parsedTimeRange, visParams, maxBuckets); + validateInterval(parsedTimeRange, visParams, maxBuckets); - const resp = await getCoreStart().http.post('/api/metrics/vis/data', { - body: JSON.stringify({ - timerange: { - timezone, - ...parsedTimeRange, - }, - query, - filters, - panels: [visParams], - state: uiStateObj, - savedObjectId: savedObjectId || 'unsaved', - sessionId: dataSearch.search.session.getSessionId(), - }), - }); + const resp = await getCoreStart().http.post(ROUTES.VIS_DATA, { + body: JSON.stringify({ + timerange: { + timezone, + ...parsedTimeRange, + }, + query: input?.query, + filters: input?.filters, + panels: [visParams], + state: uiStateObj, + sessionId: dataSearch.search.session.getSessionId(), + }), + }); - return { - dateFormat, - scaledDataFormat, - timezone, - ...resp, - }; - } catch (error) { - return Promise.reject(error); - } + return resp; } - return Promise.resolve({}); + return {}; }; diff --git a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx new file mode 100644 index 0000000000000..67ed487d29378 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { IUiSettingsClient } from 'kibana/public'; +import { VisualizationContainer } from '../../visualizations/public'; +import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; +import { TimeseriesRenderValue, TimeseriesVisParams } from './metrics_fn'; +import { TimeseriesVisData } from '../common/types'; + +const TimeseriesVisualization = lazy( + () => import('./application/components/timeseries_visualization') +); + +const checkIfDataExists = (visData: TimeseriesVisData | {}, model: TimeseriesVisParams) => { + if ('type' in visData) { + const data = visData.type === 'table' ? visData.series : visData?.[model.id]?.series; + return Boolean(data?.length); + } + + return false; +}; + +export const getTimeseriesVisRenderer: (deps: { + uiSettings: IUiSettingsClient; +}) => ExpressionRenderDefinition = ({ uiSettings }) => ({ + name: 'timeseries_vis', + reuseDomNode: true, + render: async (domNode, config, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + const showNoResult = !checkIfDataExists(config.visData, config.visParams); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts b/src/plugins/vis_type_timeseries/public/to_ast.ts similarity index 59% rename from src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts rename to src/plugins/vis_type_timeseries/public/to_ast.ts index 38002c7552952..58de0269fd27f 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts +++ b/src/plugins/vis_type_timeseries/public/to_ast.ts @@ -17,23 +17,17 @@ * under the License. */ -import { ExprVisAPIEvents } from '../../../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { TimeseriesExpressionFunctionDefinition, TimeseriesVisParams } from './metrics_fn'; -export const createBrushHandler = (applyFilter: ExprVisAPIEvents['applyFilter']) => ( - gte: string, - lte: string -) => { - return applyFilter({ - timeFieldName: '*', - filters: [ - { - range: { - '*': { - gte, - lte, - }, - }, - }, - ], +export const toExpressionAst = (vis: Vis) => { + const timeseries = buildExpressionFunction('tsvb', { + params: JSON.stringify(vis.params), + uiState: JSON.stringify(vis.uiState), }); + + const ast = buildExpression([timeseries]); + + return ast.toAst(); }; diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index fcb66d2e12fd1..aefbe0ea78d4b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -20,46 +20,37 @@ import { FakeRequest, RequestHandlerContext } from 'kibana/server'; import _ from 'lodash'; import { first, map } from 'rxjs/operators'; + +import { Filter, Query } from 'src/plugins/data/common'; import { getPanelData } from './vis_data/get_panel_data'; import { Framework } from '../plugin'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; - -interface GetVisDataResponse { - [key: string]: GetVisDataPanel; -} - -interface GetVisDataPanel { - id: string; - series: GetVisDataSeries[]; -} - -interface GetVisDataSeries { - id: string; - label: string; - data: GetVisDataDataPoint[]; -} - -type GetVisDataDataPoint = [number, number]; +import { TimeseriesVisData } from '../../common/types'; export interface GetVisDataOptions { - timerange?: any; - panels?: any; - filters?: any; - state?: any; - query?: any; + timerange: { + min: number | string; + max: number | string; + timezone?: string; + }; + panels: unknown[]; + filters?: Filter[]; + state?: Record; + query?: Query | Query[]; + sessionId?: string; } export type GetVisData = ( requestContext: RequestHandlerContext, options: GetVisDataOptions, framework: Framework -) => Promise; +) => Promise; export function getVisData( requestContext: RequestHandlerContext, request: FakeRequest & { body: GetVisDataOptions }, framework: Framework -): Promise { +): Promise { // NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now @@ -81,10 +72,10 @@ export function getVisData( .toPromise(); }, }; - const promises = (reqFacade.payload as GetVisDataOptions).panels.map(getPanelData(reqFacade)); + const promises = reqFacade.payload.panels.map(getPanelData(reqFacade)); return Promise.all(promises).then((res) => { return res.reduce((acc, data) => { return _.assign(acc as any, data); }, {}); - }) as Promise; + }) as Promise; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_active_series.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_active_series.ts index 235235cf6a3be..b7644f6582761 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_active_series.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_active_series.ts @@ -29,7 +29,8 @@ export const getActiveSeries = (panel: PanelSchema) => { } // Toogle visibility functionality for 'gauge', 'markdown' is not accessible - const shouldNotApplyFilter = [PANEL_TYPES.GAUGE, PANEL_TYPES.MARKDOWN].includes(panel.type); + const shouldNotApplyFilter = + PANEL_TYPES.GAUGE === panel.type || PANEL_TYPES.MARKDOWN === panel.type; return visibleSeries.filter((series) => !series.hidden || shouldNotApplyFilter); }; diff --git a/src/plugins/vis_type_timeseries/server/routes/vis.ts b/src/plugins/vis_type_timeseries/server/routes/vis.ts index 1ca8b57ab230f..bba086720da0a 100644 --- a/src/plugins/vis_type_timeseries/server/routes/vis.ts +++ b/src/plugins/vis_type_timeseries/server/routes/vis.ts @@ -21,6 +21,7 @@ import { IRouter, KibanaRequest } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { getVisData, GetVisDataOptions } from '../lib/get_vis_data'; import { visPayloadSchema } from '../../common/vis_schema'; +import { ROUTES } from '../../common/constants'; import { ValidationTelemetryServiceSetup } from '../index'; import { Framework } from '../plugin'; @@ -33,7 +34,7 @@ export const visDataRoutes = ( ) => { router.post( { - path: '/api/metrics/vis/data', + path: ROUTES.VIS_DATA, validate: { body: escapeHatch, }, @@ -43,11 +44,9 @@ export const visDataRoutes = ( visPayloadSchema.validate(request.body); } catch (error) { logFailedValidation(); - const savedObjectId = - (typeof request.body === 'object' && (request.body as any).savedObjectId) || - 'unavailable'; + framework.logger.warn( - `Request validation error: ${error.message} (saved object id: ${savedObjectId}). This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md` + `Request validation error: ${error.message}. This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md` ); } diff --git a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap index 978c67fb71721..94c5da872b1cb 100644 --- a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap +++ b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap @@ -3,6 +3,7 @@ exports[`VisualizationNoResults should render according to snapshot 1`] = `
+
{showNoResult ? handlers.done()} /> : children} diff --git a/src/plugins/visualizations/public/components/visualization_noresults.tsx b/src/plugins/visualizations/public/components/visualization_noresults.tsx index c77ef4490a4b3..90d5353185229 100644 --- a/src/plugins/visualizations/public/components/visualization_noresults.tsx +++ b/src/plugins/visualizations/public/components/visualization_noresults.tsx @@ -30,7 +30,7 @@ export class VisualizationNoResults extends React.Component +
diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index 2c6cfc6fb7462..03a355c604c4d 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -4,8 +4,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls t exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' uiState='{}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 0c210a04d2007..653542bd8837d 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -101,12 +101,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { expect(actual).toMatchSnapshot(); }); - it('handles metrics/tsvb function', () => { - const params = { foo: 'bar' }; - const actual = buildPipelineVisFunction.metrics(params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - describe('handles region_map function', () => { it('without buckets', () => { const params = { metric: {} }; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 3593d62b9d2e6..d412ec918a71a 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -222,13 +222,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { input_control_vis: (params) => { return `input_control_vis ${prepareJson('visConfig', params)}`; }, - metrics: ({ title, ...params }, schemas, uiState = {}) => { - const paramsJson = prepareJson('params', params); - const uiStateJson = prepareJson('uiState', uiState); - - const paramsArray = [paramsJson, uiStateJson].filter((param) => Boolean(param)); - return `tsvb ${paramsArray.join(' ')}`; - }, region_map: (params, schemas) => { const visConfig = { ...params, diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index 0be4fbbebe7c5..b3812af38c348 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }) { }); it('tsvb time series shows no data message', async () => { - expect(await testSubjects.exists('noTSVBDataMessage')).to.be(true); + expect(await testSubjects.exists('timeseriesVis > visNoResult')).to.be(true); }); it('metric value shows no data', async () => { diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 37634d0248b04..0e305eaafc82f 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -549,7 +549,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro public async checkPreviewIsDisabled(): Promise { log.debug(`Check no data message is present`); - await testSubjects.existOrFail('noTSVBDataMessage', { timeout: 5000 }); + await testSubjects.existOrFail('timeseriesVis > visNoResult', { timeout: 5000 }); } public async cloneSeries(nth: number = 0): Promise { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5e129bea61d0d..46ecdf15ccd7e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4035,7 +4035,6 @@ "visTypeTimeseries.movingAverage.windowSizeHint": "ウィンドウは、必ず、期間のサイズの 2 倍以上でなければなりません", "visTypeTimeseries.movingAverage.windowSizeLabel": "ウィンドウサイズ", "visTypeTimeseries.noButtonLabel": "いいえ", - "visTypeTimeseries.noDataDescription": "選択されたメトリックに表示するデータがありません", "visTypeTimeseries.percentile.aggregationLabel": "集約", "visTypeTimeseries.percentile.fieldLabel": "フィールド", "visTypeTimeseries.percentile.fillToLabel": "次の基準に合わせる:", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c584f5bb254a0..ccf5b5adb39a6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4036,7 +4036,6 @@ "visTypeTimeseries.movingAverage.windowSizeHint": "窗口必须始终至少是期间大小的两倍", "visTypeTimeseries.movingAverage.windowSizeLabel": "窗口大小", "visTypeTimeseries.noButtonLabel": "否", - "visTypeTimeseries.noDataDescription": "所选指标没有可显示的数据", "visTypeTimeseries.percentile.aggregationLabel": "聚合", "visTypeTimeseries.percentile.fieldLabel": "字段", "visTypeTimeseries.percentile.fillToLabel": "填充到:",