mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
translate InfraOps visualization component (Part 2) (#25212)
* translate InfraOps visualization component (Part 2 - part of folder components and root files) * update translation of Beats Management vizualization component (Part 2) * update translation of Infra Ops vizualization component (Part 2) * update translation of Infra Ops vizualization component (Part 2) * update translation of Infra Ops vizualization component (Part 2) * change some ids and add some logic * update Infra Ops Part 2 - directly wrap some classes by injectI18n() * Update Infra Part-II change some code * update Infra-II - add static to displayName, add needed translations * update Infra-II - fix errors wich broke CI * update Infra-II - fix errors wich broke CI * update Infra-II - fix errors * update Infra-II - fix errors in group_by_controls * update Infra-II - update nodeType in files with errors * update Infra-II * update Infra-II * update Infra-II - update one type * add one empty line, use lodash get method
This commit is contained in:
parent
9e94fef2ee
commit
584e68fb3b
15 changed files with 1405 additions and 855 deletions
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { I18nServiceType } from '@kbn/i18n/angular';
|
||||
|
||||
export enum FeatureCatalogueCategory {
|
||||
ADMIN = 'admin',
|
||||
DATA = 'data',
|
||||
|
@ -33,7 +35,7 @@ interface FeatureCatalogueObject {
|
|||
category: FeatureCatalogueCategory;
|
||||
}
|
||||
|
||||
type FeatureCatalogueRegistryFunction = () => FeatureCatalogueObject;
|
||||
type FeatureCatalogueRegistryFunction = (i18n: I18nServiceType) => FeatureCatalogueObject;
|
||||
|
||||
export const FeatureCatalogueRegistryProvider: {
|
||||
register: (fn: FeatureCatalogueRegistryFunction) => void;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import JoiNamespace from 'joi';
|
||||
import { resolve } from 'path';
|
||||
|
||||
|
@ -19,31 +20,43 @@ export function infra(kibana: any) {
|
|||
require: ['kibana', 'elasticsearch'],
|
||||
uiExports: {
|
||||
app: {
|
||||
description: 'Explore your infrastructure',
|
||||
description: i18n.translate('xpack.infra.infrastructureDescription', {
|
||||
defaultMessage: 'Explore your infrastructure',
|
||||
}),
|
||||
icon: 'plugins/infra/images/infra_mono_white.svg',
|
||||
main: 'plugins/infra/app',
|
||||
title: 'Infrastructure',
|
||||
title: i18n.translate('xpack.infra.infrastructureTitle', {
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
listed: false,
|
||||
url: `/app/${APP_ID}#/home`,
|
||||
},
|
||||
home: ['plugins/infra/register_feature'],
|
||||
links: [
|
||||
{
|
||||
description: 'Explore your infrastructure',
|
||||
description: i18n.translate('xpack.infra.linkInfrastructureDescription', {
|
||||
defaultMessage: 'Explore your infrastructure',
|
||||
}),
|
||||
icon: 'plugins/infra/images/infra_mono_white.svg',
|
||||
euiIconType: 'infraApp',
|
||||
id: 'infra:home',
|
||||
order: 8000,
|
||||
title: 'Infrastructure',
|
||||
title: i18n.translate('xpack.infra.linkInfrastructureTitle', {
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
url: `/app/${APP_ID}#/home`,
|
||||
},
|
||||
{
|
||||
description: 'Explore your logs',
|
||||
description: i18n.translate('xpack.infra.linkLogsDescription', {
|
||||
defaultMessage: 'Explore your logs',
|
||||
}),
|
||||
icon: 'plugins/infra/images/logging_mono_white.svg',
|
||||
euiIconType: 'loggingApp',
|
||||
id: 'infra:logs',
|
||||
order: 8001,
|
||||
title: 'Logs',
|
||||
title: i18n.translate('xpack.infra.linkLogsTitle', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
url: `/app/${APP_ID}#/logs`,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiPageContentBody, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { InfraMetricData } from '../../../common/graphql/types';
|
||||
|
@ -19,66 +20,87 @@ interface Props {
|
|||
loading: boolean;
|
||||
nodeName: string;
|
||||
onChangeRangeTime?: (time: metricTimeActions.MetricRangeTimeState) => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
interface State {
|
||||
crosshairValue: number | null;
|
||||
}
|
||||
|
||||
export class Metrics extends React.PureComponent<Props, State> {
|
||||
public readonly state = {
|
||||
crosshairValue: null,
|
||||
};
|
||||
export const Metrics = injectI18n(
|
||||
class extends React.PureComponent<Props, State> {
|
||||
public static displayName = 'Metrics';
|
||||
public readonly state = {
|
||||
crosshairValue: null,
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (this.props.loading) {
|
||||
public render() {
|
||||
const { intl } = this.props;
|
||||
if (this.props.loading) {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height="100vh"
|
||||
width="auto"
|
||||
text={intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.metrics.loadingNodeDataText',
|
||||
defaultMessage: 'Loading data for {nodeName}',
|
||||
},
|
||||
{
|
||||
nodeName: this.props.nodeName,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <React.Fragment>{this.props.layouts.map(this.renderLayout)}</React.Fragment>;
|
||||
}
|
||||
|
||||
private renderLayout = (layout: InfraMetricLayout) => {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height="100vh"
|
||||
width="auto"
|
||||
text={`Loading data for ${this.props.nodeName}`}
|
||||
<React.Fragment key={layout.id}>
|
||||
<EuiPageContentBody>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={layout.id}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.layoutLabelOverviewTitle"
|
||||
defaultMessage="{layoutLabel} Overview"
|
||||
values={{
|
||||
layoutLabel: layout.label,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiPageContentBody>
|
||||
{layout.sections.map(this.renderSection(layout))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private renderSection = (layout: InfraMetricLayout) => (section: InfraMetricLayoutSection) => {
|
||||
let sectionProps = {};
|
||||
if (section.type === 'chart') {
|
||||
const { onChangeRangeTime } = this.props;
|
||||
sectionProps = {
|
||||
onChangeRangeTime,
|
||||
crosshairValue: this.state.crosshairValue,
|
||||
onCrosshairUpdate: this.onCrosshairUpdate,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
section={section}
|
||||
metrics={this.props.metrics}
|
||||
key={`${layout.id}-${section.id}`}
|
||||
{...sectionProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <React.Fragment>{this.props.layouts.map(this.renderLayout)}</React.Fragment>;
|
||||
};
|
||||
|
||||
private onCrosshairUpdate = (crosshairValue: number) => {
|
||||
this.setState({
|
||||
crosshairValue,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private renderLayout = (layout: InfraMetricLayout) => {
|
||||
return (
|
||||
<React.Fragment key={layout.id}>
|
||||
<EuiPageContentBody>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={layout.id}>{`${layout.label} Overview`}</h2>
|
||||
</EuiTitle>
|
||||
</EuiPageContentBody>
|
||||
{layout.sections.map(this.renderSection(layout))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private renderSection = (layout: InfraMetricLayout) => (section: InfraMetricLayoutSection) => {
|
||||
let sectionProps = {};
|
||||
if (section.type === 'chart') {
|
||||
const { onChangeRangeTime } = this.props;
|
||||
sectionProps = {
|
||||
onChangeRangeTime,
|
||||
crosshairValue: this.state.crosshairValue,
|
||||
onCrosshairUpdate: this.onCrosshairUpdate,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
section={section}
|
||||
metrics={this.props.metrics}
|
||||
key={`${layout.id}-${section.id}`}
|
||||
{...sectionProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private onCrosshairUpdate = (crosshairValue: number) => {
|
||||
this.setState({
|
||||
crosshairValue,
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
EuiXAxis,
|
||||
EuiYAxis,
|
||||
} from '@elastic/eui/lib/experimental';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import Color from 'color';
|
||||
import { get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
@ -42,6 +43,7 @@ interface Props {
|
|||
onChangeRangeTime?: (time: metricTimeActions.MetricRangeTimeState) => void;
|
||||
crosshairValue?: number;
|
||||
onCrosshairUpdate?: (crosshairValue: number) => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
const isInfraMetricLayoutVisualizationType = (
|
||||
|
@ -115,106 +117,114 @@ const seriesHasLessThen2DataPoints = (series: InfraDataSeries): boolean => {
|
|||
return series.data.length < 2;
|
||||
};
|
||||
|
||||
export class ChartSection extends React.PureComponent<Props> {
|
||||
public render() {
|
||||
const { crosshairValue, section, metric, onCrosshairUpdate } = 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';
|
||||
export const ChartSection = injectI18n(
|
||||
class extends React.PureComponent<Props> {
|
||||
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) || ''
|
||||
);
|
||||
const itemsFormatter = createItemsFormatter(formatterFunction, seriesLabels, seriesColors);
|
||||
return (
|
||||
<EuiPageContentBody>
|
||||
<EuiTitle size="s">
|
||||
<h3 id={section.id}>{section.label}</h3>
|
||||
</EuiTitle>
|
||||
<div style={{ height: 200 }}>
|
||||
<EuiSeriesChart {...chartProps}>
|
||||
<EuiXAxis marginLeft={MARGIN_LEFT} />
|
||||
<EuiYAxis tickFormat={formatterFunction} marginLeft={MARGIN_LEFT} />
|
||||
<EuiCrosshairX
|
||||
seriesNames={seriesLabels}
|
||||
itemsFormat={itemsFormatter}
|
||||
titleFormat={titleFormatter}
|
||||
{...crossHairProps}
|
||||
/>
|
||||
{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 (
|
||||
<EuiChartComponent
|
||||
key={`${section.id}-${series.id}`}
|
||||
{...seriesProps}
|
||||
marginLeft={MARGIN_LEFT}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiSeriesChart>
|
||||
</div>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
}
|
||||
const bounds = visConfig && visConfig.bounds;
|
||||
if (bounds) {
|
||||
chartProps.yDomain = [bounds.min, bounds.max];
|
||||
}
|
||||
if (!metric) {
|
||||
chartProps.statusText = 'Missing data';
|
||||
}
|
||||
if (metric.series.some(seriesHasLessThen2DataPoints)) {
|
||||
chartProps.statusText =
|
||||
'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) || ''
|
||||
);
|
||||
const itemsFormatter = createItemsFormatter(formatterFunction, seriesLabels, seriesColors);
|
||||
return (
|
||||
<EuiPageContentBody>
|
||||
<EuiTitle size="s">
|
||||
<h3 id={section.id}>{section.label}</h3>
|
||||
</EuiTitle>
|
||||
<div style={{ height: 200 }}>
|
||||
<EuiSeriesChart {...chartProps}>
|
||||
<EuiXAxis marginLeft={MARGIN_LEFT} />
|
||||
<EuiYAxis tickFormat={formatterFunction} marginLeft={MARGIN_LEFT} />
|
||||
<EuiCrosshairX
|
||||
seriesNames={seriesLabels}
|
||||
itemsFormat={itemsFormatter}
|
||||
titleFormat={titleFormatter}
|
||||
{...crossHairProps}
|
||||
/>
|
||||
{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 (
|
||||
<EuiChartComponent
|
||||
key={`${section.id}-${series.id}`}
|
||||
{...seriesProps}
|
||||
marginLeft={MARGIN_LEFT}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiSeriesChart>
|
||||
</div>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
}
|
||||
|
||||
private handleSelectionBrushEnd = (area: Area) => {
|
||||
const { onChangeRangeTime } = this.props;
|
||||
const { startX, endX } = area.domainArea;
|
||||
if (onChangeRangeTime) {
|
||||
onChangeRangeTime({
|
||||
to: endX.valueOf(),
|
||||
from: startX.valueOf(),
|
||||
} as metricTimeActions.MetricRangeTimeState);
|
||||
}
|
||||
};
|
||||
}
|
||||
private handleSelectionBrushEnd = (area: Area) => {
|
||||
const { onChangeRangeTime } = this.props;
|
||||
const { startX, endX } = area.domainArea;
|
||||
if (onChangeRangeTime) {
|
||||
onChangeRangeTime({
|
||||
to: endX.valueOf(),
|
||||
from: startX.valueOf(),
|
||||
} as metricTimeActions.MetricRangeTimeState);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
interface DomainArea {
|
||||
startX: moment.Moment;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -53,16 +54,27 @@ export class MetricsTimeControls extends React.Component<
|
|||
iconType="pause"
|
||||
onClick={this.stopLiveStreaming}
|
||||
>
|
||||
Stop refreshing
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metricsTimeControls.stopRefreshingButtonLabel"
|
||||
defaultMessage="Stop refreshing"
|
||||
/>
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton iconSide="left" iconType="play" onClick={this.startLiveStreaming}>
|
||||
Auto-refresh
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metricsTimeControls.autoRefreshButtonLabel"
|
||||
defaultMessage="Auto-refresh"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={this.resetSearch}>Reset</EuiButtonEmpty>
|
||||
<EuiButtonEmpty onClick={this.resetSearch}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metricsTimeControls.resetButtonLabel"
|
||||
defaultMessage="Reset"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
@ -72,11 +84,19 @@ export class MetricsTimeControls extends React.Component<
|
|||
<EuiFlexGroup gutterSize="s" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color={goColor} fill onClick={this.searchRangeTime}>
|
||||
Go
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metricsTimeControls.goButtonLabel"
|
||||
defaultMessage="Go"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={this.cancelSearch}>Cancel</EuiButtonEmpty>
|
||||
<EuiButtonEmpty onClick={this.cancelSearch}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metricsTimeControls.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { find } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { find, get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
|
@ -28,15 +30,156 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const commonDates = [
|
||||
'Today',
|
||||
'Yesterday',
|
||||
'This week',
|
||||
'Week to date',
|
||||
'This month',
|
||||
'Month to date',
|
||||
'This year',
|
||||
'Year to date',
|
||||
enum DatePickerDateOptions {
|
||||
today = 'today',
|
||||
yesterday = 'yesterday',
|
||||
thisWeek = 'this_week',
|
||||
weekToDate = 'week_to_date',
|
||||
thisMonth = 'this_month',
|
||||
monthToDate = 'month_to_date',
|
||||
thisYear = 'this_year',
|
||||
yearToDate = 'year_to_date',
|
||||
}
|
||||
|
||||
const commonDates: Array<{ id: string; label: any }> = [
|
||||
{
|
||||
id: DatePickerDateOptions.today,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.todayText', {
|
||||
defaultMessage: 'Today',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.yesterday,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.yesterdayText', {
|
||||
defaultMessage: 'Yesterday',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.thisWeek,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.thisWeekText', {
|
||||
defaultMessage: 'This week',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.weekToDate,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.weekToDateText', {
|
||||
defaultMessage: 'Week to date',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.thisMonth,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.thisMonthText', {
|
||||
defaultMessage: 'This month',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.monthToDate,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.monthToDateText', {
|
||||
defaultMessage: 'Month to date',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.thisYear,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.thisYearText', {
|
||||
defaultMessage: 'This year',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DatePickerDateOptions.yearToDate,
|
||||
label: i18n.translate('xpack.infra.rangeDatePicker.yearToDateText', {
|
||||
defaultMessage: 'Year to date',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const singleLastOptions: Array<{ value: string; text: any }> = [
|
||||
{
|
||||
value: 'seconds',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.secondLabel', {
|
||||
defaultMessage: 'second',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'minutes',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.minuteLabel', {
|
||||
defaultMessage: 'minute',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'hours',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.hourLabel', {
|
||||
defaultMessage: 'hour',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'days',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.dayLabel', {
|
||||
defaultMessage: 'day',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'weeks',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.weekLabel', {
|
||||
defaultMessage: 'week',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'months',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.monthLabel', {
|
||||
defaultMessage: 'month',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'years',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.singleUnitOptions.yearLabel', {
|
||||
defaultMessage: 'year',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const pluralLastOptions: Array<{ value: string; text: any }> = [
|
||||
{
|
||||
value: 'seconds',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.secondsLabel', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'minutes',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.minutesLabel', {
|
||||
defaultMessage: 'minutes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'hours',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.hoursLabel', {
|
||||
defaultMessage: 'hours',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'days',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.daysLabel', {
|
||||
defaultMessage: 'days',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'weeks',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.weeksLabel', {
|
||||
defaultMessage: 'weeks',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'months',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.monthsLabel', {
|
||||
defaultMessage: 'months',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'years',
|
||||
text: i18n.translate('xpack.infra.rangeDatePicker.pluralUnitOptions.yearsLabel', {
|
||||
defaultMessage: 'years',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
interface RangeDatePickerProps {
|
||||
|
@ -51,6 +194,7 @@ interface RangeDatePickerProps {
|
|||
disabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
ref?: React.RefObject<any>;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export interface RecentlyUsed {
|
||||
|
@ -67,350 +211,391 @@ interface RangeDatePickerState {
|
|||
quickSelectUnit: string;
|
||||
}
|
||||
|
||||
export class RangeDatePicker extends React.PureComponent<
|
||||
RangeDatePickerProps,
|
||||
RangeDatePickerState
|
||||
> {
|
||||
public readonly state = {
|
||||
startDate: this.props.startDate,
|
||||
endDate: this.props.endDate,
|
||||
isPopoverOpen: false,
|
||||
recentlyUsed: [],
|
||||
quickSelectTime: 1,
|
||||
quickSelectUnit: 'hours',
|
||||
};
|
||||
export const RangeDatePicker = injectI18n(
|
||||
class extends React.PureComponent<RangeDatePickerProps, RangeDatePickerState> {
|
||||
public static displayName = 'RangeDatePicker';
|
||||
public readonly state = {
|
||||
startDate: this.props.startDate,
|
||||
endDate: this.props.endDate,
|
||||
isPopoverOpen: false,
|
||||
recentlyUsed: [],
|
||||
quickSelectTime: 1,
|
||||
quickSelectUnit: 'hours',
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { isLoading, disabled } = this.props;
|
||||
const { startDate, endDate } = this.state;
|
||||
const quickSelectButton = (
|
||||
<EuiButtonEmpty
|
||||
className="euiFormControlLayout__prepend"
|
||||
style={{ borderRight: 'none' }}
|
||||
onClick={this.onButtonClick}
|
||||
disabled={disabled}
|
||||
aria-label="Date quick select"
|
||||
size="xs"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
>
|
||||
<EuiIcon type="calendar" />
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
const commonlyUsed = this.renderCommonlyUsed(commonDates);
|
||||
const recentlyUsed = this.renderRecentlyUsed([
|
||||
...this.state.recentlyUsed,
|
||||
...this.props.recentlyUsed,
|
||||
]);
|
||||
|
||||
const quickSelectPopover = (
|
||||
<EuiPopover
|
||||
id="QuickSelectPopover"
|
||||
button={quickSelectButton}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
anchorPosition="downLeft"
|
||||
ownFocus
|
||||
>
|
||||
<div style={{ width: '400px' }}>
|
||||
{this.renderQuickSelect()}
|
||||
<EuiHorizontalRule />
|
||||
{commonlyUsed}
|
||||
<EuiHorizontalRule />
|
||||
{recentlyUsed}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFormControlLayout prepend={quickSelectPopover}>
|
||||
<EuiDatePickerRange
|
||||
className="euiDatePickerRange--inGroup"
|
||||
iconType={false}
|
||||
public render() {
|
||||
const { isLoading, disabled, intl } = this.props;
|
||||
const { startDate, endDate } = this.state;
|
||||
const quickSelectButton = (
|
||||
<EuiButtonEmpty
|
||||
className="euiFormControlLayout__prepend"
|
||||
style={{ borderRight: 'none' }}
|
||||
onClick={this.onButtonClick}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
startDateControl={
|
||||
<EuiDatePicker
|
||||
dateFormat="L LTS"
|
||||
selected={startDate}
|
||||
onChange={this.handleChangeStart}
|
||||
isInvalid={startDate && endDate ? startDate > endDate : false}
|
||||
fullWidth
|
||||
aria-label="Start date"
|
||||
disabled={disabled}
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
/>
|
||||
}
|
||||
endDateControl={
|
||||
<EuiDatePicker
|
||||
dateFormat="L LTS"
|
||||
selected={endDate}
|
||||
onChange={this.handleChangeEnd}
|
||||
isInvalid={startDate && endDate ? startDate > endDate : false}
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
aria-label="End date"
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
popperPlacement="top-end"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
);
|
||||
}
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'xpack.infra.rangeDatePicker.dateQuickSelectAriaLabel',
|
||||
defaultMessage: 'Date quick select',
|
||||
})}
|
||||
size="xs"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
>
|
||||
<EuiIcon type="calendar" />
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
public resetRangeDate(startDate: moment.Moment, endDate: moment.Moment) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}
|
||||
const commonlyUsed = this.renderCommonlyUsed(commonDates);
|
||||
const recentlyUsed = this.renderRecentlyUsed([
|
||||
...this.state.recentlyUsed,
|
||||
...this.props.recentlyUsed,
|
||||
]);
|
||||
|
||||
private handleChangeStart = (date: moment.Moment | null) => {
|
||||
if (date && this.state.startDate !== date) {
|
||||
this.props.onChangeRangeTime(date, this.state.endDate, false);
|
||||
this.setState({
|
||||
startDate: date,
|
||||
});
|
||||
const quickSelectPopover = (
|
||||
<EuiPopover
|
||||
id="QuickSelectPopover"
|
||||
button={quickSelectButton}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover.bind(this)}
|
||||
anchorPosition="downLeft"
|
||||
ownFocus
|
||||
>
|
||||
<div style={{ width: '400px' }}>
|
||||
{this.renderQuickSelect()}
|
||||
<EuiHorizontalRule />
|
||||
{commonlyUsed}
|
||||
<EuiHorizontalRule />
|
||||
{recentlyUsed}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFormControlLayout prepend={quickSelectPopover}>
|
||||
<EuiDatePickerRange
|
||||
className="euiDatePickerRange--inGroup"
|
||||
iconType={false}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
startDateControl={
|
||||
<EuiDatePicker
|
||||
dateFormat="L LTS"
|
||||
selected={startDate}
|
||||
onChange={this.handleChangeStart}
|
||||
isInvalid={startDate && endDate ? startDate > endDate : false}
|
||||
fullWidth
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'xpack.infra.rangeDatePicker.startDateAriaLabel',
|
||||
defaultMessage: 'Start date',
|
||||
})}
|
||||
disabled={disabled}
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
/>
|
||||
}
|
||||
endDateControl={
|
||||
<EuiDatePicker
|
||||
dateFormat="L LTS"
|
||||
selected={endDate}
|
||||
onChange={this.handleChangeEnd}
|
||||
isInvalid={startDate && endDate ? startDate > endDate : false}
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'xpack.infra.rangeDatePicker.endDateAriaLabel',
|
||||
defaultMessage: 'End date',
|
||||
})}
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
popperPlacement="top-end"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private handleChangeEnd = (date: moment.Moment | null) => {
|
||||
if (date && this.state.endDate !== date) {
|
||||
this.props.onChangeRangeTime(this.state.startDate, date, false);
|
||||
public resetRangeDate(startDate: moment.Moment, endDate: moment.Moment) {
|
||||
this.setState({
|
||||
endDate: date,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onButtonClick = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
};
|
||||
|
||||
private closePopover = (type: string, from?: string, to?: string) => {
|
||||
const { startDate, endDate, recentlyUsed } = this.managedStartEndDateFromType(type, from, to);
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
isPopoverOpen: false,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}
|
||||
|
||||
private handleChangeStart = (date: moment.Moment | null) => {
|
||||
if (date && this.state.startDate !== date) {
|
||||
this.props.onChangeRangeTime(date, this.state.endDate, false);
|
||||
this.setState({
|
||||
startDate: date,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private handleChangeEnd = (date: moment.Moment | null) => {
|
||||
if (date && this.state.endDate !== date) {
|
||||
this.props.onChangeRangeTime(this.state.startDate, date, false);
|
||||
this.setState({
|
||||
endDate: date,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onButtonClick = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
};
|
||||
|
||||
private closePopover = (type: string, from?: string, to?: string) => {
|
||||
const { startDate, endDate, recentlyUsed } = this.managedStartEndDateFromType(type, from, to);
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
isPopoverOpen: false,
|
||||
startDate,
|
||||
endDate,
|
||||
recentlyUsed,
|
||||
},
|
||||
() => {
|
||||
if (type) {
|
||||
this.props.onChangeRangeTime(startDate, endDate, true);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private managedStartEndDateFromType(type: string, from?: string, to?: string) {
|
||||
const { intl } = this.props;
|
||||
let { startDate, endDate } = this.state;
|
||||
let recentlyUsed: RecentlyUsed[] = this.state.recentlyUsed;
|
||||
let textJustUsed = type;
|
||||
|
||||
if (type === 'quick-select') {
|
||||
textJustUsed = intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.rangeDatePicker.lastQuickSelectTimeText',
|
||||
defaultMessage: 'Last {quickSelectTime} {quickSelectUnit}',
|
||||
},
|
||||
{
|
||||
quickSelectTime: this.state.quickSelectTime,
|
||||
quickSelectUnit:
|
||||
this.state.quickSelectTime === 1
|
||||
? get(find(singleLastOptions, { value: this.state.quickSelectUnit }), 'text')
|
||||
: get(find(pluralLastOptions, { value: this.state.quickSelectUnit }), 'text'),
|
||||
}
|
||||
);
|
||||
startDate = moment().subtract(this.state.quickSelectTime, this.state
|
||||
.quickSelectUnit as moment.unitOfTime.DurationConstructor);
|
||||
endDate = moment();
|
||||
} else if (type === DatePickerDateOptions.today) {
|
||||
startDate = moment().startOf('day');
|
||||
endDate = moment()
|
||||
.startOf('day')
|
||||
.add(24, 'hour');
|
||||
} else if (type === DatePickerDateOptions.yesterday) {
|
||||
startDate = moment()
|
||||
.subtract(1, 'day')
|
||||
.startOf('day');
|
||||
endDate = moment()
|
||||
.subtract(1, 'day')
|
||||
.startOf('day')
|
||||
.add(24, 'hour');
|
||||
} else if (type === DatePickerDateOptions.thisWeek) {
|
||||
startDate = moment().startOf('week');
|
||||
endDate = moment()
|
||||
.startOf('week')
|
||||
.add(1, 'week');
|
||||
} else if (type === DatePickerDateOptions.weekToDate) {
|
||||
startDate = moment().subtract(1, 'week');
|
||||
endDate = moment();
|
||||
} else if (type === DatePickerDateOptions.thisMonth) {
|
||||
startDate = moment().startOf('month');
|
||||
endDate = moment()
|
||||
.startOf('month')
|
||||
.add(1, 'month');
|
||||
} else if (type === DatePickerDateOptions.monthToDate) {
|
||||
startDate = moment().subtract(1, 'month');
|
||||
endDate = moment();
|
||||
} else if (type === DatePickerDateOptions.thisYear) {
|
||||
startDate = moment().startOf('year');
|
||||
endDate = moment()
|
||||
.startOf('year')
|
||||
.add(1, 'year');
|
||||
} else if (type === DatePickerDateOptions.yearToDate) {
|
||||
startDate = moment().subtract(1, 'year');
|
||||
endDate = moment();
|
||||
} else if (type === 'date-range' && to && from) {
|
||||
startDate = moment(from);
|
||||
endDate = moment(to);
|
||||
}
|
||||
|
||||
textJustUsed =
|
||||
type === 'date-range' || !type ? type : get(find(commonDates, { id: type }), 'label');
|
||||
|
||||
if (textJustUsed !== undefined && !find(recentlyUsed, ['text', textJustUsed])) {
|
||||
recentlyUsed.unshift({ type, text: textJustUsed });
|
||||
recentlyUsed = recentlyUsed.slice(0, 5);
|
||||
}
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
recentlyUsed,
|
||||
},
|
||||
() => {
|
||||
if (type) {
|
||||
this.props.onChangeRangeTime(startDate, endDate, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private renderQuickSelect = () => {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.rangeDatePicker.quickSelectTitle"
|
||||
defaultMessage="Quick select"
|
||||
/>
|
||||
</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.rangeDatePicker.lastQuickSelectTitle"
|
||||
defaultMessage="Last"
|
||||
/>
|
||||
</span>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow>
|
||||
<EuiFieldNumber
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'xpack.infra.rangeDatePicker.countOfFormRowAriaLabel',
|
||||
defaultMessage: 'Count of',
|
||||
})}
|
||||
defaultValue="1"
|
||||
value={this.state.quickSelectTime}
|
||||
step={0}
|
||||
onChange={arg => {
|
||||
this.onChange('quickSelectTime', arg);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow>
|
||||
<EuiSelect
|
||||
value={this.state.quickSelectUnit}
|
||||
options={this.state.quickSelectTime === 1 ? singleLastOptions : pluralLastOptions}
|
||||
onChange={arg => {
|
||||
this.onChange('quickSelectUnit', arg);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow>
|
||||
<EuiButton
|
||||
onClick={() => this.closePopover('quick-select')}
|
||||
style={{ minWidth: 0 }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.rangeDatePicker.applyFormRowButtonLabel"
|
||||
defaultMessage="Apply"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private onChange = (stateType: string, args: any) => {
|
||||
let value = args.currentTarget.value;
|
||||
|
||||
if (stateType === 'quickSelectTime' && value !== '') {
|
||||
value = parseInt(args.currentTarget.value, 10);
|
||||
}
|
||||
);
|
||||
};
|
||||
this.setState({
|
||||
...this.state,
|
||||
[stateType]: value,
|
||||
});
|
||||
};
|
||||
|
||||
private managedStartEndDateFromType(type: string, from?: string, to?: string) {
|
||||
let { startDate, endDate } = this.state;
|
||||
let recentlyUsed: RecentlyUsed[] = this.state.recentlyUsed;
|
||||
let textJustUsed = type;
|
||||
private renderCommonlyUsed = (recentlyCommonDates: Array<{ id: string; label: any }>) => {
|
||||
const links = recentlyCommonDates.map(date => {
|
||||
return (
|
||||
<EuiFlexItem key={date.id}>
|
||||
<EuiLink onClick={() => this.closePopover(date.id)}>{date.label}</EuiLink>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
if (type === 'quick-select') {
|
||||
textJustUsed = `Last ${this.state.quickSelectTime} ${singularize(
|
||||
this.state.quickSelectUnit,
|
||||
this.state.quickSelectTime
|
||||
)}`;
|
||||
startDate = moment().subtract(this.state.quickSelectTime, this.state
|
||||
.quickSelectUnit as moment.unitOfTime.DurationConstructor);
|
||||
endDate = moment();
|
||||
} else if (type === 'Today') {
|
||||
startDate = moment().startOf('day');
|
||||
endDate = moment()
|
||||
.startOf('day')
|
||||
.add(24, 'hour');
|
||||
} else if (type === 'Yesterday') {
|
||||
startDate = moment()
|
||||
.subtract(1, 'day')
|
||||
.startOf('day');
|
||||
endDate = moment()
|
||||
.subtract(1, 'day')
|
||||
.startOf('day')
|
||||
.add(24, 'hour');
|
||||
} else if (type === 'This week') {
|
||||
startDate = moment().startOf('week');
|
||||
endDate = moment()
|
||||
.startOf('week')
|
||||
.add(1, 'week');
|
||||
} else if (type === 'Week to date') {
|
||||
startDate = moment().subtract(1, 'week');
|
||||
endDate = moment();
|
||||
} else if (type === 'This month') {
|
||||
startDate = moment().startOf('month');
|
||||
endDate = moment()
|
||||
.startOf('month')
|
||||
.add(1, 'month');
|
||||
} else if (type === 'Month to date') {
|
||||
startDate = moment().subtract(1, 'month');
|
||||
endDate = moment();
|
||||
} else if (type === 'This year') {
|
||||
startDate = moment().startOf('year');
|
||||
endDate = moment()
|
||||
.startOf('year')
|
||||
.add(1, 'year');
|
||||
} else if (type === 'Year to date') {
|
||||
startDate = moment().subtract(1, 'year');
|
||||
endDate = moment();
|
||||
} else if (type === 'date-range' && to && from) {
|
||||
startDate = moment(from);
|
||||
endDate = moment(to);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.rangeDatePicker.renderCommonlyUsedLinksTitle"
|
||||
defaultMessage="Commonly used"
|
||||
/>
|
||||
</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<EuiFlexGrid gutterSize="s" columns={2}>
|
||||
{links}
|
||||
</EuiFlexGrid>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
if (textJustUsed !== undefined && !find(recentlyUsed, ['text', textJustUsed])) {
|
||||
recentlyUsed.unshift({ type, text: textJustUsed });
|
||||
recentlyUsed = recentlyUsed.slice(0, 5);
|
||||
}
|
||||
private renderRecentlyUsed = (recentDates: RecentlyUsed[]) => {
|
||||
const links = recentDates.map((date: RecentlyUsed) => {
|
||||
let dateRange;
|
||||
let dateLink = (
|
||||
<EuiLink onClick={() => this.closePopover(date.type)}>{dateRange || date.text}</EuiLink>
|
||||
);
|
||||
if (typeof date.text !== 'string') {
|
||||
dateRange = `${date.text[0]} – ${date.text[1]}`;
|
||||
dateLink = (
|
||||
<EuiLink onClick={() => this.closePopover(date.type, date.text[0], date.text[1])}>
|
||||
{dateRange || date.type}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
recentlyUsed,
|
||||
return (
|
||||
<EuiFlexItem grow={false} key={`${dateRange || date.type}`}>
|
||||
{dateLink}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.rangeDatePicker.recentlyUsedDateRangesTitle"
|
||||
defaultMessage="Recently used date ranges"
|
||||
/>
|
||||
</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<EuiFlexGroup gutterSize="s" style={{ flexDirection: 'column' }}>
|
||||
{links}
|
||||
</EuiFlexGroup>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private renderQuickSelect = () => {
|
||||
const lastOptions = [
|
||||
{ value: 'seconds', text: singularize('seconds', this.state.quickSelectTime) },
|
||||
{ value: 'minutes', text: singularize('minutes', this.state.quickSelectTime) },
|
||||
{ value: 'hours', text: singularize('hours', this.state.quickSelectTime) },
|
||||
{ value: 'days', text: singularize('days', this.state.quickSelectTime) },
|
||||
{ value: 'weeks', text: singularize('weeks', this.state.quickSelectTime) },
|
||||
{ value: 'months', text: singularize('months', this.state.quickSelectTime) },
|
||||
{ value: 'years', text: singularize('years', this.state.quickSelectTime) },
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>Quick select</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<span>Last</span>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow>
|
||||
<EuiFieldNumber
|
||||
aria-label="Count of"
|
||||
defaultValue="1"
|
||||
value={this.state.quickSelectTime}
|
||||
step={0}
|
||||
onChange={arg => {
|
||||
this.onChange('quickSelectTime', arg);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow>
|
||||
<EuiSelect
|
||||
value={this.state.quickSelectUnit}
|
||||
options={lastOptions}
|
||||
onChange={arg => {
|
||||
this.onChange('quickSelectUnit', arg);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow>
|
||||
<EuiButton onClick={() => this.closePopover('quick-select')} style={{ minWidth: 0 }}>
|
||||
Apply
|
||||
</EuiButton>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private onChange = (stateType: string, args: any) => {
|
||||
let value = args.currentTarget.value;
|
||||
|
||||
if (stateType === 'quickSelectTime' && value !== '') {
|
||||
value = parseInt(args.currentTarget.value, 10);
|
||||
}
|
||||
this.setState({
|
||||
...this.state,
|
||||
[stateType]: value,
|
||||
});
|
||||
};
|
||||
|
||||
private renderCommonlyUsed = (recentlyCommonDates: string[]) => {
|
||||
const links = recentlyCommonDates.map(date => {
|
||||
return (
|
||||
<EuiFlexItem key={date}>
|
||||
<EuiLink onClick={() => this.closePopover(date)}>{date}</EuiLink>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>Commonly used</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<EuiFlexGrid gutterSize="s" columns={2}>
|
||||
{links}
|
||||
</EuiFlexGrid>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private renderRecentlyUsed = (recentDates: RecentlyUsed[]) => {
|
||||
const links = recentDates.map((date: RecentlyUsed) => {
|
||||
let dateRange;
|
||||
let dateLink = (
|
||||
<EuiLink onClick={() => this.closePopover(date.type)}>{dateRange || date.text}</EuiLink>
|
||||
);
|
||||
if (typeof date.text !== 'string') {
|
||||
dateRange = `${date.text[0]} – ${date.text[1]}`;
|
||||
dateLink = (
|
||||
<EuiLink onClick={() => this.closePopover(date.type, date.text[0], date.text[1])}>
|
||||
{dateRange || date.type}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false} key={`${dateRange || date.type}`}>
|
||||
{dateLink}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<span>Recently used date ranges</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<EuiFlexGroup gutterSize="s" style={{ flexDirection: 'column' }}>
|
||||
{links}
|
||||
</EuiFlexGroup>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const singularize = (str: string, qty: number) => (qty === 1 ? str.slice(0, -1) : str);
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { get, max, min } from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -36,6 +37,7 @@ interface Props {
|
|||
reload: () => void;
|
||||
onDrilldown: (filter: KueryFilterQuery) => void;
|
||||
timeRange: InfraTimerangeInput;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
interface MetricFormatter {
|
||||
|
@ -89,121 +91,150 @@ const calculateBoundsFromMap = (map: InfraWaffleData): InfraWaffleMapBounds => {
|
|||
return { min: min(values), max: max(values) };
|
||||
};
|
||||
|
||||
export class Waffle extends React.Component<Props, {}> {
|
||||
public render() {
|
||||
const { loading, map, reload, timeRange } = this.props;
|
||||
if (loading) {
|
||||
return <InfraLoadingPanel height="100%" width="100%" text="Loading data" />;
|
||||
} else if (!loading && map && map.length === 0) {
|
||||
export const Waffle = injectI18n(
|
||||
class extends React.Component<Props, {}> {
|
||||
public static displayName = 'Waffle';
|
||||
public render() {
|
||||
const { loading, map, reload, timeRange, intl } = this.props;
|
||||
if (loading) {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height="100%"
|
||||
width="100%"
|
||||
text={intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.loadingDataText',
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
} else if (!loading && map && map.length === 0) {
|
||||
return (
|
||||
<CenteredEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.noDataTitle"
|
||||
defaultMessage="There is no data to display."
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
titleSize="m"
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.noDataDescription"
|
||||
defaultMessage="Try adjusting your time or filter."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton
|
||||
iconType="refresh"
|
||||
color="primary"
|
||||
fill
|
||||
onClick={() => {
|
||||
reload();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.checkNewDataButtonLabel"
|
||||
defaultMessage="Check for new data"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
data-test-subj="noMetricsDataPrompt"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const { metric } = this.props.options;
|
||||
const metricFormatter = get(
|
||||
METRIC_FORMATTERS,
|
||||
metric.type,
|
||||
METRIC_FORMATTERS[InfraMetricType.count]
|
||||
);
|
||||
const bounds = (metricFormatter && metricFormatter.bounds) || calculateBoundsFromMap(map);
|
||||
return (
|
||||
<CenteredEmptyPrompt
|
||||
title={<h2>There is no data to display.</h2>}
|
||||
titleSize="m"
|
||||
body={<p>Try adjusting your time or filter.</p>}
|
||||
actions={
|
||||
<EuiButton
|
||||
iconType="refresh"
|
||||
color="primary"
|
||||
fill
|
||||
onClick={() => {
|
||||
reload();
|
||||
}}
|
||||
>
|
||||
Check for new data
|
||||
</EuiButton>
|
||||
}
|
||||
data-test-subj="noMetricsDataPrompt"
|
||||
/>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
const groupsWithLayout = applyWaffleMapLayout(map, width, height);
|
||||
return (
|
||||
<WaffleMapOuterContiner
|
||||
innerRef={(el: any) => measureRef(el)}
|
||||
data-test-subj="waffleMap"
|
||||
>
|
||||
<WaffleMapInnerContainer>
|
||||
{groupsWithLayout.map(this.renderGroup(bounds, timeRange))}
|
||||
</WaffleMapInnerContainer>
|
||||
<Legend
|
||||
formatter={this.formatter}
|
||||
bounds={bounds}
|
||||
legend={this.props.options.legend}
|
||||
/>
|
||||
</WaffleMapOuterContiner>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
);
|
||||
}
|
||||
const { metric } = this.props.options;
|
||||
const metricFormatter = get(
|
||||
METRIC_FORMATTERS,
|
||||
metric.type,
|
||||
METRIC_FORMATTERS[InfraMetricType.count]
|
||||
);
|
||||
const bounds = (metricFormatter && metricFormatter.bounds) || calculateBoundsFromMap(map);
|
||||
return (
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
const groupsWithLayout = applyWaffleMapLayout(map, width, height);
|
||||
return (
|
||||
<WaffleMapOuterContiner
|
||||
innerRef={(el: any) => measureRef(el)}
|
||||
data-test-subj="waffleMap"
|
||||
>
|
||||
<WaffleMapInnerContainer>
|
||||
{groupsWithLayout.map(this.renderGroup(bounds, timeRange))}
|
||||
</WaffleMapInnerContainer>
|
||||
<Legend
|
||||
formatter={this.formatter}
|
||||
bounds={bounds}
|
||||
legend={this.props.options.legend}
|
||||
/>
|
||||
</WaffleMapOuterContiner>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
);
|
||||
|
||||
// TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example.
|
||||
private formatter = (val: string | number) => {
|
||||
const { metric } = this.props.options;
|
||||
const metricFormatter = get(
|
||||
METRIC_FORMATTERS,
|
||||
metric.type,
|
||||
METRIC_FORMATTERS[InfraMetricType.count]
|
||||
);
|
||||
if (val == null) {
|
||||
return '';
|
||||
}
|
||||
const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template);
|
||||
return formatter(val);
|
||||
};
|
||||
|
||||
private handleDrilldown = (filter: string) => {
|
||||
this.props.onDrilldown({
|
||||
kind: 'kuery',
|
||||
expression: filter,
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
private renderGroup = (bounds: InfraWaffleMapBounds, timeRange: InfraTimerangeInput) => (
|
||||
group: InfraWaffleMapGroup
|
||||
) => {
|
||||
if (isWaffleMapGroupWithGroups(group)) {
|
||||
return (
|
||||
<GroupOfGroups
|
||||
onDrilldown={this.handleDrilldown}
|
||||
key={group.id}
|
||||
options={this.props.options}
|
||||
group={group}
|
||||
formatter={this.formatter}
|
||||
bounds={bounds}
|
||||
nodeType={this.props.nodeType}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isWaffleMapGroupWithNodes(group)) {
|
||||
return (
|
||||
<GroupOfNodes
|
||||
key={group.id}
|
||||
options={this.props.options}
|
||||
group={group}
|
||||
onDrilldown={this.handleDrilldown}
|
||||
formatter={this.formatter}
|
||||
isChild={false}
|
||||
bounds={bounds}
|
||||
nodeType={this.props.nodeType}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example.
|
||||
private formatter = (val: string | number) => {
|
||||
const { metric } = this.props.options;
|
||||
const metricFormatter = get(
|
||||
METRIC_FORMATTERS,
|
||||
metric.type,
|
||||
METRIC_FORMATTERS[InfraMetricType.count]
|
||||
);
|
||||
if (val == null) {
|
||||
return '';
|
||||
}
|
||||
const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template);
|
||||
return formatter(val);
|
||||
};
|
||||
|
||||
private handleDrilldown = (filter: string) => {
|
||||
this.props.onDrilldown({
|
||||
kind: 'kuery',
|
||||
expression: filter,
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
private renderGroup = (bounds: InfraWaffleMapBounds, timeRange: InfraTimerangeInput) => (
|
||||
group: InfraWaffleMapGroup
|
||||
) => {
|
||||
if (isWaffleMapGroupWithGroups(group)) {
|
||||
return (
|
||||
<GroupOfGroups
|
||||
onDrilldown={this.handleDrilldown}
|
||||
key={group.id}
|
||||
options={this.props.options}
|
||||
group={group}
|
||||
formatter={this.formatter}
|
||||
bounds={bounds}
|
||||
nodeType={this.props.nodeType}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isWaffleMapGroupWithNodes(group)) {
|
||||
return (
|
||||
<GroupOfNodes
|
||||
key={group.id}
|
||||
options={this.props.options}
|
||||
group={group}
|
||||
onDrilldown={this.handleDrilldown}
|
||||
formatter={this.formatter}
|
||||
isChild={false}
|
||||
bounds={bounds}
|
||||
nodeType={this.props.nodeType}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const WaffleMapOuterContiner = styled.div`
|
||||
flex: 1 0 0%;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiContextMenu, EuiContextMenuPanelDescriptor, EuiPopover } from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { InfraNodeType, InfraTimerangeInput } from '../../../common/graphql/types';
|
||||
|
@ -14,72 +15,74 @@ import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to';
|
|||
interface Props {
|
||||
options: InfraWaffleMapOptions;
|
||||
timeRange: InfraTimerangeInput;
|
||||
children: any;
|
||||
node: InfraWaffleMapNode;
|
||||
nodeType: InfraNodeType;
|
||||
isPopoverOpen: boolean;
|
||||
closePopover: () => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export const NodeContextMenu: React.SFC<Props> = ({
|
||||
options,
|
||||
timeRange,
|
||||
children,
|
||||
node,
|
||||
isPopoverOpen,
|
||||
closePopover,
|
||||
nodeType,
|
||||
}) => {
|
||||
const nodeName = node.path.length > 0 ? node.path[node.path.length - 1].value : undefined;
|
||||
const nodeLogsUrl = nodeName
|
||||
? getNodeLogsUrl({
|
||||
nodeType,
|
||||
nodeName,
|
||||
time: timeRange.to,
|
||||
})
|
||||
: undefined;
|
||||
const nodeDetailUrl = nodeName
|
||||
? getNodeDetailUrl({
|
||||
nodeType,
|
||||
nodeName,
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
})
|
||||
: undefined;
|
||||
export const NodeContextMenu = injectI18n(
|
||||
({ options, timeRange, children, node, isPopoverOpen, closePopover, nodeType, intl }: Props) => {
|
||||
const nodeName = node.path.length > 0 ? node.path[node.path.length - 1].value : undefined;
|
||||
const nodeLogsUrl = nodeName
|
||||
? getNodeLogsUrl({
|
||||
nodeType,
|
||||
nodeName,
|
||||
time: timeRange.to,
|
||||
})
|
||||
: undefined;
|
||||
const nodeDetailUrl = nodeName
|
||||
? getNodeDetailUrl({
|
||||
nodeType,
|
||||
nodeName,
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: '',
|
||||
items: [
|
||||
...(nodeLogsUrl
|
||||
? [
|
||||
{
|
||||
name: `View logs`,
|
||||
href: nodeLogsUrl,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(nodeDetailUrl
|
||||
? [
|
||||
{
|
||||
name: `View metrics`,
|
||||
href: nodeDetailUrl,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
];
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: '',
|
||||
items: [
|
||||
...(nodeLogsUrl
|
||||
? [
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
id: 'xpack.infra.nodeContextMenu.viewLogsName',
|
||||
defaultMessage: 'View logs',
|
||||
}),
|
||||
href: nodeLogsUrl,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(nodeDetailUrl
|
||||
? [
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
id: 'xpack.infra.nodeContextMenu.viewMetricsName',
|
||||
defaultMessage: 'View metrics',
|
||||
}),
|
||||
href: nodeDetailUrl,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
closePopover={closePopover}
|
||||
id={`${node.id}-popover`}
|
||||
isOpen={isPopoverOpen}
|
||||
button={children}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<EuiPopover
|
||||
closePopover={closePopover}
|
||||
id={`${node.id}-popover`}
|
||||
isOpen={isPopoverOpen}
|
||||
button={children}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
EuiFilterGroup,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { InfraNodeType, InfraPathInput, InfraPathType } from '../../../common/graphql/types';
|
||||
|
||||
|
@ -19,55 +20,161 @@ interface Props {
|
|||
nodeType: InfraNodeType;
|
||||
groupBy: InfraPathInput[];
|
||||
onChange: (groupBy: InfraPathInput[]) => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
const OPTIONS = {
|
||||
[InfraNodeType.pod]: [
|
||||
{ text: 'Namespace', type: InfraPathType.terms, field: 'kubernetes.namespace' },
|
||||
{ text: 'Node', type: InfraPathType.terms, field: 'kubernetes.node.name' },
|
||||
],
|
||||
[InfraNodeType.container]: [
|
||||
{ text: 'Host', type: InfraPathType.terms, field: 'host.name' },
|
||||
{ text: 'Availability Zone', type: InfraPathType.terms, field: 'meta.cloud.availability_zone' },
|
||||
{ text: 'Machine Type', type: InfraPathType.terms, field: 'meta.cloud.machine_type' },
|
||||
{ text: 'Project ID', type: InfraPathType.terms, field: 'meta.cloud.project_id' },
|
||||
{ text: 'Provider', type: InfraPathType.terms, field: 'meta.cloud.provider' },
|
||||
],
|
||||
[InfraNodeType.host]: [
|
||||
{ text: 'Availability Zone', type: InfraPathType.terms, field: 'meta.cloud.availability_zone' },
|
||||
{ text: 'Machine Type', type: InfraPathType.terms, field: 'meta.cloud.machine_type' },
|
||||
{ text: 'Project ID', type: InfraPathType.terms, field: 'meta.cloud.project_id' },
|
||||
{ text: 'Cloud Provider', type: InfraPathType.terms, field: 'meta.cloud.provider' },
|
||||
],
|
||||
let OPTIONS: { [P in InfraNodeType]: Array<{ text: string; type: InfraPathType; field: string }> };
|
||||
const getOptions = (
|
||||
nodeType: InfraNodeType,
|
||||
intl: InjectedIntl
|
||||
): Array<{ text: string; type: InfraPathType; field: string }> => {
|
||||
if (!OPTIONS) {
|
||||
OPTIONS = {
|
||||
[InfraNodeType.pod]: [
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.podGroupByOptions.namespaceLabel',
|
||||
defaultMessage: 'Namespace',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'kubernetes.namespace',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.podGroupByOptions.nodeLabel',
|
||||
defaultMessage: 'Node',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
],
|
||||
[InfraNodeType.container]: [
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.containerGroupByOptions.hostLabel',
|
||||
defaultMessage: 'Host',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'host.name',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.containerGroupByOptions.availabilityZoneLabel',
|
||||
defaultMessage: 'Availability Zone',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.availability_zone',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.containerGroupByOptions.machineTypeLabel',
|
||||
defaultMessage: 'Machine Type',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.machine_type',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.containerGroupByOptions.projectIDLabel',
|
||||
defaultMessage: 'Project ID',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.project_id',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.containerGroupByOptions.providerLabel',
|
||||
defaultMessage: 'Provider',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.provider',
|
||||
},
|
||||
],
|
||||
[InfraNodeType.host]: [
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.hostGroupByOptions.availabilityZoneLabel',
|
||||
defaultMessage: 'Availability Zone',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.availability_zone',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.hostGroupByOptions.machineTypeLabel',
|
||||
defaultMessage: 'Machine Type',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.machine_type',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.hostGroupByOptions.projectIDLabel',
|
||||
defaultMessage: 'Project ID',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.project_id',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.hostGroupByOptions.cloudProviderLabel',
|
||||
defaultMessage: 'Cloud Provider',
|
||||
}),
|
||||
type: InfraPathType.terms,
|
||||
field: 'meta.cloud.provider',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return OPTIONS[nodeType];
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
type State = Readonly<typeof initialState>;
|
||||
|
||||
export class WaffleGroupByControls extends React.PureComponent<Props, State> {
|
||||
public readonly state: State = initialState;
|
||||
public render() {
|
||||
const { nodeType, groupBy } = this.props;
|
||||
const options = OPTIONS[nodeType];
|
||||
if (!options.length) {
|
||||
throw Error(`Unable to select group by options for ${nodeType}`);
|
||||
}
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 'firstPanel',
|
||||
title: 'Select up to two groupings',
|
||||
items: options.map(o => {
|
||||
const icon = groupBy.some(g => g.field === o.field) ? 'check' : 'empty';
|
||||
const panel = { name: o.text, onClick: this.handleClick(o.field), icon };
|
||||
return panel;
|
||||
}),
|
||||
},
|
||||
];
|
||||
const buttonBody =
|
||||
groupBy.length > 0
|
||||
? groupBy
|
||||
export const WaffleGroupByControls = injectI18n(
|
||||
class extends React.PureComponent<Props, State> {
|
||||
public static displayName = 'WaffleGroupByControls';
|
||||
public readonly state: State = initialState;
|
||||
|
||||
public render() {
|
||||
const { nodeType, groupBy, intl } = this.props;
|
||||
const options = getOptions(nodeType, intl);
|
||||
|
||||
if (!options.length) {
|
||||
throw Error(
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.waffle.unableToSelectGroupErrorMessage',
|
||||
defaultMessage: 'Unable to select group by options for {nodeType}',
|
||||
},
|
||||
{
|
||||
nodeType,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 'firstPanel',
|
||||
title: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.selectTwoGroupingsTitle',
|
||||
defaultMessage: 'Select up to two groupings',
|
||||
}),
|
||||
items: options.map(o => {
|
||||
const icon = groupBy.some(g => g.field === o.field) ? 'check' : 'empty';
|
||||
const panel = { name: o.text, onClick: this.handleClick(o.field), icon };
|
||||
return panel;
|
||||
}),
|
||||
},
|
||||
];
|
||||
const buttonBody =
|
||||
groupBy.length > 0 ? (
|
||||
groupBy
|
||||
.map(g => options.find(o => o.field === g.field))
|
||||
.filter(o => o != null)
|
||||
// In this map the `o && o.field` is totally unnecessary but Typescript is
|
||||
|
@ -77,56 +184,71 @@ export class WaffleGroupByControls extends React.PureComponent<Props, State> {
|
|||
key={o && o.field}
|
||||
iconType="cross"
|
||||
iconOnClick={this.handleRemove((o && o.field) || '')}
|
||||
iconOnClickAriaLabel={`Remove ${o && o.text} grouping`}
|
||||
iconOnClickAriaLabel={intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.waffle.removeGroupingItemAriaLabel',
|
||||
defaultMessage: 'Remove {groupingItem} grouping',
|
||||
},
|
||||
{
|
||||
groupingItem: o && o.text,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{o && o.text}
|
||||
</EuiBadge>
|
||||
))
|
||||
: 'All';
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={this.handleToggle}>
|
||||
Group By: {buttonBody}
|
||||
</EuiFilterButton>
|
||||
);
|
||||
) : (
|
||||
<FormattedMessage id="xpack.infra.waffle.groupByAllTitle" defaultMessage="All" />
|
||||
);
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={this.handleToggle}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.groupByButtonLabel"
|
||||
defaultMessage="Group By: "
|
||||
/>
|
||||
{buttonBody}
|
||||
</EuiFilterButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="groupByPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<EuiContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
||||
}
|
||||
|
||||
private handleRemove = (field: string) => () => {
|
||||
const { groupBy } = this.props;
|
||||
this.props.onChange(groupBy.filter(g => g.field !== field));
|
||||
// We need to close the panel after we rmeove the pill icon otherwise
|
||||
// it will remain open because the click is still captured by the EuiFilterButton
|
||||
setTimeout(() => this.handleClose());
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
this.setState({ isPopoverOpen: false });
|
||||
};
|
||||
|
||||
private handleToggle = () => {
|
||||
this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen }));
|
||||
};
|
||||
|
||||
private handleClick = (field: string) => () => {
|
||||
const { groupBy } = this.props;
|
||||
if (groupBy.some(g => g.field === field)) {
|
||||
this.handleRemove(field)();
|
||||
} else if (this.props.groupBy.length < 2) {
|
||||
this.props.onChange([...groupBy, { type: InfraPathType.terms, field }]);
|
||||
this.handleClose();
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="groupByPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<EuiContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private handleRemove = (field: string) => () => {
|
||||
const { groupBy } = this.props;
|
||||
this.props.onChange(groupBy.filter(g => g.field !== field));
|
||||
// We need to close the panel after we rmeove the pill icon otherwise
|
||||
// it will remain open because the click is still captured by the EuiFilterButton
|
||||
setTimeout(() => this.handleClose());
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
this.setState({ isPopoverOpen: false });
|
||||
};
|
||||
|
||||
private handleToggle = () => {
|
||||
this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen }));
|
||||
};
|
||||
|
||||
private handleClick = (field: string) => () => {
|
||||
const { groupBy } = this.props;
|
||||
if (groupBy.some(g => g.field === field)) {
|
||||
this.handleRemove(field)();
|
||||
} else if (this.props.groupBy.length < 2) {
|
||||
this.props.onChange([...groupBy, { type: InfraPathType.terms, field }]);
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -11,35 +11,115 @@ import {
|
|||
EuiFilterGroup,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { InfraMetricInput, InfraMetricType, InfraNodeType } from '../../../common/graphql/types';
|
||||
interface Props {
|
||||
nodeType: InfraNodeType;
|
||||
metric: InfraMetricInput;
|
||||
onChange: (metric: InfraMetricInput) => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
const OPTIONS = {
|
||||
[InfraNodeType.pod]: [
|
||||
{ text: 'CPU Usage', value: InfraMetricType.cpu },
|
||||
{ text: 'Memory Usage', value: InfraMetricType.memory },
|
||||
{ text: 'Inbound Traffic', value: InfraMetricType.rx },
|
||||
{ text: 'Outbound Traffic', value: InfraMetricType.tx },
|
||||
],
|
||||
[InfraNodeType.container]: [
|
||||
{ text: 'CPU Usage', value: InfraMetricType.cpu },
|
||||
{ text: 'Memory Usage', value: InfraMetricType.memory },
|
||||
{ text: 'Inbound Traffic', value: InfraMetricType.rx },
|
||||
{ text: 'Outbound Traffic', value: InfraMetricType.tx },
|
||||
],
|
||||
[InfraNodeType.host]: [
|
||||
{ text: 'CPU Usage', value: InfraMetricType.cpu },
|
||||
{ text: 'Memory Usage', value: InfraMetricType.memory },
|
||||
{ text: 'Load', value: InfraMetricType.load },
|
||||
{ text: 'Inbound Traffic', value: InfraMetricType.rx },
|
||||
{ text: 'Outbound Traffic', value: InfraMetricType.tx },
|
||||
{ text: 'Log Rate', value: InfraMetricType.logRate },
|
||||
],
|
||||
let OPTIONS: { [P in InfraNodeType]: Array<{ text: string; value: InfraMetricType }> };
|
||||
const getOptions = (
|
||||
nodeType: InfraNodeType,
|
||||
intl: InjectedIntl
|
||||
): Array<{ text: string; value: InfraMetricType }> => {
|
||||
if (!OPTIONS) {
|
||||
const CPUUsage = intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.cpuUsageText',
|
||||
defaultMessage: 'CPU Usage',
|
||||
});
|
||||
|
||||
const MemoryUsage = intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.memoryUsageText',
|
||||
defaultMessage: 'Memory Usage',
|
||||
});
|
||||
|
||||
const InboundTraffic = intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.inboundTrafficText',
|
||||
defaultMessage: 'Inbound Traffic',
|
||||
});
|
||||
|
||||
const OutboundTraffic = intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.outboundTrafficText',
|
||||
defaultMessage: 'Outbound Traffic',
|
||||
});
|
||||
|
||||
OPTIONS = {
|
||||
[InfraNodeType.pod]: [
|
||||
{
|
||||
text: CPUUsage,
|
||||
value: InfraMetricType.cpu,
|
||||
},
|
||||
{
|
||||
text: MemoryUsage,
|
||||
value: InfraMetricType.memory,
|
||||
},
|
||||
{
|
||||
text: InboundTraffic,
|
||||
value: InfraMetricType.rx,
|
||||
},
|
||||
{
|
||||
text: OutboundTraffic,
|
||||
value: InfraMetricType.tx,
|
||||
},
|
||||
],
|
||||
[InfraNodeType.container]: [
|
||||
{
|
||||
text: CPUUsage,
|
||||
value: InfraMetricType.cpu,
|
||||
},
|
||||
{
|
||||
text: MemoryUsage,
|
||||
value: InfraMetricType.memory,
|
||||
},
|
||||
{
|
||||
text: InboundTraffic,
|
||||
value: InfraMetricType.rx,
|
||||
},
|
||||
{
|
||||
text: OutboundTraffic,
|
||||
value: InfraMetricType.tx,
|
||||
},
|
||||
],
|
||||
[InfraNodeType.host]: [
|
||||
{
|
||||
text: CPUUsage,
|
||||
value: InfraMetricType.cpu,
|
||||
},
|
||||
{
|
||||
text: MemoryUsage,
|
||||
value: InfraMetricType.memory,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.loadText',
|
||||
defaultMessage: 'Load',
|
||||
}),
|
||||
value: InfraMetricType.load,
|
||||
},
|
||||
{
|
||||
text: InboundTraffic,
|
||||
value: InfraMetricType.rx,
|
||||
},
|
||||
{
|
||||
text: OutboundTraffic,
|
||||
value: InfraMetricType.tx,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.metricOptions.hostLogRateText',
|
||||
defaultMessage: 'Log Rate',
|
||||
}),
|
||||
value: InfraMetricType.logRate,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return OPTIONS[nodeType];
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
|
@ -47,60 +127,73 @@ const initialState = {
|
|||
};
|
||||
type State = Readonly<typeof initialState>;
|
||||
|
||||
export class WaffleMetricControls extends React.PureComponent<Props, State> {
|
||||
public readonly state: State = initialState;
|
||||
public render() {
|
||||
const { metric } = this.props;
|
||||
const options = OPTIONS[this.props.nodeType];
|
||||
const value = metric.type;
|
||||
if (!options.length || !value) {
|
||||
throw Error('Unable to select options or value for metric.');
|
||||
}
|
||||
const currentLabel = options.find(o => o.value === metric.type);
|
||||
if (!currentLabel) {
|
||||
return 'null';
|
||||
}
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: '',
|
||||
items: options.map(o => {
|
||||
const icon = o.value === metric.type ? 'check' : 'empty';
|
||||
const panel = { name: o.text, onClick: this.handleClick(o.value), icon };
|
||||
return panel;
|
||||
}),
|
||||
},
|
||||
];
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={this.handleToggle}>
|
||||
Metric: {currentLabel.text}
|
||||
</EuiFilterButton>
|
||||
);
|
||||
export const WaffleMetricControls = injectI18n(
|
||||
class extends React.PureComponent<Props, State> {
|
||||
public static displayName = 'WaffleMetricControls';
|
||||
public readonly state: State = initialState;
|
||||
public render() {
|
||||
const { metric, nodeType, intl } = this.props;
|
||||
const options = getOptions(nodeType, intl);
|
||||
const value = metric.type;
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="metricsPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
||||
if (!options.length || !value) {
|
||||
throw Error(
|
||||
intl.formatMessage({
|
||||
id: 'xpack.infra.waffle.unableToSelectMetricErrorTitle',
|
||||
defaultMessage: 'Unable to select options or value for metric.',
|
||||
})
|
||||
);
|
||||
}
|
||||
const currentLabel = options.find(o => o.value === metric.type);
|
||||
if (!currentLabel) {
|
||||
return 'null';
|
||||
}
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: '',
|
||||
items: options.map(o => {
|
||||
const icon = o.value === metric.type ? 'check' : 'empty';
|
||||
const panel = { name: o.text, onClick: this.handleClick(o.value), icon };
|
||||
return panel;
|
||||
}),
|
||||
},
|
||||
];
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={this.handleToggle}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.metricButtonLabel"
|
||||
defaultMessage="Metric: {selectedMetric}"
|
||||
values={{ selectedMetric: currentLabel.text }}
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="metricsPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
);
|
||||
}
|
||||
private handleClose = () => {
|
||||
this.setState({ isPopoverOpen: false });
|
||||
};
|
||||
|
||||
private handleToggle = () => {
|
||||
this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen }));
|
||||
};
|
||||
|
||||
private handleClick = (value: InfraMetricType) => () => {
|
||||
this.props.onChange({ type: value });
|
||||
this.handleClose();
|
||||
};
|
||||
}
|
||||
private handleClose = () => {
|
||||
this.setState({ isPopoverOpen: false });
|
||||
};
|
||||
|
||||
private handleToggle = () => {
|
||||
this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen }));
|
||||
};
|
||||
|
||||
private handleClick = (value: InfraMetricType) => () => {
|
||||
this.props.onChange({ type: value });
|
||||
this.handleClose();
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiKeyPadMenu, EuiKeyPadMenuItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
InfraMetricInput,
|
||||
|
@ -24,7 +25,15 @@ export class WaffleNodeTypeSwitcher extends React.PureComponent<Props> {
|
|||
public render() {
|
||||
return (
|
||||
<EuiKeyPadMenu>
|
||||
<EuiKeyPadMenuItem label="Hosts" onClick={this.handleClick(InfraNodeType.host)}>
|
||||
<EuiKeyPadMenuItem
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.nodeTypeSwitcher.hostsLabel"
|
||||
defaultMessage="Hosts"
|
||||
/>
|
||||
}
|
||||
onClick={this.handleClick(InfraNodeType.host)}
|
||||
>
|
||||
<img src="../plugins/infra/images/hosts.svg" className="euiIcon euiIcon--large" />
|
||||
</EuiKeyPadMenuItem>
|
||||
<EuiKeyPadMenuItem label="Kubernetes" onClick={this.handleClick(InfraNodeType.pod)}>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -29,11 +30,17 @@ export class WaffleTimeControls extends React.Component<WaffleTimeControlsProps>
|
|||
iconType="pause"
|
||||
onClick={this.stopLiveStreaming}
|
||||
>
|
||||
Stop refreshing
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.stopRefreshingButtonLabel"
|
||||
defaultMessage="Stop refreshing"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty iconSide="left" iconType="play" onClick={this.startLiveStreaming}>
|
||||
Auto-refresh
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.autoRefreshButtonLabel"
|
||||
defaultMessage="Auto-refresh"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
@ -28,27 +29,39 @@ export const WithLogMinimap = asChildFunctionRenderer(withLogMinimap);
|
|||
|
||||
export const availableIntervalSizes = [
|
||||
{
|
||||
label: '1 Year',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneYearLabel', {
|
||||
defaultMessage: '1 Year',
|
||||
}),
|
||||
intervalSize: 1000 * 60 * 60 * 24 * 365,
|
||||
},
|
||||
{
|
||||
label: '1 Month',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneMonthLabel', {
|
||||
defaultMessage: '1 Month',
|
||||
}),
|
||||
intervalSize: 1000 * 60 * 60 * 24 * 30,
|
||||
},
|
||||
{
|
||||
label: '1 Week',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneWeekLabel', {
|
||||
defaultMessage: '1 Week',
|
||||
}),
|
||||
intervalSize: 1000 * 60 * 60 * 24 * 7,
|
||||
},
|
||||
{
|
||||
label: '1 Day',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneDayLabel', {
|
||||
defaultMessage: '1 Day',
|
||||
}),
|
||||
intervalSize: 1000 * 60 * 60 * 24,
|
||||
},
|
||||
{
|
||||
label: '1 Hour',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneHourLabel', {
|
||||
defaultMessage: '1 Hour',
|
||||
}),
|
||||
intervalSize: 1000 * 60 * 60,
|
||||
},
|
||||
{
|
||||
label: '1 Minute',
|
||||
label: i18n.translate('xpack.infra.mapLogs.oneMinuteLabel', {
|
||||
defaultMessage: '1 Minute',
|
||||
}),
|
||||
intervalSize: 1000 * 60,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { first, last } from 'lodash';
|
||||
import { InfraNode, InfraNodePath } from '../../../common/graphql/types';
|
||||
import {
|
||||
|
@ -44,7 +45,12 @@ function findOrCreateGroupWithNodes(
|
|||
}
|
||||
return {
|
||||
id,
|
||||
name: id === '__all__' ? 'All' : last(path).value,
|
||||
name:
|
||||
id === '__all__'
|
||||
? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithNodes.allName', {
|
||||
defaultMessage: 'All',
|
||||
})
|
||||
: last(path).value,
|
||||
count: 0,
|
||||
width: 0,
|
||||
squareSize: 0,
|
||||
|
@ -63,7 +69,12 @@ function findOrCreateGroupWithGroups(
|
|||
}
|
||||
return {
|
||||
id,
|
||||
name: id === '__all__' ? 'All' : last(path).value,
|
||||
name:
|
||||
id === '__all__'
|
||||
? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithGroups.allName', {
|
||||
defaultMessage: 'All',
|
||||
})
|
||||
: last(path).value,
|
||||
count: 0,
|
||||
width: 0,
|
||||
squareSize: 0,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { I18nServiceType } from '@kbn/i18n/angular';
|
||||
import {
|
||||
FeatureCatalogueCategory,
|
||||
FeatureCatalogueRegistryProvider,
|
||||
|
@ -11,22 +12,30 @@ import {
|
|||
|
||||
const APP_ID = 'infra';
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => ({
|
||||
FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({
|
||||
id: 'infraops',
|
||||
title: 'Infrastructure',
|
||||
description:
|
||||
'Explore infrastructure metrics and logs for common servers, containers, and services.',
|
||||
title: i18n('xpack.infra.registerFeatures.infraOpsTitle', {
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
description: i18n('xpack.infra.registerFeatures.infraOpsDescription', {
|
||||
defaultMessage:
|
||||
'Explore infrastructure metrics and logs for common servers, containers, and services.',
|
||||
}),
|
||||
icon: 'infraApp',
|
||||
path: `/app/${APP_ID}#home`,
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.DATA,
|
||||
}));
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => ({
|
||||
FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({
|
||||
id: 'infralogging',
|
||||
title: 'Logs',
|
||||
description:
|
||||
'Stream logs in real time or scroll through historical views in a console-like experience.',
|
||||
title: i18n('xpack.infra.registerFeatures.logsTitle', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
description: i18n('xpack.infra.registerFeatures.logsDescription', {
|
||||
defaultMessage:
|
||||
'Stream logs in real time or scroll through historical views in a console-like experience.',
|
||||
}),
|
||||
icon: 'loggingApp',
|
||||
path: `/app/${APP_ID}#logs`,
|
||||
showOnHomePage: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue