diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx index 461c78482a431..8976d77ea912f 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx @@ -3,251 +3,122 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiPageContentBody, EuiTitle } from '@elastic/eui'; -import { - EuiAreaSeries, - EuiBarSeries, - EuiCrosshairX, - EuiDataPoint, - EuiLineSeries, - EuiSeriesChart, - EuiSeriesChartProps, - EuiSeriesProps, - EuiXAxis, - EuiYAxis, -} from '@elastic/eui/lib/experimental'; +import React, { useCallback } from 'react'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import Color from 'color'; import { get } from 'lodash'; -import moment from 'moment'; -import React, { ReactText } from 'react'; -import { InfraDataSeries, InfraMetricData, InfraTimerangeInput } from '../../../graphql/types'; -import { InfraFormatter, InfraFormatterType } from '../../../lib/lib'; +import { Axis, Chart, getAxisId, niceTimeFormatter, Position, Settings } from '@elastic/charts'; +import { EuiPageContentBody, EuiTitle } from '@elastic/eui'; +import { InfraMetricLayoutSection } from '../../../pages/metrics/layouts/types'; +import { InfraMetricData, InfraTimerangeInput } from '../../../graphql/types'; +import { getChartTheme } from '../../metrics_explorer/helpers/get_chart_theme'; +import { InfraFormatterType } from '../../../lib/lib'; +import { SeriesChart } from './series_chart'; import { - InfraMetricLayoutSection, - InfraMetricLayoutVisualizationType, -} from '../../../pages/metrics/layouts/types'; -import { createFormatter } from '../../../utils/formatters'; - -const MARGIN_LEFT = 60; - -const chartComponentsByType = { - [InfraMetricLayoutVisualizationType.line]: EuiLineSeries, - [InfraMetricLayoutVisualizationType.area]: EuiAreaSeries, - [InfraMetricLayoutVisualizationType.bar]: EuiBarSeries, -}; + getFormatter, + getMaxMinTimestamp, + getChartName, + getChartColor, + getChartType, + seriesHasLessThen2DataPoints, +} from './helpers'; +import { ErrorMessage } from './error_message'; interface Props { section: InfraMetricLayoutSection; metric: InfraMetricData; onChangeRangeTime?: (time: InfraTimerangeInput) => void; - crosshairValue?: number; - onCrosshairUpdate?: (crosshairValue: number) => void; isLiveStreaming?: boolean; stopLiveStreaming?: () => void; intl: InjectedIntl; } -const isInfraMetricLayoutVisualizationType = ( - subject: any -): subject is InfraMetricLayoutVisualizationType => { - return InfraMetricLayoutVisualizationType[subject] != null; -}; - -const getChartName = (section: InfraMetricLayoutSection, seriesId: string) => { - return get(section, ['visConfig', 'seriesOverrides', seriesId, 'name'], seriesId); -}; - -const getChartColor = (section: InfraMetricLayoutSection, seriesId: string): string | undefined => { - const color = new Color( - get(section, ['visConfig', 'seriesOverrides', seriesId, 'color'], '#999') - ); - return color.hex().toString(); -}; - -const getChartType = (section: InfraMetricLayoutSection, seriesId: string) => { - const value = get(section, ['visConfig', 'type']); - const overrideValue = get(section, ['visConfig', 'seriesOverrides', seriesId, 'type']); - if (isInfraMetricLayoutVisualizationType(overrideValue)) { - return overrideValue; - } - if (isInfraMetricLayoutVisualizationType(value)) { - return value; - } - return InfraMetricLayoutVisualizationType.line; -}; - -const getFormatter = (formatter: InfraFormatterType, formatterTemplate: string) => ( - val: ReactText -) => { - if (val == null) { - return ''; - } - return createFormatter(formatter, formatterTemplate)(val); -}; - -const titleFormatter = (dataPoints: EuiDataPoint[]) => { - if (dataPoints.length > 0) { - const [firstDataPoint] = dataPoints; - const { originalValues } = firstDataPoint; - return { - title: , - value: moment(originalValues.x).format('lll'), - }; - } -}; - -const createItemsFormatter = ( - formatter: InfraFormatter, - labels: string[], - seriesColors: string[] -) => (dataPoints: EuiDataPoint[]) => { - return dataPoints.map(d => { - return { - title: ( - - - {labels[d.seriesIndex]} - - ), - value: formatter(d.y), - }; - }); -}; - -const seriesHasLessThen2DataPoints = (series: InfraDataSeries): boolean => { - return series.data.length < 2; -}; - export const ChartSection = injectI18n( - class extends React.PureComponent { - public static displayName = 'ChartSection'; - public render() { - const { crosshairValue, section, metric, onCrosshairUpdate, intl } = this.props; - const { visConfig } = section; - const crossHairProps = { - crosshairValue, - onCrosshairUpdate, - }; - const chartProps: EuiSeriesChartProps = { - xType: 'time', - showCrosshair: false, - showDefaultAxis: false, - enableSelectionBrush: true, - onSelectionBrushEnd: this.handleSelectionBrushEnd, - }; - const stacked = visConfig && visConfig.stacked; - if (stacked) { - chartProps.stackBy = 'y'; - } - const bounds = visConfig && visConfig.bounds; - if (bounds) { - chartProps.yDomain = [bounds.min, bounds.max]; - } - if (!metric) { - chartProps.statusText = intl.formatMessage({ - id: 'xpack.infra.chartSection.missingMetricDataText', - defaultMessage: 'Missing data', - }); - } - if (metric.series.some(seriesHasLessThen2DataPoints)) { - chartProps.statusText = intl.formatMessage({ - id: 'xpack.infra.chartSection.notEnoughDataPointsToRenderText', - defaultMessage: 'Not enough data points to render chart, try increasing the time range.', - }); - } - const formatter = get(visConfig, 'formatter', InfraFormatterType.number); - const formatterTemplate = get(visConfig, 'formatterTemplate', '{{value}}'); - const formatterFunction = getFormatter(formatter, formatterTemplate); - const seriesLabels = get(metric, 'series', [] as InfraDataSeries[]).map(s => - getChartName(section, s.id) - ); - const seriesColors = get(metric, 'series', [] as InfraDataSeries[]).map( - s => getChartColor(section, s.id) || '' + ({ onChangeRangeTime, section, metric, intl, stopLiveStreaming, isLiveStreaming }: Props) => { + const { visConfig } = section; + const formatter = get(visConfig, 'formatter', InfraFormatterType.number); + const formatterTemplate = get(visConfig, 'formatterTemplate', '{{value}}'); + const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ + formatter, + formatterTemplate, + ]); + const dateFormatter = useCallback(niceTimeFormatter(getMaxMinTimestamp(metric)), [metric]); + const handleTimeChange = useCallback( + (from: number, to: number) => { + if (onChangeRangeTime) { + if (isLiveStreaming && stopLiveStreaming) { + stopLiveStreaming(); + } + onChangeRangeTime({ + from, + to, + interval: '>=1m', + }); + } + }, + [onChangeRangeTime, isLiveStreaming, stopLiveStreaming] + ); + + if (!metric) { + return ( + ); - const itemsFormatter = createItemsFormatter(formatterFunction, seriesLabels, seriesColors); + } + + if (metric.series.some(seriesHasLessThen2DataPoints)) { return ( - - -

{section.label}

-
-
- - - - - {metric && - metric.series.map(series => { - if (!series || series.data.length < 2) { - return null; - } - const data = series.data.map(d => { - return { x: d.timestamp, y: d.value || 0, y0: 0 }; - }); - const chartType = getChartType(section, series.id); - const name = getChartName(section, series.id); - const seriesProps: EuiSeriesProps = { - data, - name, - lineSize: 2, - }; - const color = getChartColor(section, series.id); - if (color) { - seriesProps.color = color; - } - const EuiChartComponent = chartComponentsByType[chartType]; - return ( - - ); - })} - -
-
+ ); } - private handleSelectionBrushEnd = (area: Area) => { - const { onChangeRangeTime, isLiveStreaming, stopLiveStreaming } = this.props; - const { startX, endX } = area.domainArea; - if (onChangeRangeTime) { - if (isLiveStreaming && stopLiveStreaming) { - stopLiveStreaming(); - } - onChangeRangeTime({ - to: endX.valueOf(), - from: startX.valueOf(), - interval: '>=1m', - }); - } - }; + return ( + + +

{section.label}

+
+
+ + + + {metric && + metric.series.map(series => ( + + ))} + + +
+
+ ); } ); - -interface DomainArea { - startX: moment.Moment; - endX: moment.Moment; - startY: number; - endY: number; -} - -interface DrawArea { - x0: number; - x1: number; - y0: number; - y1: number; -} - -interface Area { - domainArea: DomainArea; - drawArea: DrawArea; -} diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/error_message.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/error_message.tsx new file mode 100644 index 0000000000000..52ef0033ad634 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/error_message.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; + +interface Props { + title: string; + body: string; +} + +export const ErrorMessage = ({ title, body }: Props) => ( + {title}} body={

{body}

} /> +); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts b/x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts new file mode 100644 index 0000000000000..8db30f6b4c415 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactText } from 'react'; +import Color from 'color'; +import { get, first, last, min, max } from 'lodash'; +import { InfraFormatterType } from '../../../../lib/lib'; +import { createFormatter } from '../../../../utils/formatters'; +import { InfraDataSeries, InfraMetricData } from '../../../../graphql/types'; +import { + InfraMetricLayoutVisualizationType, + InfraMetricLayoutSection, +} from '../../../../pages/metrics/layouts/types'; + +/** + * Returns a formatter + */ +export const getFormatter = (formatter: InfraFormatterType, template: string) => (val: ReactText) => + val != null ? createFormatter(formatter, template)(val) : ''; + +/** + * Does a series have more then two points? + */ +export const seriesHasLessThen2DataPoints = (series: InfraDataSeries): boolean => { + return series.data.length < 2; +}; + +/** + * Returns the minimum and maximum timestamp for a metric + */ +export const getMaxMinTimestamp = (metric: InfraMetricData): [number, number] => { + if (metric.series.some(seriesHasLessThen2DataPoints)) { + return [0, 0]; + } + const values = metric.series.reduce( + (acc, item) => { + const firstRow = first(item.data); + const lastRow = last(item.data); + return acc.concat([ + (firstRow && firstRow.timestamp) || 0, + (lastRow && lastRow.timestamp) || 0, + ]); + }, + [] as number[] + ); + return [min(values), max(values)]; +}; + +/** + * Returns the chart name from the visConfig based on the series id, otherwise it + * just returns the seriesId + */ +export const getChartName = (section: InfraMetricLayoutSection, seriesId: string) => { + return get(section, ['visConfig', 'seriesOverrides', seriesId, 'name'], seriesId); +}; + +/** + * Returns the chart color from the visConfig based on the series id, otherwise it + * just returns a default color of #999 + */ +export const getChartColor = (section: InfraMetricLayoutSection, seriesId: string) => { + const color = new Color( + get(section, ['visConfig', 'seriesOverrides', seriesId, 'color'], '#999') + ); + return color.hex().toString(); +}; + +/** + * Type guard for InfraMetricLayoutVisualizationType + */ +const isInfraMetricLayoutVisualizationType = ( + subject: any +): subject is InfraMetricLayoutVisualizationType => { + return InfraMetricLayoutVisualizationType[subject] != null; +}; + +/** + * Gets the chart type based on the section and seriesId + */ +export const getChartType = (section: InfraMetricLayoutSection, seriesId: string) => { + const value = get(section, ['visConfig', 'type']); + const overrideValue = get(section, ['visConfig', 'seriesOverrides', seriesId, 'type']); + if (isInfraMetricLayoutVisualizationType(overrideValue)) { + return overrideValue; + } + if (isInfraMetricLayoutVisualizationType(value)) { + return value; + } + return InfraMetricLayoutVisualizationType.line; +}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts b/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts index 7c12ff7520566..f5a8fcfed6dd6 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts +++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts @@ -5,8 +5,9 @@ */ import { InfraMetricLayoutSectionType } from '../../../pages/metrics/layouts/types'; -import { ChartSection } from './chart_section'; +// import { ChartSection } from './chart_section'; import { GaugesSection } from './gauges_section'; +import { ChartSection } from './chart_section'; export const sections = { [InfraMetricLayoutSectionType.chart]: ChartSection, diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx new file mode 100644 index 0000000000000..10d05885cbd1c --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + AreaSeries, + BarSeries, + ScaleType, + getSpecId, + DataSeriesColorsValues, + CustomSeriesColorsMap, +} from '@elastic/charts'; +import { InfraMetricLayoutVisualizationType } from '../../../pages/metrics/layouts/types'; +import { InfraDataSeries } from '../../../graphql/types'; + +interface Props { + id: string; + name: string; + color: string; + series: InfraDataSeries; + type: InfraMetricLayoutVisualizationType; + stack: boolean | undefined; +} + +export const SeriesChart = (props: Props) => { + if (props.type === InfraMetricLayoutVisualizationType.bar) { + return ; + } + return ; +}; + +export const AreaChart = ({ id, color, series, name, type, stack }: Props) => { + const style = { + area: { + fill: color, + opacity: 1, + visible: InfraMetricLayoutVisualizationType.area === type, + }, + line: { + stroke: color, + strokeWidth: InfraMetricLayoutVisualizationType.area === type ? 1 : 2, + visible: true, + }, + border: { + visible: false, + strokeWidth: 2, + stroke: color, + }, + point: { + visible: false, + radius: 0.2, + stroke: color, + strokeWidth: 2, + opacity: 1, + }, + }; + const colors: DataSeriesColorsValues = { + colorValues: [], + specId: getSpecId(id), + }; + const customColors: CustomSeriesColorsMap = new Map(); + customColors.set(colors, color); + return ( + + ); +}; + +export const BarChart = ({ id, color, series, name, type, stack }: Props) => { + const style = { + rectBorder: { + stroke: color, + strokeWidth: 1, + visible: true, + }, + border: { + visible: false, + strokeWidth: 2, + stroke: color, + }, + rect: { + fill: color, + opacity: 1, + }, + }; + const colors: DataSeriesColorsValues = { + colorValues: [], + specId: getSpecId(id), + }; + const customColors: CustomSeriesColorsMap = new Map(); + customColors.set(colors, color); + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/host.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/host.ts index 73556a8a1b236..753d97d07db16 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/host.ts +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/host.ts @@ -104,7 +104,6 @@ export const hostLayoutCreator: InfraMetricLayoutCreator = theme => [ stacked: true, type: InfraMetricLayoutVisualizationType.area, formatter: InfraFormatterType.percent, - bounds: { min: 0, max: 1 }, seriesOverrides: { user: { color: theme.eui.euiColorVis0 }, system: { color: theme.eui.euiColorVis2 }, diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/types.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/types.ts index d9ad1d943f5a3..c48a2ae0fcbfb 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/types.ts +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/layouts/types.ts @@ -36,7 +36,6 @@ export interface InfraMetricLayoutVisualizationConfig { type?: InfraMetricLayoutVisualizationType; formatter?: InfraFormatterType; formatterTemplate?: string; - bounds?: { min: number; max: number }; seriesOverrides: SeriesOverrideObject; }