Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] x-axis labels on Error occurrences chart are incorrect based on Kibana timezone #55686

Merged
merged 3 commits into from
Jan 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@

import { EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { scaleUtc } from 'd3-scale';
import d3 from 'd3';
import React from 'react';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';
import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
// @ts-ignore
import Histogram from '../../../shared/charts/Histogram';
import { EmptyMessage } from '../../../shared/EmptyMessage';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';

interface IBucket {
key: number;
Expand Down Expand Up @@ -61,7 +64,7 @@ export function ErrorDistribution({ distribution, title }: Props) {
distribution.bucketSize
);

if (distribution.noHits) {
if (!buckets || distribution.noHits) {
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
return (
<EmptyMessage
heading={i18n.translate('xpack.apm.errorGroupDetails.noErrorsLabel', {
Expand All @@ -71,6 +74,12 @@ export function ErrorDistribution({ distribution, title }: Props) {
);
}

const xMin = d3.min(buckets, d => d.x0);
const xMax = d3.max(buckets, d => d.x);
const tickFormat = scaleUtc()
.domain([xMin, xMax])
.tickFormat();

return (
<div>
<EuiTitle size="xs">
Expand All @@ -79,7 +88,11 @@ export function ErrorDistribution({ distribution, title }: Props) {
<Histogram
tooltipHeader={tooltipHeader}
verticalLineHover={(bucket: FormattedBucket) => bucket.x}
xType="time"
xType="time-utc"
formatX={(value: Date) => {
const time = value.getTime();
return tickFormat(new Date(time - getTimezoneOffsetInMs(time)));
}}
buckets={buckets}
bucketSize={distribution.bucketSize}
formatYShort={(value: number) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import React from 'react';

import { TimeSeries, Coordinate } from '../../../../../typings/timeseries';
import { unit } from '../../../../style/variables';
import { getTimezoneOffsetInMs } from './getTimezoneOffsetInMs';
import { getDomainTZ, getTimeTicksTZ } from '../helper/timezone';

const XY_HEIGHT = unit * 16;
const XY_MARGIN = {
Expand Down Expand Up @@ -73,7 +73,6 @@ export function getPlotValues(
);

const xMin = d3.min(flattenedCoordinates, d => d.x);

const xMax = d3.max(flattenedCoordinates, d => d.x);

if (yMax === 'max') {
Expand All @@ -83,9 +82,7 @@ export function getPlotValues(
yMin = d3.min(flattenedCoordinates, d => d.y ?? 0);
}

const [xMinZone, xMaxZone] = [xMin, xMax].map(x => {
return x - getTimezoneOffsetInMs(x);
});
const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax);

const xScale = getXScale(xMin, xMax, width);
const yScale = getYScale(yMin, yMax);
Expand All @@ -97,15 +94,11 @@ export function getPlotValues(
// d3 will determine the exact number of ticks based on the selected range
const xTickTotal = Math.floor(width / 100);

const xTickValues = d3.time.scale
.utc()
.domain([xMinZone, xMaxZone])
.range([0, width])
.ticks(xTickTotal)
.map(x => {
const time = x.getTime();
return new Date(time + getTimezoneOffsetInMs(time));
});
const xTickValues = getTimeTicksTZ({
domain: [xMinZone, xMaxZone],
totalTicks: xTickTotal,
width
});

return {
x: xScale,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { unit } from '../../../../style/variables';
import Tooltip from '../Tooltip';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { tint } from 'polished';
import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone';

const XY_HEIGHT = unit * 10;
const XY_MARGIN = {
Expand Down Expand Up @@ -104,6 +105,9 @@ export class HistogramInner extends PureComponent {
return null;
}

const isTimeSeries =
this.props.xType === 'time' || this.props.xType === 'time-utc';

const xMin = d3.min(buckets, d => d.x0);
const xMax = d3.max(buckets, d => d.x);
const yMin = 0;
Expand All @@ -120,11 +124,18 @@ export class HistogramInner extends PureComponent {
.range([XY_HEIGHT, 0])
.nice();

const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax);
const xTickValues = isTimeSeries
? getTimeTicksTZ({
domain: [xMinZone, xMaxZone],
totalTicks: X_TICK_TOTAL,
width: XY_WIDTH
})
: undefined;

const xDomain = x.domain();
const yDomain = y.domain();
const yTickValues = [0, yDomain[1] / 2, yDomain[1]];
const isTimeSeries =
this.props.xType === 'time' || this.props.xType === 'time-utc';
const shouldShowTooltip =
hoveredBucket.x > 0 && (hoveredBucket.y > 0 || isTimeSeries);

Expand All @@ -150,6 +161,7 @@ export class HistogramInner extends PureComponent {
tickSizeInner={0}
tickTotal={X_TICK_TOTAL}
tickFormat={formatX}
tickValues={xTickValues}
/>
<YAxis
tickSize={0}
Expand Down Expand Up @@ -213,7 +225,7 @@ export class HistogramInner extends PureComponent {
[XY_MARGIN.left, XY_MARGIN.top],
[XY_WIDTH, XY_HEIGHT]
]}
nodes={this.props.buckets.map(bucket => {
nodes={buckets.map(bucket => {
return {
...bucket,
xCenter: (bucket.x0 + bucket.x) / 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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-timezone';
import { getDomainTZ, getTimeTicksTZ } from '../timezone';

describe('Timezone helper', () => {
let originalTimezone: moment.MomentZone | null;
const min = new Date('Tue Jan 28 2020 05:36:00 GMT+0100').valueOf();
const max = new Date('Wed Jan 29 2020 07:12:00 GMT+0100').valueOf();

afterAll(() => {
moment.tz.setDefault(originalTimezone ? originalTimezone.name : '');
});
describe('getTimeTicksTZ', () => {
it('returns ticks when in Ameca/New_York timezone', () => {
moment.tz.setDefault('America/New_York');
expect(
getTimeTicksTZ({ domain: [min, max], totalTicks: 8, width: 1138 })
).toEqual([
new Date('2020-01-28T11:00:00.000Z'),
new Date('2020-01-28T14:00:00.000Z'),
new Date('2020-01-28T17:00:00.000Z'),
new Date('2020-01-28T20:00:00.000Z'),
new Date('2020-01-28T23:00:00.000Z'),
new Date('2020-01-29T02:00:00.000Z'),
new Date('2020-01-29T05:00:00.000Z'),
new Date('2020-01-29T08:00:00.000Z'),
new Date('2020-01-29T11:00:00.000Z')
]);
});
it('returns ticks when in Europe/Amsterdam timezone', () => {
moment.tz.setDefault('Europe/Amsterdam');
expect(
getTimeTicksTZ({ domain: [min, max], totalTicks: 8, width: 1138 })
).toEqual([
new Date('2020-01-28T05:00:00.000Z'),
new Date('2020-01-28T08:00:00.000Z'),
new Date('2020-01-28T11:00:00.000Z'),
new Date('2020-01-28T14:00:00.000Z'),
new Date('2020-01-28T17:00:00.000Z'),
new Date('2020-01-28T20:00:00.000Z'),
new Date('2020-01-28T23:00:00.000Z'),
new Date('2020-01-29T02:00:00.000Z'),
new Date('2020-01-29T05:00:00.000Z')
]);
});
});

describe('getDomainTZ', () => {
it('returns domain when in Ameca/New_York timezone', () => {
moment.tz.setDefault('America/New_York');
expect(getDomainTZ(min, max)).toEqual([
new Date('Tue Jan 28 2020 00:36:00 GMT+0100').valueOf(),
new Date('Wed Jan 29 2020 02:12:00 GMT+0100').valueOf()
]);
});
it('returns domain when in Europe/Amsterdam timezone', () => {
moment.tz.setDefault('Europe/Amsterdam');
expect(getDomainTZ(min, max)).toEqual([
new Date('Tue Jan 28 2020 06:36:00 GMT+0100').valueOf(),
new Date('Wed Jan 29 2020 08:12:00 GMT+0100').valueOf()
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 d3 from 'd3';
import { getTimezoneOffsetInMs } from '../CustomPlot/getTimezoneOffsetInMs';

interface Params {
domain: [number, number];
totalTicks: number;
width: number;
}

export const getTimeTicksTZ = ({ domain, totalTicks, width }: Params) =>
d3.time.scale
.utc()
.domain(domain)
.range([0, width])
.ticks(totalTicks)
.map(x => {
const time = x.getTime();
return new Date(time + getTimezoneOffsetInMs(time));
});

export const getDomainTZ = (min: number, max: number): [number, number] => {
const [xMinZone, xMaxZone] = [min, max].map(
time => time - getTimezoneOffsetInMs(time)
);
return [xMinZone, xMaxZone];
};