mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Monitoring] Ensure all charts use the configured timezone (#45949)
* Consistently apply dateFormat:tz to all monitoring time-series data * Ensure browser timezone works properly * Fix tests * Fix other test * Simplfy timezone fetching * Fix tests
This commit is contained in:
parent
d935b3da08
commit
3f7c3e0d55
12 changed files with 83 additions and 30 deletions
|
@ -17,8 +17,10 @@ export const LARGE_ABBREVIATED = '0,0.[0]a';
|
|||
* @param date Either a numeric Unix timestamp or a {@code Date} object
|
||||
* @returns The date formatted using 'LL LTS'
|
||||
*/
|
||||
export function formatDateTimeLocal(date) {
|
||||
return moment.tz(date, moment.tz.guess()).format('LL LTS');
|
||||
export function formatDateTimeLocal(date, useUTC = false) {
|
||||
return useUTC
|
||||
? moment.utc(date).format('LL LTS')
|
||||
: moment.tz(date, moment.tz.guess()).format('LL LTS');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -76,8 +76,8 @@ export class ChartTarget extends React.Component {
|
|||
.value();
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
const opts = getChartOptions({
|
||||
async getOptions() {
|
||||
const opts = await getChartOptions({
|
||||
yaxis: { tickFormatter: this.props.tickFormatter },
|
||||
xaxis: this.props.timeRange
|
||||
});
|
||||
|
@ -88,12 +88,12 @@ export class ChartTarget extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
renderChart() {
|
||||
async renderChart() {
|
||||
const { target } = this.refs;
|
||||
const { series } = this.props;
|
||||
const data = this.filterData(series, this.props.seriesToShow);
|
||||
|
||||
this.plot = $.plot(target, data, this.getOptions());
|
||||
this.plot = $.plot(target, data, await this.getOptions());
|
||||
|
||||
this._handleResize = () => {
|
||||
if (!this.plot) { return; }
|
||||
|
|
|
@ -4,17 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { merge } from 'lodash';
|
||||
import { CHART_LINE_COLOR, CHART_TEXT_COLOR } from '../../../common/constants';
|
||||
|
||||
export function getChartOptions(axisOptions) {
|
||||
export async function getChartOptions(axisOptions) {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const timezone = $injector.get('config').get('dateFormat:tz');
|
||||
const opts = {
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
xaxis: {
|
||||
color: CHART_LINE_COLOR,
|
||||
timezone: 'browser',
|
||||
timezone: timezone === 'Browser' ? 'browser' : 'utc',
|
||||
mode: 'time', // requires `time` flot plugin
|
||||
font: {
|
||||
color: CHART_TEXT_COLOR
|
||||
|
|
|
@ -50,7 +50,7 @@ const columns = [
|
|||
field: 'timestamp',
|
||||
name: columnTimestampTitle,
|
||||
width: '12%',
|
||||
render: timestamp => formatDateTimeLocal(timestamp),
|
||||
render: timestamp => formatDateTimeLocal(timestamp, true),
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
|
@ -80,7 +80,7 @@ const clusterColumns = [
|
|||
field: 'timestamp',
|
||||
name: columnTimestampTitle,
|
||||
width: '12%',
|
||||
render: timestamp => formatDateTimeLocal(timestamp),
|
||||
render: timestamp => formatDateTimeLocal(timestamp, true),
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
|
|
|
@ -51,7 +51,10 @@ function getMockReq(metricsBuckets = []) {
|
|||
},
|
||||
params: {
|
||||
clusterUuid: '1234xyz'
|
||||
}
|
||||
},
|
||||
getUiSettingsService: () => ({
|
||||
get: () => 'Browser'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,9 @@ import Promise from 'bluebird';
|
|||
import { checkParam } from '../error_missing_required';
|
||||
import { getSeries } from './get_series';
|
||||
import { calculateTimeseriesInterval } from '../calculate_timeseries_interval';
|
||||
import { getTimezone } from '../get_timezone';
|
||||
|
||||
export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
|
||||
export async function getMetrics(req, indexPattern, metricSet = [], filters = []) {
|
||||
checkParam(indexPattern, 'indexPattern in details/getMetrics');
|
||||
checkParam(metricSet, 'metricSet in details/getMetrics');
|
||||
|
||||
|
@ -21,6 +22,7 @@ export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
|
|||
const max = moment.utc(req.payload.timeRange.max).valueOf();
|
||||
const minIntervalSeconds = config.get('xpack.monitoring.min_interval_seconds');
|
||||
const bucketSize = calculateTimeseriesInterval(min, max, minIntervalSeconds);
|
||||
const timezone = await getTimezone(req);
|
||||
|
||||
return Promise.map(metricSet, metric => {
|
||||
// metric names match the literal metric name, but they can be supplied in groups or individually
|
||||
|
@ -33,7 +35,7 @@ export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
|
|||
}
|
||||
|
||||
return Promise.map(metricNames, metricName => {
|
||||
return getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize });
|
||||
return getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize, timezone });
|
||||
});
|
||||
})
|
||||
.then(rows => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
NORMALIZED_DERIVATIVE_UNIT,
|
||||
CALCULATE_DURATION_UNTIL
|
||||
} from '../../../common/constants';
|
||||
import { formatUTCTimestampForTimezone } from '../format_timezone';
|
||||
|
||||
/**
|
||||
* Derivative metrics for the first two agg buckets are unusable. For the first bucket, there
|
||||
|
@ -177,7 +178,7 @@ const formatBucketSize = bucketSizeInSeconds => {
|
|||
return formatTimestampToDuration(timestamp, CALCULATE_DURATION_UNTIL, now);
|
||||
};
|
||||
|
||||
function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
|
||||
function handleSeries(metric, min, max, bucketSizeInSeconds, timezone, response) {
|
||||
const { derivative, calculation: customCalculation } = metric;
|
||||
const buckets = get(response, 'aggregations.check.buckets', []);
|
||||
const firstUsableBucketIndex = findFirstUsableBucketIndex(buckets, min);
|
||||
|
@ -193,14 +194,17 @@ function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
|
|||
data = buckets
|
||||
.slice(firstUsableBucketIndex, lastUsableBucketIndex + 1) // take only the buckets we know are usable
|
||||
.map(bucket => ([
|
||||
bucket.key,
|
||||
formatUTCTimestampForTimezone(bucket.key, timezone),
|
||||
calculation(bucket, key, metric, bucketSizeInSeconds)
|
||||
])); // map buckets to X/Y coords for Flot charting
|
||||
}
|
||||
|
||||
return {
|
||||
bucket_size: formatBucketSize(bucketSizeInSeconds),
|
||||
timeRange: { min, max },
|
||||
timeRange: {
|
||||
min: formatUTCTimestampForTimezone(min, timezone),
|
||||
max: formatUTCTimestampForTimezone(max, timezone),
|
||||
},
|
||||
metric: metric.serialize(),
|
||||
data
|
||||
};
|
||||
|
@ -217,7 +221,7 @@ function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
|
|||
* @param {Array} filters Any filters that should be applied to the query.
|
||||
* @return {Promise} The object response containing the {@code timeRange}, {@code metric}, and {@code data}.
|
||||
*/
|
||||
export async function getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize }) {
|
||||
export async function getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize, timezone }) {
|
||||
checkParam(indexPattern, 'indexPattern in details/getSeries');
|
||||
|
||||
const metric = metrics[metricName];
|
||||
|
@ -226,5 +230,5 @@ export async function getSeries(req, indexPattern, metricName, filters, { min, m
|
|||
}
|
||||
const response = await fetchSeries(req, indexPattern, metric, min, max, bucketSize, filters);
|
||||
|
||||
return handleSeries(metric, min, max, bucketSize, response);
|
||||
return handleSeries(metric, min, max, bucketSize, timezone, response);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
|
||||
|
||||
/**
|
||||
* This function is designed to offset a UTC timestamp based on the provided timezone
|
||||
* For example, EST is UTC-4h so this function will subtract (4 * 60 * 60 * 1000)ms
|
||||
* from the UTC timestamp. This allows us to allow users to view monitoring data
|
||||
* in various timezones without needing to not store UTC dates.
|
||||
*
|
||||
* @param {*} utcTimestamp UTC timestamp
|
||||
* @param {*} timezone The timezone to convert into
|
||||
*/
|
||||
export const formatUTCTimestampForTimezone = (utcTimestamp, timezone) => {
|
||||
if (timezone === 'Browser') {
|
||||
return utcTimestamp;
|
||||
}
|
||||
const offsetInMinutes = moment.tz(timezone).utcOffset();
|
||||
const offsetTimestamp = utcTimestamp + (offsetInMinutes * 1 * 60 * 1000);
|
||||
return offsetTimestamp;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export async function getTimezone(req) {
|
||||
return await req.getUiSettingsService().get('dateFormat:tz');
|
||||
}
|
|
@ -4,10 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { get } from 'lodash';
|
||||
import { checkParam } from '../error_missing_required';
|
||||
import { createTimeFilter } from '../create_query';
|
||||
import { detectReason } from './detect_reason';
|
||||
import { formatUTCTimestampForTimezone } from '../format_timezone';
|
||||
import { getTimezone } from '../get_timezone';
|
||||
|
||||
async function handleResponse(response, req, filebeatIndexPattern, opts) {
|
||||
const result = {
|
||||
|
@ -15,15 +18,17 @@ async function handleResponse(response, req, filebeatIndexPattern, opts) {
|
|||
logs: []
|
||||
};
|
||||
|
||||
const timezone = await getTimezone(req);
|
||||
const hits = get(response, 'hits.hits', []);
|
||||
if (hits.length) {
|
||||
result.enabled = true;
|
||||
result.logs = hits.map(hit => {
|
||||
const source = hit._source;
|
||||
const type = get(source, 'event.dataset').split('.')[1];
|
||||
const utcTimestamp = moment(get(source, '@timestamp')).valueOf();
|
||||
|
||||
return {
|
||||
timestamp: get(source, '@timestamp'),
|
||||
timestamp: formatUTCTimestampForTimezone(utcTimestamp, timezone),
|
||||
component: get(source, 'elasticsearch.component'),
|
||||
node: get(source, 'elasticsearch.node.name'),
|
||||
index: get(source, 'elasticsearch.index.name'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"enabled": true,
|
||||
"logs": [{
|
||||
"timestamp": "2019-03-15T17:07:21.089Z",
|
||||
"timestamp": 1552669641089,
|
||||
"component": "o.e.n.Node",
|
||||
"node": "Elastic-MBP.local",
|
||||
"index": ".monitoring-es",
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
{
|
||||
"enabled": true,
|
||||
"logs": [{
|
||||
"timestamp": "2019-03-15T17:19:07.365Z",
|
||||
"timestamp": 1552670347365,
|
||||
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "WARN",
|
||||
"type": "deprecation",
|
||||
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:57.366Z",
|
||||
"timestamp": 1552670337366,
|
||||
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "WARN",
|
||||
"type": "deprecation",
|
||||
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:47.400Z",
|
||||
"timestamp": 1552670327400,
|
||||
"component": "o.e.c.m.MetaDataCreateIndexService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"index": ".monitoring-beats-7-2019.03.15",
|
||||
|
@ -23,14 +23,14 @@
|
|||
"type": "server",
|
||||
"message": "creating index, cause [auto(bulk api)], templates [.monitoring-beats], shards [1]/[0], mappings [_doc]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:47.387Z",
|
||||
"timestamp": 1552670327387,
|
||||
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "WARN",
|
||||
"type": "deprecation",
|
||||
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:42.084Z",
|
||||
"timestamp": 1552670322084,
|
||||
"component": "o.e.c.m.MetaDataMappingService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"index": "filebeat-8.0.0-2019.03.15-000001",
|
||||
|
@ -38,7 +38,7 @@
|
|||
"type": "server",
|
||||
"message": "update_mapping [_doc]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:41.811Z",
|
||||
"timestamp": 1552670321811,
|
||||
"component": "o.e.c.m.MetaDataMappingService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"index": "filebeat-8.0.0-2019.03.15-000001",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"type": "server",
|
||||
"message": "update_mapping [_doc]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:41.447Z",
|
||||
"timestamp": 1552670321447,
|
||||
"component": "o.e.c.m.MetaDataCreateIndexService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"index": "filebeat-8.0.0-2019.03.15-000001",
|
||||
|
@ -54,21 +54,21 @@
|
|||
"type": "server",
|
||||
"message": "creating index, cause [api], templates [filebeat-8.0.0], shards [1]/[1], mappings [_doc]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:41.385Z",
|
||||
"timestamp": 1552670321385,
|
||||
"component": "o.e.c.m.MetaDataIndexTemplateService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "INFO",
|
||||
"type": "server",
|
||||
"message": "adding template [filebeat-8.0.0] for index patterns [filebeat-8.0.0-*]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:41.185Z",
|
||||
"timestamp": 1552670321185,
|
||||
"component": "o.e.x.i.a.TransportPutLifecycleAction",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "INFO",
|
||||
"type": "server",
|
||||
"message": "adding index lifecycle policy [filebeat-8.0.0]"
|
||||
}, {
|
||||
"timestamp": "2019-03-15T17:18:36.137Z",
|
||||
"timestamp": 1552670316137,
|
||||
"component": "o.e.c.r.a.AllocationService",
|
||||
"node": "Elastic-MBP.local",
|
||||
"level": "INFO",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue