Correctly apply timezone to formatted dates and ticks (#33831) (#35523)

This commit is contained in:
Joe Reuter 2019-04-24 11:28:42 +02:00 committed by GitHub
parent bf4d1233d4
commit 87295f50c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 53 deletions

View file

@ -53,6 +53,7 @@ export function createDateFormat(FieldFormat) {
}
const date = moment(val);
if (date.isValid()) {
return date.format(pattern);
} else {

View file

@ -96,11 +96,21 @@ export function VislibLibAxisProvider(Private) {
const position = this.axisConfig.get('position');
const axisFormatter = this.axisConfig.get('labels.axisFormatter');
return d3.svg.axis()
const d3Axis = d3.svg
.axis()
.scale(scale)
.tickFormat(axisFormatter)
.ticks(this.tickScale(length))
.orient(position);
if (this.axisConfig.isTimeDomain()) {
// use custom overwritten tick function on time domains to get nice
// tick positions (e.g. at the start of the day) even for custom timezones
d3Axis.tickValues(scale.timezoneCorrectedTicks(this.tickScale(length)));
} else {
d3Axis.ticks(this.tickScale(length));
}
return d3Axis;
}
getScale() {

View file

@ -179,7 +179,9 @@ export function VislibAxisScaleProvider() {
let scaleType = scaleTypeArg || 'linear';
if (scaleType === 'square root') scaleType = 'sqrt';
if (this.axisConfig.isTimeDomain()) return d3.time.scale.utc(); // allow time scale
if (this.axisConfig.isTimeDomain()) {
return d3.time.scale.utc(); // allow time scale
}
if (this.axisConfig.isOrdinal()) return d3.scale.ordinal();
if (typeof d3.scale[scaleType] !== 'function') {
return this.throwCustomError(`Axis.getScaleType: ${scaleType} is not a function`);
@ -201,6 +203,7 @@ export function VislibAxisScaleProvider() {
const padding = config.get('style.rangePadding');
const outerPadding = config.get('style.rangeOuterPadding');
this.scale = scale.domain(domain);
if (config.isOrdinal()) {
this.scale.rangeBands(range, padding, outerPadding);
} else {
@ -213,6 +216,19 @@ export function VislibAxisScaleProvider() {
this.validateScale(this.scale);
if (this.axisConfig.isTimeDomain()) {
// on a time domain shift it to have the buckets start at nice points in time (e.g. at the start of the day) in UTC
// then shift the calculated tick positions back into the real domain to have a nice tick position in the actual
// time zone. This is necessary because the d3 time scale doesn't provide a function to get nice time positions in
// a configurable time zone directly.
const offset = moment(domain[0]).utcOffset();
const shiftedDomain = domain.map(val => moment(val).add(offset, 'minute'));
this.tickScale = scale.copy().domain(shiftedDomain);
this.scale.timezoneCorrectedTicks = (n) => this.tickScale.ticks(n).map((d) => {
return moment(d).subtract(offset, 'minute').valueOf();
});
}
return this.scale;
}

View file

@ -440,21 +440,23 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
const maxTicks = [
'2015-09-19 17:00',
'2015-09-20 05:00',
'2015-09-20 17:00',
'2015-09-21 05:00',
'2015-09-21 17:00',
'2015-09-22 05:00',
'2015-09-22 17:00',
'2015-09-23 05:00'
'2015-09-20 00:00',
'2015-09-20 12:00',
'2015-09-21 00:00',
'2015-09-21 12:00',
'2015-09-22 00:00',
'2015-09-22 12:00',
'2015-09-23 00:00',
'2015-09-23 12:00'
];
for (const tick of await PageObjects.discover.getBarChartXTicks()) {
if (!maxTicks.includes(tick)) {
throw new Error(`unexpected x-axis tick "${tick}"`);
await retry.try(async function () {
for (const tick of await PageObjects.discover.getBarChartXTicks()) {
if (!maxTicks.includes(tick)) {
throw new Error(`unexpected x-axis tick "${tick}"`);
}
}
}
});
});
});
});

View file

@ -22,48 +22,52 @@ import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const kibanaServer = getService('kibanaServer');
const browser = getService('browser');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'pointSeries', 'timePicker']);
const pointSeriesVis = PageObjects.pointSeries;
describe('point series', function describeIndexTests() {
before(async function () {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
async function initChart() {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
log.debug('navigateToApp visualize');
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickLineChart');
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
log.debug('Bucket = X-Axis');
await PageObjects.visualize.clickBucket('X-Axis');
log.debug('Aggregation = Date Histogram');
await PageObjects.visualize.selectAggregation('Date Histogram');
log.debug('Field = @timestamp');
await PageObjects.visualize.selectField('@timestamp');
// add another metrics
log.debug('Add Metric');
await PageObjects.visualize.clickAddMetric();
log.debug('Metric = Value Axis');
await PageObjects.visualize.clickBucket('Y-Axis', 'metric');
log.debug('Aggregation = Average');
await PageObjects.visualize.selectAggregation('Average', 'metrics');
log.debug('Field = memory');
await PageObjects.visualize.selectField('machine.ram', 'metrics');
// go to options page
log.debug('Going to axis options');
await pointSeriesVis.clickAxisOptions();
// add another value axis
log.debug('adding axis');
await pointSeriesVis.clickAddAxis();
// set average count to use second value axis
await pointSeriesVis.toggleCollapsibleTitle('Average machine.ram');
log.debug('Average memory value axis - ValueAxis-2');
await pointSeriesVis.setSeriesAxis(1, 'ValueAxis-2');
await PageObjects.visualize.waitForVisualizationRenderingStabilized();
await PageObjects.visualize.clickGo();
});
log.debug('navigateToApp visualize');
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickLineChart');
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
log.debug('Bucket = X-Axis');
await PageObjects.visualize.clickBucket('X-Axis');
log.debug('Aggregation = Date Histogram');
await PageObjects.visualize.selectAggregation('Date Histogram');
log.debug('Field = @timestamp');
await PageObjects.visualize.selectField('@timestamp');
// add another metrics
log.debug('Add Metric');
await PageObjects.visualize.clickAddMetric();
log.debug('Metric = Value Axis');
await PageObjects.visualize.clickBucket('Y-Axis', 'metric');
log.debug('Aggregation = Average');
await PageObjects.visualize.selectAggregation('Average', 'metrics');
log.debug('Field = memory');
await PageObjects.visualize.selectField('machine.ram', 'metrics');
// go to options page
log.debug('Going to axis options');
await pointSeriesVis.clickAxisOptions();
// add another value axis
log.debug('adding axis');
await pointSeriesVis.clickAddAxis();
// set average count to use second value axis
await pointSeriesVis.toggleCollapsibleTitle('Average machine.ram');
log.debug('Average memory value axis - ValueAxis-2');
await pointSeriesVis.setSeriesAxis(1, 'ValueAxis-2');
await PageObjects.visualize.waitForVisualizationRenderingStabilized();
await PageObjects.visualize.clickGo();
}
describe('point series', function describeIndexTests() {
before(initChart);
describe('secondary value axis', function () {
@ -178,5 +182,32 @@ export default function ({ getService, getPageObjects }) {
});
});
describe('x axis labels', async function () {
const expectedLabels = [
'2015-09-20 00:00',
'2015-09-21 00:00',
'2015-09-22 00:00',
'2015-09-23 00:00',
];
it('should show round labels in default timezone', async function () {
await initChart();
const labels = await PageObjects.visualize.getXAxisLabels();
expect(labels).to.eql(expectedLabels);
});
it('should show round labels in different timezone', async function () {
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' });
await browser.refresh();
await initChart();
const labels = await PageObjects.visualize.getXAxisLabels();
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'Browser' });
await browser.refresh();
expect(labels).to.eql(expectedLabels);
});
});
});
}