Skip to content

Commit

Permalink
[APM] x-axis labels on Error occurrences chart are incorrect based on…
Browse files Browse the repository at this point in the history
… Kibana timezone (#55686) (#56289)

* adjusting x-axis to use kibana timezone

* adjusting x-axis to use kibana timezone

* refactoring
  • Loading branch information
cauemarcondes committed Jan 29, 2020
1 parent 8481b8f commit ef5b680
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 20 deletions.
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) {
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];
};

0 comments on commit ef5b680

Please sign in to comment.