From 95714696fb15cafdf9b27008631a75fcbedca549 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Fri, 16 Oct 2015 13:46:33 -0500 Subject: [PATCH] Add support for global timezones. Closes #1600 --- package.json | 2 + src/optimize/BaseOptimizer.js | 1 + src/plugins/kibana/public/kibana.js | 9 ++++ .../agg_types/buckets/date_histogram.js | 10 ++++- src/ui/public/config/defaults.js | 11 ++++- src/ui/public/stringify/__tests__/_date.js | 43 +++++++++++++++---- src/ui/public/stringify/types/Date.js | 11 ++++- webpackShims/moment-timezone.js | 2 + 8 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 webpackShims/moment-timezone.js diff --git a/package.json b/package.json index 8499c678a40f6..718160499f9c8 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "minimatch": "2.0.10", "mkdirp": "0.5.1", "moment": "2.10.6", + "moment-timezone": "^0.4.1", "raw-loader": "0.5.1", "request": "2.61.0", "requirefrom": "0.2.0", @@ -147,6 +148,7 @@ "html-entities": "1.1.3", "husky": "0.8.1", "istanbul-instrumenter-loader": "0.1.3", + "json-loader": "^0.5.3", "karma": "0.13.9", "karma-chrome-launcher": "0.2.0", "karma-coverage": "0.5.1", diff --git a/src/optimize/BaseOptimizer.js b/src/optimize/BaseOptimizer.js index 5f35fdd39d2ed..22ccd3434f94f 100644 --- a/src/optimize/BaseOptimizer.js +++ b/src/optimize/BaseOptimizer.js @@ -105,6 +105,7 @@ class BaseOptimizer { }, { test: /\.css$/, loader: ExtractTextPlugin.extract('style', `css${mapQ}`) }, { test: /\.jade$/, loader: 'jade' }, + { test: /\.json$/, loader: 'json' }, { test: /\.(html|tmpl)$/, loader: 'raw' }, { test: /\.png$/, loader: 'url?limit=10000&name=[path][name].[ext]' }, { test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, loader: 'file?name=[path][name].[ext]' }, diff --git a/src/plugins/kibana/public/kibana.js b/src/plugins/kibana/public/kibana.js index 5a2c325c18087..ebc3b4c476ba3 100644 --- a/src/plugins/kibana/public/kibana.js +++ b/src/plugins/kibana/public/kibana.js @@ -4,6 +4,8 @@ require('plugins/kibana/dashboard/index'); require('plugins/kibana/settings/index'); require('plugins/kibana/doc/index'); +var moment = require('moment-timezone'); + var chrome = require('ui/chrome'); var routes = require('ui/routes'); var modules = require('ui/modules'); @@ -48,8 +50,15 @@ chrome } ]) .setRootController('kibana', function ($scope, $rootScope, courier, config) { + function setDefaultTimezone() { + moment.tz.setDefault(config.get('dateFormat:tz')); + } + // wait for the application to finish loading $scope.$on('application.load', function () { courier.start(); }); + + $scope.$on('init:config', setDefaultTimezone); + $scope.$on('change:config.dateFormat:tz', setDefaultTimezone); }); diff --git a/src/ui/public/agg_types/buckets/date_histogram.js b/src/ui/public/agg_types/buckets/date_histogram.js index a471c6e311cd1..a323e304850c5 100644 --- a/src/ui/public/agg_types/buckets/date_histogram.js +++ b/src/ui/public/agg_types/buckets/date_histogram.js @@ -7,7 +7,9 @@ define(function (require) { var TimeBuckets = Private(require('ui/time_buckets')); var createFilter = Private(require('ui/agg_types/buckets/create_filter/date_histogram')); var intervalOptions = Private(require('ui/agg_types/buckets/_interval_options')); - var timeZone = tzDetect.determine().name(); + var configDefaults = Private(require('ui/config/defaults')); + + var detectedTimezone = tzDetect.determine().name(); var tzOffset = moment().format('Z'); function getInterval(agg) { @@ -94,7 +96,11 @@ define(function (require) { var interval = agg.buckets.getInterval(); output.bucketInterval = interval; output.params.interval = interval.expression; - output.params.time_zone = timeZone || tzOffset; + + var isDefaultTimezone = config.get('dateFormat:tz') === configDefaults['dateFormat:tz'].value; + output.params.time_zone = isDefaultTimezone ? + (detectedTimezone || tzOffset) : + config.get('dateFormat:tz'); var scaleMetrics = interval.scaled && interval.scale < 1; if (scaleMetrics) { diff --git a/src/ui/public/config/defaults.js b/src/ui/public/config/defaults.js index 6447489f772c5..51a8cf679db28 100644 --- a/src/ui/public/config/defaults.js +++ b/src/ui/public/config/defaults.js @@ -1,4 +1,7 @@ -define(function () { +define(function (require) { + var moment = require('moment-timezone'); + var _ = require('lodash'); + return function configDefaultsProvider() { // wraped in provider so that a new instance is given to each app/test @@ -20,6 +23,12 @@ define(function () { value: 'MMMM Do YYYY, HH:mm:ss.SSS', description: 'When displaying a pretty formatted date, use this format', }, + 'dateFormat:tz': { + value: 'Default', + description: 'Which timezone should be used. "Default" will use your detected timezone.', + type: 'select', + options: _.union(['Default'], moment.tz.names()) + }, 'dateFormat:scaled': { type: 'json', value: diff --git a/src/ui/public/stringify/__tests__/_date.js b/src/ui/public/stringify/__tests__/_date.js index ea7f5e890bb75..40f3380df52da 100644 --- a/src/ui/public/stringify/__tests__/_date.js +++ b/src/ui/public/stringify/__tests__/_date.js @@ -1,20 +1,47 @@ describe('Date Format', function () { - var fieldFormats; var expect = require('expect.js'); var ngMock = require('ngMock'); + var moment = require('moment-timezone'); + var fieldFormats; + var settings; + var convert; + var $scope; + var off; beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { + beforeEach(ngMock.inject(function (Private, config, $rootScope) { + $scope = $rootScope; + settings = config; + fieldFormats = Private(require('ui/registry/field_formats')); + var DateFormat = fieldFormats.getType('date'); + var date = new DateFormat(); + + convert = date.convert.bind(date); })); it('decoding an undefined or null date should return an empty string', function () { - var DateFormat = fieldFormats.getType('date'); - var date = new DateFormat({ - pattern: 'dd-MM-yyyy' - }); - expect(date.convert(null)).to.be('-'); - expect(date.convert(undefined)).to.be('-'); + expect(convert(null)).to.be('-'); + expect(convert(undefined)).to.be('-'); }); + it('should clear the memoization cache after changing the date', function () { + function setDefaultTimezone() { + moment.tz.setDefault(settings.get('dateFormat:tz')); + } + var time = 1445027693942; + + off = $scope.$on('change:config.dateFormat:tz', setDefaultTimezone); + + settings.set('dateFormat:tz', 'America/Chicago'); + $scope.$digest(); + var chicagoTime = convert(time); + + settings.set('dateFormat:tz', 'America/Phoenix'); + $scope.$digest(); + var phoenixTime = convert(time); + + expect(chicagoTime).not.to.equal(phoenixTime); + off(); + }); }); diff --git a/src/ui/public/stringify/types/Date.js b/src/ui/public/stringify/types/Date.js index 8f40b0c3a340f..bdd3ee463fc06 100644 --- a/src/ui/public/stringify/types/Date.js +++ b/src/ui/public/stringify/types/Date.js @@ -17,7 +17,8 @@ define(function (require) { DateTime.fieldType = 'date'; DateTime.paramDefaults = new BoundToConfigObj({ - pattern: '=dateFormat' + pattern: '=dateFormat', + timezone: '=dateFormat:tz' }); DateTime.editor = { @@ -41,9 +42,14 @@ define(function (require) { // don't give away our ref to converter so // we can hot-swap when config changes var pattern = this.param('pattern'); + var timezone = this.param('timezone'); - if (this._memoizedPattern !== pattern) { + var timezoneChanged = this._timeZone !== timezone; + var datePatternChanged = this._memoizedPattern !== pattern; + if (timezoneChanged || datePatternChanged) { + this._timeZone = timezone; this._memoizedPattern = pattern; + this._memoizedConverter = _.memoize(function converter(val) { if (val === null || val === undefined) { return '-'; @@ -51,6 +57,7 @@ define(function (require) { return moment(val).format(pattern); }); } + return this._memoizedConverter(val); }; diff --git a/webpackShims/moment-timezone.js b/webpackShims/moment-timezone.js new file mode 100644 index 0000000000000..3781ad0ab3f9d --- /dev/null +++ b/webpackShims/moment-timezone.js @@ -0,0 +1,2 @@ +var moment = module.exports = require('../node_modules/moment-timezone/moment-timezone'); +moment.tz.load(require('../node_modules/moment-timezone/data/packed/latest.json'));