From 978226f39a74e53fb51ead0f219eb91b7de0cb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Mon, 8 Mar 2021 17:54:10 +0100 Subject: [PATCH] [Metrics UI] Add preview charts to Inventory alerts (#91658) * add first version of preview chart for inventory alerts * Make preview chart collapsible * Add margin to expressions without charts * Remove some duplication in metric alerts preview charts * Add warning thresholds to inventory alerts preview chart * Add threshold annotations component * Clean imports and unused variables * Add tests for threschold annotations component * Remove unused translations * Set correct id to inventory alerts preview chart * Get accountId and region with useWaffleOptions for preview chart * Save inventory alert thresholds in the same unit as ES * minor fixes * Revert "Save inventory alert thresholds in the same unit as ES" This reverts commit 118d83efeb83288876650f6c7a2ac1e176ff44b9. * Remove threshold formatter function and convert values inside expression chart component Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../threshold_annotations.test.tsx | 153 ++++++++++++ .../threshold_annotations.tsx | 164 ++++++++++++ .../inventory/components/expression.tsx | 67 +++-- .../inventory/components/expression_chart.tsx | 236 ++++++++++++++++++ .../components/expression_chart.tsx | 223 ++--------------- .../translations/translations/ja-JP.json | 8 - .../translations/translations/zh-CN.json | 8 - 7 files changed, 625 insertions(+), 234 deletions(-) create mode 100644 x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.test.tsx create mode 100644 x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.tsx create mode 100644 x-pack/plugins/infra/public/alerting/inventory/components/expression_chart.tsx diff --git a/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.test.tsx b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.test.tsx new file mode 100644 index 000000000000..306a623eed98 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.test.tsx @@ -0,0 +1,153 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { ThresholdAnnotations } from './threshold_annotations'; +import { + Comparator, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../server/lib/alerting/metric_threshold/types'; +// import { Color } from 'x-pack/plugins/infra/common/color_palette'; +import { Color } from '../../../../common/color_palette'; +import { shallow } from 'enzyme'; + +jest.mock('@elastic/charts', () => { + const original = jest.requireActual('@elastic/charts'); + + const mockComponent = (props: {}) => { + return
; + }; + + return { + ...original, + LineAnnotation: mockComponent, + RectAnnotation: mockComponent, + }; +}); + +describe('ThresholdAnnotations', () => { + async function setup(props = {}) { + const defaultProps = { + threshold: [20, 30], + sortedThresholds: [20, 30], + comparator: Comparator.GT, + color: Color.color0, + id: 'testId', + firstTimestamp: 123456789, + lastTimestamp: 987654321, + domain: { min: 10, max: 20 }, + }; + const wrapper = shallow(); + + return wrapper; + } + + it('should render a line annotation for each threshold', async () => { + const wrapper = await setup(); + + const annotation = wrapper.find('[data-test-subj="threshold-line"]'); + const expectedValues = [{ dataValue: 20 }, { dataValue: 30 }]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + expect(annotation.length).toBe(1); + }); + + it('should render a rectangular annotation for in between thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.BETWEEN }); + + const annotation = wrapper.find('[data-test-subj="between-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 20, + y1: 30, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render an upper rectangular annotation for outside range thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.OUTSIDE_RANGE }); + + const annotation = wrapper.find('[data-test-subj="outside-range-lower-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 10, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a lower rectangular annotation for outside range thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.OUTSIDE_RANGE }); + + const annotation = wrapper.find('[data-test-subj="outside-range-upper-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 30, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a rectangular annotation for below thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.LT }); + + const annotation = wrapper.find('[data-test-subj="below-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 10, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); + + it('should render a rectangular annotation for above thresholds', async () => { + const wrapper = await setup({ comparator: Comparator.GT }); + + const annotation = wrapper.find('[data-test-subj="above-rect"]'); + const expectedValues = [ + { + coordinates: { + x0: 123456789, + x1: 987654321, + y0: 20, + y1: 20, + }, + }, + ]; + const values = annotation.prop('dataValues'); + + expect(values).toEqual(expectedValues); + }); +}); diff --git a/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.tsx b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.tsx new file mode 100644 index 000000000000..5098eb2c23f5 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/threshold_annotations.tsx @@ -0,0 +1,164 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { first, last } from 'lodash'; +import { RectAnnotation, AnnotationDomainTypes, LineAnnotation } from '@elastic/charts'; + +import { + Comparator, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../server/lib/alerting/metric_threshold/types'; +import { Color, colorTransformer } from '../../../../common/color_palette'; + +interface ThresholdAnnotationsProps { + threshold: number[]; + sortedThresholds: number[]; + comparator: Comparator; + color: Color; + id: string; + firstTimestamp: number; + lastTimestamp: number; + domain: { min: number; max: number }; +} + +const opacity = 0.3; + +export const ThresholdAnnotations = ({ + threshold, + sortedThresholds, + comparator, + color, + id, + firstTimestamp, + lastTimestamp, + domain, +}: ThresholdAnnotationsProps) => { + if (!comparator || !threshold) return null; + const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(comparator); + const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(comparator); + return ( + <> + ({ + dataValue: t, + }))} + style={{ + line: { + strokeWidth: 2, + stroke: colorTransformer(color), + opacity: 1, + }, + }} + /> + {sortedThresholds.length === 2 && comparator === Comparator.BETWEEN ? ( + <> + + + ) : null} + {sortedThresholds.length === 2 && comparator === Comparator.OUTSIDE_RANGE ? ( + <> + + + + ) : null} + {isBelow && first(threshold) != null ? ( + + ) : null} + {isAbove && first(threshold) != null ? ( + + ) : null} + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 7233ce3de749..9ce7162933f2 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -71,6 +71,7 @@ import { import { validateMetricThreshold } from './validation'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { ExpressionChart } from './expression_chart'; const FILTER_TYPING_DEBOUNCE_MS = 500; export interface AlertContextMeta { @@ -291,11 +292,13 @@ export const Expressions: React.FC = (props) => { - + + + @@ -313,17 +316,26 @@ export const Expressions: React.FC = (props) => { errors={(errors[idx] as IErrorObject) || emptyError} expression={e || {}} fields={derivedIndexPattern.fields} - /> + > + + ); })} - + + +
= (props) => { - const { setAlertParams, expression, errors, expressionId, remove, canDelete, fields } = props; + const [isExpanded, setRowState] = useState(true); + const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); + + const { + children, + setAlertParams, + expression, + errors, + expressionId, + remove, + canDelete, + fields, + } = props; const { metric, comparator = Comparator.GT, @@ -579,6 +607,16 @@ export const ExpressionRow: React.FC = (props) => { return ( <> + + + + @@ -670,6 +708,7 @@ export const ExpressionRow: React.FC = (props) => { )} + {isExpanded ?
{children}
: null} ); diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression_chart.tsx new file mode 100644 index 000000000000..b1a58a869e01 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression_chart.tsx @@ -0,0 +1,236 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo, useCallback } from 'react'; +import { Axis, Chart, niceTimeFormatter, Position, Settings } from '@elastic/charts'; +import { first, last } from 'lodash'; +import moment from 'moment'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Color } from '../../../../common/color_palette'; +import { MetricsExplorerRow, MetricsExplorerAggregation } from '../../../../common/http_api'; +import { MetricExplorerSeriesChart } from '../../../pages/metrics/metrics_explorer/components/series_chart'; +import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; +import { calculateDomain } from '../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain'; +import { getMetricId } from '../../../pages/metrics/metrics_explorer/components/helpers/get_metric_id'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; +import { useSnapshot } from '../../../pages/metrics/inventory_view/hooks/use_snaphot'; +import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; +import { createInventoryMetricFormatter } from '../../../pages/metrics/inventory_view/lib/create_inventory_metric_formatter'; + +import { + ChartContainer, + LoadingState, + NoDataState, + TIME_LABELS, + tooltipProps, + getChartTheme, +} from '../../common/criterion_preview_chart/criterion_preview_chart'; +import { ThresholdAnnotations } from '../../common/criterion_preview_chart/threshold_annotations'; +import { useWaffleOptionsContext } from '../../../pages/metrics/inventory_view/hooks/use_waffle_options'; + +interface Props { + expression: InventoryMetricConditions; + filterQuery?: string; + nodeType: InventoryItemType; + sourceId: string; +} + +export const ExpressionChart: React.FC = ({ + expression, + filterQuery, + nodeType, + sourceId, +}) => { + const timerange = useMemo( + () => ({ + interval: `${expression.timeSize || 1}${expression.timeUnit}`, + from: moment() + .subtract((expression.timeSize || 1) * 20, expression.timeUnit) + .valueOf(), + to: moment().valueOf(), + forceInterval: true, + ignoreLookback: true, + }), + [expression.timeSize, expression.timeUnit] + ); + + const buildCustomMetric = (metric: any) => ({ + ...metric, + type: 'custom' as SnapshotMetricType, + }); + + const options = useWaffleOptionsContext(); + const { loading, nodes } = useSnapshot( + filterQuery, + expression.metric === 'custom' + ? [buildCustomMetric(expression.customMetric)] + : [{ type: expression.metric }], + [], + nodeType, + sourceId, + 0, + options.accountId, + options.region, + true, + timerange + ); + + const { uiSettings } = useKibanaContextForPlugin().services; + + const metric = { + field: expression.metric, + aggregation: 'avg' as MetricsExplorerAggregation, + color: Color.color0, + }; + const isDarkMode = uiSettings?.get('theme:darkMode') || false; + const dateFormatter = useMemo(() => { + const firstSeries = nodes[0]?.metrics[0]?.timeseries; + const firstTimestamp = first(firstSeries?.rows)?.timestamp; + const lastTimestamp = last(firstSeries?.rows)?.timestamp; + + if (firstTimestamp == null || lastTimestamp == null) { + return (value: number) => `${value}`; + } + + return niceTimeFormatter([firstTimestamp, lastTimestamp]); + }, [nodes]); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const yAxisFormater = useCallback( + createInventoryMetricFormatter( + expression.metric === 'custom' + ? buildCustomMetric(expression.customMetric) + : { type: expression.metric } + ), + [expression.metric] + ); + + if (loading || !nodes) { + return ; + } + + const convertThreshold = (threshold: number) => convertMetricValue(expression.metric, threshold); + const convertedThresholds = expression.threshold.map(convertThreshold); + const convertedWarningThresholds = expression.warningThreshold?.map(convertThreshold) ?? []; + + const criticalThresholds = convertedThresholds.slice().sort(); + const warningThresholds = convertedWarningThresholds.slice().sort(); + const thresholds = [...criticalThresholds, ...warningThresholds].sort(); + + // Creating a custom series where the ID is changed to 0 + // so that we can get a proper domian + const firstSeries = nodes[0]?.metrics[0]?.timeseries; + if (!firstSeries || !firstSeries.rows || firstSeries.rows.length === 0) { + return ; + } + + const series = { + ...firstSeries, + id: nodes[0]?.name, + rows: firstSeries.rows.map((row) => { + const newRow: MetricsExplorerRow = { ...row }; + thresholds.forEach((thresholdValue, index) => { + newRow[getMetricId(metric, `threshold_${index}`)] = thresholdValue; + }); + return newRow; + }), + }; + + const firstTimestamp = first(firstSeries.rows)!.timestamp; + const lastTimestamp = last(firstSeries.rows)!.timestamp; + const dataDomain = calculateDomain(series, [metric], false); + const domain = { + max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, // add 10% headroom. + min: Math.min(dataDomain.min, first(thresholds) || dataDomain.min) * 0.9, // add 10% floor + }; + + if (domain.min === first(convertedThresholds)) { + domain.min = domain.min * 0.9; + } + + const { timeSize, timeUnit } = expression; + const timeLabel = TIME_LABELS[timeUnit as keyof typeof TIME_LABELS]; + + return ( + <> + + + + + {expression.warningComparator && expression.warningThreshold && ( + + )} + + + + + +
+ {series.id !== 'ALL' ? ( + + + + ) : ( + + + + )} +
+ + ); +}; + +const convertMetricValue = (metric: SnapshotMetricType, value: number) => { + if (converters[metric]) { + return converters[metric](value); + } else { + return value; + } +}; +const converters: Record number> = { + cpu: (n) => Number(n) / 100, + memory: (n) => Number(n) / 100, +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index aa135df8aad7..2a274c4b6d50 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -6,39 +6,31 @@ */ import React, { useMemo, useCallback } from 'react'; -import { - Axis, - Chart, - niceTimeFormatter, - Position, - Settings, - TooltipValue, - RectAnnotation, - AnnotationDomainTypes, - LineAnnotation, -} from '@elastic/charts'; +import { Axis, Chart, niceTimeFormatter, Position, Settings } from '@elastic/charts'; import { first, last } from 'lodash'; -import moment from 'moment'; -import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { IIndexPattern } from 'src/plugins/data/public'; import { InfraSource } from '../../../../common/http_api/source_api'; -import { - Comparator, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../server/lib/alerting/metric_threshold/types'; -import { Color, colorTransformer } from '../../../../common/color_palette'; +import { Color } from '../../../../common/color_palette'; import { MetricsExplorerRow, MetricsExplorerAggregation } from '../../../../common/http_api'; import { MetricExplorerSeriesChart } from '../../../pages/metrics/metrics_explorer/components/series_chart'; import { MetricExpression } from '../types'; import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; -import { getChartTheme } from '../../../pages/metrics/metrics_explorer/components/helpers/get_chart_theme'; import { createFormatterForMetric } from '../../../pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric'; import { calculateDomain } from '../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain'; import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; import { getMetricId } from '../../../pages/metrics/metrics_explorer/components/helpers/get_metric_id'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { + ChartContainer, + LoadingState, + NoDataState, + TIME_LABELS, + tooltipProps, + getChartTheme, +} from '../../common/criterion_preview_chart/criterion_preview_chart'; +import { ThresholdAnnotations } from '../../common/criterion_preview_chart/threshold_annotations'; interface Props { expression: MetricExpression; @@ -48,18 +40,6 @@ interface Props { groupBy?: string | string[]; } -const tooltipProps = { - headerFormatter: (tooltipValue: TooltipValue) => - moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), -}; - -const TIME_LABELS = { - s: i18n.translate('xpack.infra.metrics.alerts.timeLabels.seconds', { defaultMessage: 'seconds' }), - m: i18n.translate('xpack.infra.metrics.alerts.timeLabels.minutes', { defaultMessage: 'minutes' }), - h: i18n.translate('xpack.infra.metrics.alerts.timeLabels.hours', { defaultMessage: 'hours' }), - d: i18n.translate('xpack.infra.metrics.alerts.timeLabels.days', { defaultMessage: 'days' }), -}; - export const ExpressionChart: React.FC = ({ expression, derivedIndexPattern, @@ -99,16 +79,7 @@ export const ExpressionChart: React.FC = ({ const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); if (loading || !data) { - return ( - - - - - - ); + return ; } const criticalThresholds = expression.threshold.slice().sort(); @@ -119,16 +90,7 @@ export const ExpressionChart: React.FC = ({ // so that we can get a proper domian const firstSeries = first(data.series); if (!firstSeries || !firstSeries.rows || firstSeries.rows.length === 0) { - return ( - - - - - - ); + return ; } const series = { @@ -154,137 +116,9 @@ export const ExpressionChart: React.FC = ({ domain.min = domain.min * 0.9; } - const opacity = 0.3; const { timeSize, timeUnit } = expression; const timeLabel = TIME_LABELS[timeUnit as keyof typeof TIME_LABELS]; - const ThresholdAnnotations = ({ - threshold, - sortedThresholds, - comparator, - color, - id, - }: Partial & { sortedThresholds: number[]; color: Color; id: string }) => { - if (!comparator || !threshold) return null; - const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(comparator); - const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(comparator); - return ( - <> - ({ - dataValue: t, - }))} - style={{ - line: { - strokeWidth: 2, - stroke: colorTransformer(color), - opacity: 1, - }, - }} - /> - {sortedThresholds.length === 2 && comparator === Comparator.BETWEEN ? ( - <> - - - ) : null} - {sortedThresholds.length === 2 && comparator === Comparator.OUTSIDE_RANGE ? ( - <> - - - - ) : null} - {isBelow && first(threshold) != null ? ( - - ) : null} - {isAbove && first(threshold) != null ? ( - - ) : null} - - ); - }; - return ( <> @@ -302,6 +136,9 @@ export const ExpressionChart: React.FC = ({ sortedThresholds={criticalThresholds} color={Color.color1} id="critical" + firstTimestamp={firstTimestamp} + lastTimestamp={lastTimestamp} + domain={domain} /> {expression.warningComparator && expression.warningThreshold && ( = ({ sortedThresholds={warningThresholds} color={Color.color5} id="warning" + firstTimestamp={firstTimestamp} + lastTimestamp={lastTimestamp} + domain={domain} /> )} = ({ ); }; - -const EmptyContainer: React.FC = ({ children }) => ( -
- {children} -
-); - -const ChartContainer: React.FC = ({ children }) => ( -
- {children} -
-); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d16d0d991572..eed920444d2d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10959,14 +10959,6 @@ "xpack.infra.metrics.alertName": "メトリックしきい値", "xpack.infra.metrics.alerts.dataTimeRangeLabel": "過去{lookback} {timeLabel}", "xpack.infra.metrics.alerts.dataTimeRangeLabelWithGrouping": "{id}のデータの過去{lookback} {timeLabel}", - "xpack.infra.metrics.alerts.loadingMessage": "読み込み中", - "xpack.infra.metrics.alerts.noDataMessage": "グラフデータがありません", - "xpack.infra.metrics.alerts.timeLabels.days": "日", - "xpack.infra.metrics.alerts.timeLabels.hours": "時間", - "xpack.infra.metrics.alerts.timeLabels.minutes": "分", - "xpack.infra.metrics.alerts.timeLabels.seconds": "秒", - "xpack.infra.metrics.anomaly.alertFlyout.alertDescription": "異常スコアが定義されたしきい値を超えたときにアラートを発行します。", - "xpack.infra.metrics.anomaly.alertName": "インフラストラクチャーの異常", "xpack.infra.metrics.emptyViewDescription": "期間またはフィルターを調整してみてください。", "xpack.infra.metrics.emptyViewTitle": "表示するデータがありません。", "xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "閉じる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c3cbba209b12..e4dde893a217 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11109,14 +11109,6 @@ "xpack.infra.metrics.alertName": "指标阈值", "xpack.infra.metrics.alerts.dataTimeRangeLabel": "过去 {lookback} {timeLabel}", "xpack.infra.metrics.alerts.dataTimeRangeLabelWithGrouping": "{id} 过去 {lookback} {timeLabel}的数据", - "xpack.infra.metrics.alerts.loadingMessage": "正在加载", - "xpack.infra.metrics.alerts.noDataMessage": "糟糕,没有可用的图表数据", - "xpack.infra.metrics.alerts.timeLabels.days": "天", - "xpack.infra.metrics.alerts.timeLabels.hours": "小时", - "xpack.infra.metrics.alerts.timeLabels.minutes": "分钟", - "xpack.infra.metrics.alerts.timeLabels.seconds": "秒", - "xpack.infra.metrics.anomaly.alertFlyout.alertDescription": "当异常分数超过定义的阈值时告警。", - "xpack.infra.metrics.anomaly.alertName": "基础架构异常", "xpack.infra.metrics.emptyViewDescription": "尝试调整您的时间或筛选。", "xpack.infra.metrics.emptyViewTitle": "没有可显示的数据。", "xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "关闭",