Replace timefilter angular service with singleton (#19852)

* use uiSettingsClient instead of angular config service

* replace timefilter service with singleton

* remove all other uses of timefilter service, get timefilter singleton working

* remove Private from brush event

* globalState

* fix timefilter jest test

* clean up rebase artifacts

* lint errors

* another lint error

* fix broken functional test _shared_links

* fix broken mocha test doc.js

* fix service load order breaking status page

* remove Provider from change_time_filter

* another round of mocha test updates

* convert create_brush_handler test to jest

* fix kbnGlobalTimepicker toogle mocha test

* remove _root_search_source - removed in another PR and somehow I added it back during rebase

* better comments around Object.assign and proper js-docs format

* use  so listerners get cleaned up when scope is destroyed

* listen to correct timefilter update event

* update APM to use timefilter singleton

* update angular scope when timefilter changes

* fix APM jest test

* add listenAndDigestAsync to listen.js

* fix apm breadcrumbs jest test

* move diffTime and diffInterval into timefilter

* pass mode to updateFilter to resolve functional test failure

* call registerTimefilterWithGlobalState from APM application

* remove rootScope from kbn_global_timepicker

* use  to subscribe to timefilter in cases where results need to happen in digest async

* spalger review changes

* ensure monitoring timefilter callbacks are executed inside evalAsync

* remove unneeded evalAsync calls around setTime
This commit is contained in:
Nathan Reese 2018-06-26 10:29:41 -06:00 committed by GitHub
parent 2acb0265d8
commit 2341661cd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
118 changed files with 1374 additions and 1329 deletions

View file

@ -25,7 +25,7 @@ export function createSearchSource(kbnApi, initialState, indexPattern, aggs, use
searchSource.filter(() => {
const activeFilters = [...filters];
if (useTimeFilter) {
activeFilters.push(kbnApi.timeFilter.get(indexPattern));
activeFilters.push(kbnApi.timeFilter.createFilter(indexPattern));
}
return activeFilters;
});

View file

@ -36,6 +36,7 @@ import {
LOADING_STATUS,
QueryActionsProvider,
} from './query';
import { timefilter } from 'ui/timefilter';
const module = uiModules.get('apps/context', [
'elasticsearch',
@ -66,7 +67,7 @@ module.directive('contextApp', function ContextApp() {
};
});
function ContextAppController($scope, config, Private, timefilter) {
function ContextAppController($scope, config, Private) {
const queryParameterActions = Private(QueryParameterActionsProvider);
const queryActions = Private(QueryActionsProvider);

View file

@ -49,6 +49,7 @@ import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_fa
import { DashboardPanelActionsRegistryProvider } from 'ui/dashboard_panel_actions/dashboard_panel_actions_registry';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { timefilter } from 'ui/timefilter';
import { DashboardViewportProvider } from './viewport/dashboard_viewport_provider';
@ -68,7 +69,6 @@ app.directive('dashboardViewportProvider', function (reactDirective) {
app.directive('dashboardApp', function ($injector) {
const courier = $injector.get('courier');
const AppState = $injector.get('AppState');
const timefilter = $injector.get('timefilter');
const kbnUrl = $injector.get('kbnUrl');
const confirmModal = $injector.get('confirmModal');
const config = $injector.get('config');
@ -164,8 +164,7 @@ app.directive('dashboardApp', function ($injector) {
$rootScope.$broadcast('fetch');
courier.fetch(...args);
};
$scope.timefilter = timefilter;
dashboardStateManager.handleTimeChange($scope.timefilter.time);
dashboardStateManager.handleTimeChange(timefilter.getTime());
$scope.expandedPanel = null;
$scope.dashboardViewMode = dashboardStateManager.getViewMode();
@ -223,8 +222,8 @@ app.directive('dashboardApp', function ($injector) {
$scope.$watch('model.query', $scope.updateQueryAndFetch);
$scope.$listen(timefilter, 'fetch', () => {
dashboardStateManager.handleTimeChange($scope.timefilter.time);
$scope.$listenAndDigestAsync(timefilter, 'fetch', () => {
dashboardStateManager.handleTimeChange(timefilter.getTime());
// Currently discover relies on this logic to re-fetch. We need to refactor it to rely instead on the
// directly passed down time filter. Then we can get rid of this reliance on scope broadcasts.
$scope.refresh();

View file

@ -28,7 +28,10 @@ jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: t
describe('DashboardState', function () {
let dashboardState;
const savedDashboard = getSavedDashboardMock();
const timefilter = { time: {} };
const mockTimefilter = {
time: {},
setTime: function (time) { this.time = time; },
};
const mockQuickTimeRanges = [{ from: 'now/w', to: 'now/w', display: 'This week', section: 0 }];
const mockIndexPattern = { id: 'index1' };
@ -46,16 +49,16 @@ describe('DashboardState', function () {
savedDashboard.timeFrom = 'now/w';
savedDashboard.timeTo = 'now/w';
timefilter.time.from = '2015-09-19 06:31:44.000';
timefilter.time.to = '2015-09-29 06:31:44.000';
timefilter.time.mode = 'absolute';
mockTimefilter.time.from = '2015-09-19 06:31:44.000';
mockTimefilter.time.to = '2015-09-29 06:31:44.000';
mockTimefilter.time.mode = 'absolute';
initDashboardState();
dashboardState.syncTimefilterWithDashboard(timefilter, mockQuickTimeRanges);
dashboardState.syncTimefilterWithDashboard(mockTimefilter, mockQuickTimeRanges);
expect(timefilter.time.mode).toBe('quick');
expect(timefilter.time.to).toBe('now/w');
expect(timefilter.time.from).toBe('now/w');
expect(mockTimefilter.time.mode).toBe('quick');
expect(mockTimefilter.time.to).toBe('now/w');
expect(mockTimefilter.time.from).toBe('now/w');
});
test('syncs relative time', function () {
@ -63,16 +66,16 @@ describe('DashboardState', function () {
savedDashboard.timeFrom = 'now-13d';
savedDashboard.timeTo = 'now';
timefilter.time.from = '2015-09-19 06:31:44.000';
timefilter.time.to = '2015-09-29 06:31:44.000';
timefilter.time.mode = 'absolute';
mockTimefilter.time.from = '2015-09-19 06:31:44.000';
mockTimefilter.time.to = '2015-09-29 06:31:44.000';
mockTimefilter.time.mode = 'absolute';
initDashboardState();
dashboardState.syncTimefilterWithDashboard(timefilter, mockQuickTimeRanges);
dashboardState.syncTimefilterWithDashboard(mockTimefilter, mockQuickTimeRanges);
expect(timefilter.time.mode).toBe('relative');
expect(timefilter.time.to).toBe('now');
expect(timefilter.time.from).toBe('now-13d');
expect(mockTimefilter.time.mode).toBe('relative');
expect(mockTimefilter.time.to).toBe('now');
expect(mockTimefilter.time.from).toBe('now-13d');
});
test('syncs absolute time', function () {
@ -80,16 +83,16 @@ describe('DashboardState', function () {
savedDashboard.timeFrom = '2015-09-19 06:31:44.000';
savedDashboard.timeTo = '2015-09-29 06:31:44.000';
timefilter.time.from = 'now/w';
timefilter.time.to = 'now/w';
timefilter.time.mode = 'quick';
mockTimefilter.time.from = 'now/w';
mockTimefilter.time.to = 'now/w';
mockTimefilter.time.mode = 'quick';
initDashboardState();
dashboardState.syncTimefilterWithDashboard(timefilter, mockQuickTimeRanges);
dashboardState.syncTimefilterWithDashboard(mockTimefilter, mockQuickTimeRanges);
expect(timefilter.time.mode).toBe('absolute');
expect(timefilter.time.to).toBe(savedDashboard.timeTo);
expect(timefilter.time.from).toBe(savedDashboard.timeFrom);
expect(mockTimefilter.time.mode).toBe('absolute');
expect(mockTimefilter.time.to).toBe(savedDashboard.timeTo);
expect(mockTimefilter.time.from).toBe(savedDashboard.timeFrom);
});
});

View file

@ -142,12 +142,11 @@ export class DashboardStateManager {
* @param {String} newTimeFilter.mode
*/
handleTimeChange(newTimeFilter) {
const timeFilter = {
store.dispatch(updateTimeRange({
from: FilterUtils.convertTimeToUTCString(newTimeFilter.from),
to: FilterUtils.convertTimeToUTCString(newTimeFilter.to),
mode: newTimeFilter.mode,
};
store.dispatch(updateTimeRange(timeFilter));
}));
}
/**
@ -433,8 +432,8 @@ export class DashboardStateManager {
*/
getTimeChanged(timeFilter) {
return (
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeFrom, timeFilter.time.from) ||
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeTo, timeFilter.time.to)
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeFrom, timeFilter.getTime().from) ||
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeTo, timeFilter.getTime().to)
);
}
@ -536,7 +535,8 @@ export class DashboardStateManager {
/**
* Updates timeFilter to match the time saved with the dashboard.
* @param {Object} timeFilter
* @param {Object} timeFilter.time
* @param {func} timeFilter.setTime
* @param {func} timeFilter.setRefreshInterval
* @param quickTimeRanges
*/
syncTimefilterWithDashboard(timeFilter, quickTimeRanges) {
@ -544,20 +544,25 @@ export class DashboardStateManager {
throw new Error('The time is not saved with this dashboard so should not be synced.');
}
timeFilter.time.to = this.savedDashboard.timeTo;
timeFilter.time.from = this.savedDashboard.timeFrom;
let mode;
const isMoment = moment(this.savedDashboard.timeTo).isValid();
if (isMoment) {
timeFilter.time.mode = 'absolute';
mode = 'absolute';
} else {
const quickTime = _.find(
quickTimeRanges,
(timeRange) => timeRange.from === this.savedDashboard.timeFrom && timeRange.to === this.savedDashboard.timeTo);
timeFilter.time.mode = quickTime ? 'quick' : 'relative';
mode = quickTime ? 'quick' : 'relative';
}
timeFilter.setTime({
from: this.savedDashboard.timeFrom,
to: this.savedDashboard.timeTo,
mode
});
if (this.savedDashboard.refreshInterval) {
timeFilter.refreshInterval = this.savedDashboard.refreshInterval;
timeFilter.setRefreshInterval(this.savedDashboard.refreshInterval);
}
}

View file

@ -28,11 +28,11 @@ export function updateSavedDashboard(savedDashboard, appState, timeFilter, toJso
savedDashboard.optionsJSON = toJson(appState.options);
savedDashboard.timeFrom = savedDashboard.timeRestore ?
FilterUtils.convertTimeToUTCString(timeFilter.time.from)
FilterUtils.convertTimeToUTCString(timeFilter.getTime().from)
: undefined;
savedDashboard.timeTo = savedDashboard.timeRestore ?
FilterUtils.convertTimeToUTCString(timeFilter.time.to)
FilterUtils.convertTimeToUTCString(timeFilter.getTime().to)
: undefined;
const timeRestoreObj = _.pick(timeFilter.refreshInterval, ['display', 'pause', 'section', 'value']);
const timeRestoreObj = _.pick(timeFilter.getRefreshInterval(), ['display', 'pause', 'section', 'value']);
savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined;
}

View file

@ -31,7 +31,7 @@ import 'ui/filters/moment';
import 'ui/courier';
import 'ui/index_patterns';
import 'ui/state_management/app_state';
import 'ui/timefilter';
import { timefilter } from 'ui/timefilter';
import 'ui/share';
import 'ui/query_bar';
import { toastNotifications, getPainlessError } from 'ui/notify';
@ -142,7 +142,6 @@ function discoverController(
config,
courier,
kbnUrl,
timefilter,
localStorage,
) {
@ -186,8 +185,6 @@ function discoverController(
template: require('plugins/kibana/discover/partials/share_search.html'),
testId: 'discoverShareButton',
}];
$scope.timefilter = timefilter;
// the saved savedSearch
const savedSearch = $route.current.locals.savedSearch;
@ -204,7 +201,7 @@ function discoverController(
// searchSource which applies time range
const timeRangeSearchSource = savedSearch.searchSource.new();
timeRangeSearchSource.set('filter', () => {
return timefilter.get($scope.indexPattern);
return timefilter.createFilter($scope.indexPattern);
});
$scope.searchSource.inherits(timeRangeSearchSource);
@ -326,7 +323,6 @@ function discoverController(
timefield: $scope.indexPattern.timeFieldName,
savedSearch: savedSearch,
indexPatternList: $route.current.locals.ip.list,
timefilter: $scope.timefilter
};
const init = _.once(function () {
@ -633,8 +629,8 @@ function discoverController(
$scope.updateTime = function () {
$scope.timeRange = {
from: dateMath.parse(timefilter.time.from),
to: dateMath.parse(timefilter.time.to, { roundUp: true })
from: dateMath.parse(timefilter.getTime().from),
to: dateMath.parse(timefilter.getTime().to, { roundUp: true })
};
};
@ -739,7 +735,7 @@ function discoverController(
}
$scope.vis.filters = {
timeRange: timefilter.time
timeRange: timefilter.getTime()
};
}

View file

@ -22,10 +22,10 @@ import ngMock from 'ng_mock';
import expect from 'expect.js';
import '..';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { timefilter } from 'ui/timefilter';
let $scope;
let createController;
let timefilter;
const init = function (index, type, id) {
@ -85,9 +85,8 @@ const init = function (index, type, id) {
});
// Create the scope
ngMock.inject(function ($rootScope, $controller, _timefilter_) {
ngMock.inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
timefilter = _timefilter_;
createController = function () {
return $controller('doc', {

View file

@ -24,6 +24,7 @@ import 'ui/index_patterns';
import html from '../index.html';
import uiRoutes from 'ui/routes';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
const app = uiModules.get('apps/doc', [
@ -49,7 +50,7 @@ uiRoutes
resolve: resolveIndexPattern
});
app.controller('doc', function ($scope, $route, es, timefilter) {
app.controller('doc', function ($scope, $route, es) {
timefilter.disableAutoRefreshSelector();
timefilter.disableTimeRangeSelector();

View file

@ -27,6 +27,7 @@ import appTemplate from './app.html';
import landingTemplate from './landing.html';
import { management } from 'ui/management';
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
import { timefilter } from 'ui/timefilter';
import 'ui/kbn_top_nav';
uiRoutes
@ -45,7 +46,7 @@ require('ui/index_patterns/route_setup/load_default')({
uiModules
.get('apps/management')
.directive('kbnManagementApp', function (Private, $location, timefilter) {
.directive('kbnManagementApp', function (Private, $location) {
return {
restrict: 'E',
template: appTemplate,

View file

@ -40,6 +40,7 @@ import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
import { recentlyAccessed } from 'ui/persisted_log';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when(VisualizeConstants.CREATE_PATH, {
@ -96,7 +97,7 @@ uiModules
};
});
function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, config, kbnBaseUrl, localStorage) {
function VisEditor($scope, $route, AppState, $window, kbnUrl, courier, Private, Promise, config, kbnBaseUrl, localStorage) {
const docTitle = Private(DocTitleProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
@ -221,9 +222,8 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.isAddToDashMode = () => addToDashMode;
$scope.timefilter = timefilter;
$scope.timeRange = timefilter.time;
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter', 'isAddToDashMode');
$scope.timeRange = timefilter.getTime();
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'isAddToDashMode');
stateMonitor = stateMonitorFactory.create($state, stateDefaults);
stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => {
@ -252,11 +252,11 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
});
const updateTimeRange = () => {
$scope.timeRange = timefilter.time;
$scope.timeRange = timefilter.getTime();
};
timefilter.enableAutoRefreshSelector();
timefilter.on('update', updateTimeRange);
$scope.$listenAndDigestAsync(timefilter, 'timeUpdate', updateTimeRange);
// update the searchSource when filters update
$scope.$listen(queryFilter, 'update', function () {
@ -274,7 +274,6 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.$on('$destroy', function () {
savedVis.destroy();
stateMonitor.destroy();
timefilter.off('update', updateTimeRange);
});
}

View file

@ -21,6 +21,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis
import 'ui/pager_control';
import 'ui/pager';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
import { VisualizeListingTable } from './visualize_listing_table';
@ -32,7 +33,6 @@ app.directive('visualizeListingTable', function (reactDirective) {
export function VisualizeListingController($injector) {
const Notifier = $injector.get('Notifier');
const Private = $injector.get('Private');
const timefilter = $injector.get('timefilter');
const config = $injector.get('config');
timefilter.disableAutoRefreshSelector();

View file

@ -33,6 +33,7 @@ import { uiModules } from 'ui/modules';
import visualizeWizardStep1Template from './step_1.html';
import visualizeWizardStep2Template from './step_2.html';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { timefilter } from 'ui/timefilter';
const module = uiModules.get('app/visualize', ['kibana/courier']);
@ -50,7 +51,7 @@ routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, {
controller: 'VisualizeWizardStep1',
});
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private, config) {
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, Private, config) {
timefilter.disableAutoRefreshSelector();
timefilter.disableTimeRangeSelector();
@ -220,7 +221,7 @@ routes.when(VisualizeConstants.WIZARD_STEP_2_PAGE_PATH, {
}
});
module.controller('VisualizeWizardStep2', function ($route, $scope, timefilter, kbnUrl) {
module.controller('VisualizeWizardStep2', function ($route, $scope, kbnUrl) {
const type = $route.current.params.type;
const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM];
kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);

View file

@ -20,8 +20,9 @@
import { validateInterval } from '../lib/validate_interval';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { timefilter } from 'ui/timefilter';
const MetricsRequestHandlerProvider = function (Private, Notifier, config, timefilter, $http) {
const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http) {
const dashboardContext = Private(dashboardContextProvider);
const notify = new Notifier({ location: 'Metrics' });

View file

@ -22,21 +22,24 @@ import moment from 'moment';
import { expect } from 'chai';
describe('createBrushHandler', () => {
let timefilter;
let fn;
let mockTimefilter;
let onBrush;
let range;
beforeEach(() => {
timefilter = { time: {}, update: () => {} };
fn = createBrushHandler(timefilter);
range = { xaxis: { from: '2017-01-01T00:00:00Z', to: '2017-01-01T00:10:00Z' } };
fn(range);
mockTimefilter = {
time: {},
setTime: function (time) { this.time = time; }
};
onBrush = createBrushHandler(mockTimefilter);
});
it('returns brushHandler() that updates timefilter', () => {
expect(timefilter.time.from).to.equal(moment(range.xaxis.from).toISOString());
expect(timefilter.time.to).to.equal(moment(range.xaxis.to).toISOString());
expect(timefilter.time.mode).to.equal('absolute');
test('returns brushHandler() that updates timefilter', () => {
range = { xaxis: { from: '2017-01-01T00:00:00Z', to: '2017-01-01T00:10:00Z' } };
onBrush(range);
expect(mockTimefilter.time.from).to.equal(moment(range.xaxis.from).toISOString());
expect(mockTimefilter.time.to).to.equal(moment(range.xaxis.to).toISOString());
expect(mockTimefilter.time.mode).to.equal('absolute');
});
});

View file

@ -19,10 +19,9 @@
import moment from 'moment';
export default (timefilter) => ranges => {
//$scope.$evalAsync(() => {
timefilter.time.from = moment(ranges.xaxis.from).toISOString();
timefilter.time.to = moment(ranges.xaxis.to).toISOString();
timefilter.time.mode = 'absolute';
timefilter.update();
//});
timefilter.setTime({
from: moment(ranges.xaxis.from).toISOString(),
to: moment(ranges.xaxis.to).toISOString(),
mode: 'absolute',
});
};

View file

@ -25,6 +25,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis
import { notify, fatalError, toastNotifications } from 'ui/notify';
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { recentlyAccessed } from 'ui/persisted_log';
import { timefilter } from 'ui/timefilter';
// import the uiExports that we want to "use"
import 'uiExports/fieldFormats';
@ -93,7 +94,6 @@ app.controller('timelion', function (
kbnUrl,
Notifier,
Private,
timefilter
) {
// Keeping this at app scope allows us to keep the current page when the user
@ -213,8 +213,9 @@ app.controller('timelion', function (
};
let refresher;
$scope.$watchCollection('timefilter.refreshInterval', function (interval) {
$scope.$listen(timefilter, 'refreshIntervalUpdate', function () {
if (refresher) $timeout.cancel(refresher);
const interval = timefilter.getRefreshInterval();
if (interval.value > 0 && !interval.pause) {
function startRefresh() {
refresher = $timeout(function () {
@ -254,7 +255,7 @@ app.controller('timelion', function (
const httpResult = $http.post('../api/timelion/run', {
sheet: $scope.state.sheet,
time: _.extend(timefilter.time, {
time: _.extend(timefilter.getTime(), {
interval: $scope.state.interval,
timezone: timezone
}),

View file

@ -24,10 +24,11 @@ import $ from 'jquery';
import moment from 'moment-timezone';
import observeResize from '../../lib/observe_resize';
import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib';
import { timefilter } from 'ui/timefilter';
const DEBOUNCE_DELAY = 50;
export default function timechartFn(Private, config, $rootScope, timefilter, $compile) {
export default function timechartFn(Private, config, $rootScope, $compile) {
return function () {
return {
help: 'Draw a timeseries chart',
@ -173,10 +174,11 @@ export default function timechartFn(Private, config, $rootScope, timefilter, $co
});
$elem.on('plotselected', function (event, ranges) {
timefilter.time.from = moment(ranges.xaxis.from);
timefilter.time.to = moment(ranges.xaxis.to);
timefilter.time.mode = 'absolute';
$scope.$apply();
timefilter.setTime({
from: moment(ranges.xaxis.from),
to: moment(ranges.xaxis.to),
mode: 'absolute',
});
});
$elem.on('mouseleave', function () {

View file

@ -21,8 +21,9 @@ import { VegaParser } from './data_model/vega_parser';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
import { SearchCache } from './data_model/search_cache';
import { TimeCache } from './data_model/time_cache';
import { timefilter } from 'ui/timefilter';
export function VegaRequestHandlerProvider(Private, es, timefilter, serviceSettings) {
export function VegaRequestHandlerProvider(Private, es, serviceSettings) {
const dashboardContext = Private(dashboardContextProvider);
const searchCache = new SearchCache(es, { max: 10, maxAge: 4 * 1000 });

View file

@ -25,6 +25,7 @@ import AggParamWriterProvider from '../../agg_param_writer';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { aggTypes } from '../../..';
import { AggConfig } from '../../../../vis/agg_config';
import { timefilter } from 'ui/timefilter';
describe('params', function () {
@ -35,10 +36,9 @@ describe('params', function () {
let timeField;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector) {
beforeEach(ngMock.inject(function (Private) {
const AggParamWriter = Private(AggParamWriterProvider);
const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
const timefilter = $injector.get('timefilter');
timeField = indexPattern.timeFieldName;

View file

@ -24,7 +24,8 @@ import { metadata } from '../metadata';
import 'babel-polyfill';
import 'whatwg-fetch';
import 'custom-event-polyfill';
import '../timefilter';
import '../state_management/global_state';
import '../config';
import '../notify';
import '../private';
import '../promises';

View file

@ -1,9 +1,9 @@
<kbn-timepicker
from="timefilter.time.from"
to="timefilter.time.to"
mode="timefilter.time.mode"
from="timefilterValues.time.from"
to="timefilterValues.time.to"
mode="timefilterValues.time.mode"
active-tab="'filter'"
interval="timefilter.refreshInterval"
on-filter-select="updateFilter(from, to)"
interval="timefilterValues.refreshInterval"
on-filter-select="updateFilter(from, to, mode)"
on-interval-select="updateInterval(interval)">
</kbn-timepicker>

View file

@ -1,9 +1,9 @@
<kbn-timepicker
from="timefilter.time.from"
to="timefilter.time.to"
mode="timefilter.time.mode"
from="timefilterValues.time.from"
to="timefilterValues.time.to"
mode="timefilterValues.time.mode"
active-tab="'interval'"
interval="timefilter.refreshInterval"
on-filter-select="updateFilter(from, to)"
interval="timefilterValues.refreshInterval"
on-filter-select="updateFilter(from, to, mode)"
on-interval-select="updateInterval(interval)">
</kbn-timepicker>

View file

@ -31,6 +31,7 @@ import { FetchSoonProvider } from './fetch';
import { SearchLooperProvider } from './looper/search';
import { SavedObjectProvider } from './saved_object';
import { RedirectWhenMissingProvider } from './_redirect_when_missing';
import { timefilter } from 'ui/timefilter';
uiModules.get('kibana/courier')
@ -114,10 +115,9 @@ uiModules.get('kibana/courier')
}
};
// Listen for refreshInterval changes
$rootScope.$watchCollection('timefilter.refreshInterval', function () {
const refreshValue = _.get($rootScope, 'timefilter.refreshInterval.value');
const refreshPause = _.get($rootScope, 'timefilter.refreshInterval.pause');
$rootScope.$listen(timefilter, 'refreshIntervalUpdate', function () {
const refreshValue = _.get(timefilter.getRefreshInterval(), 'value');
const refreshPause = _.get(timefilter.getRefreshInterval(), 'pause');
if (_.isNumber(refreshValue) && !refreshPause) {
self.fetchInterval(refreshValue);
} else {

View file

@ -18,8 +18,9 @@
*/
import { requestFetchParamsToBody } from './request_fetch_params_to_body';
import { timefilter } from 'ui/timefilter';
export function RequestFetchParamsToBodyProvider(Promise, timefilter, kbnIndex, sessionId, config, esShardTimeout) {
export function RequestFetchParamsToBodyProvider(Promise, kbnIndex, sessionId, config, esShardTimeout) {
return (requestsFetchParams) => (
requestFetchParamsToBody(
requestsFetchParams,

View file

@ -22,8 +22,9 @@ import { Notifier } from '../../../notify';
import { SearchRequestProvider } from './search_request';
import { SegmentedHandleProvider } from './segmented_handle';
import { pushAll } from '../../../utils/collection';
import { timefilter } from 'ui/timefilter';
export function SegmentedRequestProvider(Private, timefilter, config) {
export function SegmentedRequestProvider(Private, config) {
const SearchRequest = Private(SearchRequestProvider);
const SegmentedHandle = Private(SegmentedHandleProvider);

View file

@ -27,7 +27,7 @@ import { FilterBarLibMapAndFlattenFiltersProvider } from './lib/map_and_flatten_
import { FilterBarLibMapFlattenAndWrapFiltersProvider } from './lib/map_flatten_and_wrap_filters';
import { FilterBarLibExtractTimeFilterProvider } from './lib/extract_time_filter';
import { FilterBarLibFilterOutTimeBasedFilterProvider } from './lib/filter_out_time_based_filter';
import { FilterBarLibChangeTimeFilterProvider } from './lib/change_time_filter';
import { changeTimeFilter } from './lib/change_time_filter';
import { FilterBarQueryFilterProvider } from './query_filter';
import { compareFilters } from './lib/compare_filters';
import { uiModules } from '../modules';
@ -42,7 +42,6 @@ module.directive('filterBar', function (Private, Promise, getAppState) {
const mapFlattenAndWrapFilters = Private(FilterBarLibMapFlattenAndWrapFiltersProvider);
const extractTimeFilter = Private(FilterBarLibExtractTimeFilterProvider);
const filterOutTimeBasedFilter = Private(FilterBarLibFilterOutTimeBasedFilterProvider);
const changeTimeFilter = Private(FilterBarLibChangeTimeFilterProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
return {

View file

@ -1,61 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import moment from 'moment';
import ngMock from 'ng_mock';
import expect from 'expect.js';
import { FilterBarLibChangeTimeFilterProvider } from '../change_time_filter';
describe('Filter Bar Directive', function () {
describe('changeTimeFilter()', function () {
let changeTimeFilter;
let timefilter;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, _timefilter_) {
changeTimeFilter = Private(FilterBarLibChangeTimeFilterProvider);
timefilter = _timefilter_;
}));
it('should change the timefilter to match the range gt/lt', function () {
const filter = { range: { '@timestamp': { gt: 1388559600000, lt: 1388646000000 } } };
changeTimeFilter(filter);
expect(timefilter.time.mode).to.be('absolute');
expect(moment.isMoment(timefilter.time.to)).to.be(true);
expect(timefilter.time.to.isSame('2014-01-02'));
expect(moment.isMoment(timefilter.time.from)).to.be(true);
expect(timefilter.time.from.isSame('2014-01-01'));
});
it('should change the timefilter to match the range gte/lte', function () {
const filter = { range: { '@timestamp': { gte: 1388559600000, lte: 1388646000000 } } };
changeTimeFilter(filter);
expect(timefilter.time.mode).to.be('absolute');
expect(moment.isMoment(timefilter.time.to)).to.be(true);
expect(timefilter.time.to.isSame('2014-01-02'));
expect(moment.isMoment(timefilter.time.from)).to.be(true);
expect(timefilter.time.from.isSame('2014-01-01'));
});
});
});

View file

@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
jest.mock('ui/chrome',
() => ({
getUiSettingsClient: () => {
return {
get: (key) => {
switch(key) {
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now', mode: 'quick' };
case 'timepicker:refreshIntervalDefaults':
return { display: 'Off', pause: false, value: 0 };
default:
throw new Error(`Unexpected config key: ${key}`);
}
}
};
},
}), { virtual: true });
import moment from 'moment';
import expect from 'expect.js';
import { changeTimeFilter } from '../change_time_filter';
import { timefilter } from 'ui/timefilter';
describe('changeTimeFilter()', function () {
test('should change the timefilter to match the range gt/lt', function () {
const filter = { range: { '@timestamp': { gt: 1388559600000, lt: 1388646000000 } } };
changeTimeFilter(filter);
expect(timefilter.getTime().mode).to.be('absolute');
expect(moment.isMoment(timefilter.getTime().to)).to.be(true);
expect(timefilter.getTime().to.isSame('2014-01-02'));
expect(moment.isMoment(timefilter.getTime().from)).to.be(true);
expect(timefilter.getTime().from.isSame('2014-01-01'));
});
test('should change the timefilter to match the range gte/lte', function () {
const filter = { range: { '@timestamp': { gte: 1388559600000, lte: 1388646000000 } } };
changeTimeFilter(filter);
expect(timefilter.getTime().mode).to.be('absolute');
expect(moment.isMoment(timefilter.getTime().to)).to.be(true);
expect(timefilter.getTime().to.isSame('2014-01-02'));
expect(moment.isMoment(timefilter.getTime().from)).to.be(true);
expect(timefilter.getTime().from.isSame('2014-01-01'));
});
});

View file

@ -19,13 +19,14 @@
import moment from 'moment';
import _ from 'lodash';
import { timefilter } from 'ui/timefilter';
export function FilterBarLibChangeTimeFilterProvider(timefilter) {
return function (filter) {
const key = _.keys(filter.range)[0];
const values = filter.range[key];
timefilter.time.from = moment(values.gt || values.gte);
timefilter.time.to = moment(values.lt || values.lte);
timefilter.time.mode = 'absolute';
};
export function changeTimeFilter(filter) {
const key = _.keys(filter.range)[0];
const values = filter.range[key];
timefilter.setTime({
from: moment(values.gt || values.gte),
to: moment(values.lt || values.lte),
mode: 'absolute',
});
}

View file

@ -21,8 +21,9 @@ import _ from 'lodash';
import moment from 'moment';
import { IndexedArray } from '../indexed_array';
import { isNumeric } from '../utils/numeric';
import { timefilter } from 'ui/timefilter';
export function IndexPatternsIntervalsProvider(timefilter) {
export function IndexPatternsIntervalsProvider() {
const intervals = new IndexedArray({
index: ['name'],

View file

@ -22,15 +22,15 @@ import { uiModules } from '../modules';
uiModules.get('kibana')
.run(function ($rootScope) {
/**
* Helper that registers an event listener, and removes that listener when
* the $scope is destroyed.
*
* @param {EventEmitter} emitter - the event emitter to listen to
* @param {string} eventName - the event name
* @param {Function} handler - the event handler
* @return {undefined}
*/
/**
* Helper that registers an event listener, and removes that listener when
* the $scope is destroyed.
*
* @param {SimpleEmitter} emitter - the event emitter to listen to
* @param {string} eventName - the event name
* @param {Function} handler - the event handler
* @return {undefined}
*/
$rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) {
emitter.on(eventName, handler);
this.$on('$destroy', function () {
@ -38,4 +38,20 @@ uiModules.get('kibana')
});
};
/**
* Helper that registers an event listener, and removes that listener when
* the $scope is destroyed. Handler is executed inside $evalAsync, ensuring digest cycle is run after the handler
*
* @param {SimpleEmitter} emitter - the event emitter to listen to
* @param {string} eventName - the event name
* @param {Function} handler - the event handler
* @return {undefined}
*/
$rootScope.constructor.prototype.$listenAndDigestAsync = function (emitter, eventName, handler) {
const evalAsyncWrappedHandler = (...args) => {
this.$evalAsync(() => handler(args));
};
this.$listen(emitter, eventName, evalAsyncWrappedHandler);
};
});

View file

@ -1,84 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import expect from 'expect.js';
import ngMock from 'ng_mock';
describe('Timefilter service', function () {
describe('calculateBounds', function () {
beforeEach(ngMock.module('kibana'));
const fifteenMinutesInMilliseconds = 15 * 60 * 1000;
const clockNowTicks = new Date(2000, 1, 1, 0, 0, 0, 0).valueOf();
let timefilter;
let $location;
let clock;
beforeEach(ngMock.inject(function (_timefilter_, _$location_) {
timefilter = _timefilter_;
$location = _$location_;
clock = sinon.useFakeTimers(clockNowTicks);
}));
afterEach(function () {
clock.restore();
});
it('uses clock time by default', function () {
const timeRange = {
from: 'now-15m',
to: 'now'
};
const result = timefilter.calculateBounds(timeRange);
expect(result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds);
expect(result.max.valueOf()).to.eql(clockNowTicks);
});
it('uses forceNow string', function () {
const timeRange = {
from: 'now-15m',
to: 'now'
};
const forceNowString = '1999-01-01T00:00:00.000Z';
$location.search('forceNow', forceNowString);
const result = timefilter.calculateBounds(timeRange);
const forceNowTicks = Date.parse(forceNowString);
expect(result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds);
expect(result.max.valueOf()).to.eql(forceNowTicks);
});
it(`throws Error if forceNow can't be parsed`, function () {
const timeRange = {
from: 'now-15m',
to: 'now'
};
$location.search('forceNow', 'malformed%20string');
expect(() => timefilter.calculateBounds(timeRange)).to.throwError();
});
});
});

View file

@ -17,4 +17,4 @@
* under the License.
*/
import './timefilter';
export { timefilter, registerTimefilterWithGlobalState } from './timefilter';

View file

@ -1,36 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { areTimePickerValsDifferent } from './diff_time_picker_vals';
export function diffIntervalFactory(self) {
let oldRefreshInterval = _.clone(self.refreshInterval);
return function () {
if (areTimePickerValsDifferent(self.refreshInterval, oldRefreshInterval)) {
self.emit('update');
if (!self.refreshInterval.pause && self.refreshInterval.value !== 0) {
self.emit('fetch');
}
}
oldRefreshInterval = _.clone(self.refreshInterval);
};
}

View file

@ -1,95 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import expect from 'expect.js';
import { diffIntervalFactory } from '../lib/diff_interval';
describe('Refresh interval diff watcher', () => {
let diffInterval;
let update;
let fetch;
let timefilter;
beforeEach(() => {
update = sinon.spy();
fetch = sinon.spy();
timefilter = {
refreshInterval: {
pause: false,
value: 0
},
emit: (eventType) => {
if (eventType === 'update') update();
if (eventType === 'fetch') fetch();
}
};
diffInterval = diffIntervalFactory(timefilter);
});
test('not emit anything if nothing has changed', () => {
timefilter.refreshInterval = { pause: false, value: 0 };
diffInterval();
expect(update.called).to.be(false);
expect(fetch.called).to.be(false);
});
test('emit only an update when paused', () => {
timefilter.refreshInterval = { pause: true, value: 5000 };
diffInterval();
expect(update.called).to.be(true);
expect(fetch.called).to.be(false);
});
test('emit update, not fetch, when switching to value: 0', () => {
timefilter.refreshInterval = { pause: false, value: 5000 };
diffInterval();
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(true);
timefilter.refreshInterval = { pause: false, value: 0 };
diffInterval();
expect(update.calledTwice).to.be(true);
expect(fetch.calledTwice).to.be(false);
});
test('should emit update, not fetch, when moving from unpaused to paused', () => {
timefilter.refreshInterval = { pause: false, value: 5000 };
diffInterval();
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(true);
timefilter.refreshInterval = { pause: true, value: 5000 };
diffInterval();
expect(update.calledTwice).to.be(true);
expect(fetch.calledTwice).to.be(false);
});
test('should emit update and fetch when unpaused', () => {
timefilter.refreshInterval = { pause: true, value: 5000 };
diffInterval();
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(false);
timefilter.refreshInterval = { pause: false, value: 5000 };
diffInterval();
expect(update.calledTwice).to.be(true);
expect(fetch.calledOnce).to.be(true);
});
});

View file

@ -1,34 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { areTimePickerValsDifferent } from './diff_time_picker_vals';
import { timeHistory } from '../time_history';
export function diffTimeFactory(self) {
let oldTime = _.clone(self.time);
return function () {
if (areTimePickerValsDifferent(self.time, oldTime)) {
timeHistory.add(self.time);
self.emit('update');
self.emit('fetch');
}
oldTime = _.clone(self.time);
};
}

View file

@ -1,61 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import expect from 'expect.js';
import { diffTimeFactory } from '../lib/diff_time';
describe('time diff watcher', () => {
let diffTime;
let update;
let fetch;
let timefilter;
beforeEach(() => {
update = sinon.spy();
fetch = sinon.spy();
timefilter = {
time: {
from: 0,
to: 1
},
emit: function (eventType) {
if (eventType === 'update') update();
if (eventType === 'fetch') fetch();
}
};
diffTime = diffTimeFactory(timefilter);
});
test('not emit anything if the time has not changed', () => {
timefilter.time = { from: 0, to: 1 };
diffTime();
expect(update.called).to.be(false);
expect(fetch.called).to.be(false);
});
test('emit update and fetch if the time has changed', () => {
timefilter.time = { from: 5, to: 10 };
diffTime();
expect(update.called).to.be(true);
expect(fetch.called).to.be(true);
});
});

View file

@ -17,11 +17,15 @@
* under the License.
*/
import { uiModules } from '../../modules';
import qs from 'querystring';
uiModules.get('kibana').config(function ($provide) {
$provide.decorator('timefilter', function ($delegate) {
$delegate.init();
return $delegate;
});
});
export function parseQueryString() {
// window.location.search is an empty string
// get search from href
const hrefSplit = window.location.href.split('?');
if (hrefSplit.length <= 1) {
return {};
}
return qs.parse(hrefSplit[1]);
}

View file

@ -20,142 +20,174 @@
import _ from 'lodash';
import moment from 'moment';
import { calculateBounds, getTime } from './get_time';
import '../state_management/global_state';
import '../config';
import { EventsProvider } from '../events';
import { diffTimeFactory } from './lib/diff_time';
import { diffIntervalFactory } from './lib/diff_interval';
import { parseQueryString } from 'ui/timefilter/lib/parse_querystring';
import { SimpleEmitter } from 'ui/utils/simple_emitter';
import uiRoutes from '../routes';
import { uiModules } from '../modules';
import { createLegacyClass } from '../utils/legacy_class';
import chrome from 'ui/chrome';
import { areTimePickerValsDifferent } from './lib/diff_time_picker_vals';
import { timeHistory } from './time_history';
class Timefilter extends SimpleEmitter {
constructor() {
super();
this.isTimeRangeSelectorEnabled = false;
this.isAutoRefreshSelectorEnabled = false;
this._time = chrome.getUiSettingsClient().get('timepicker:timeDefaults');
this._refreshInterval = chrome.getUiSettingsClient().get('timepicker:refreshIntervalDefaults');
}
getTime = () => {
return _.clone(this._time);
}
/**
* Updates timefilter time.
* Emits 'timeUpdate' and 'fetch' events when time changes
* @param {Object} time
* @property {string|moment} time.from
* @property {string|moment} time.to
* @property {string} time.mode (quick | relative | absolute)
*/
setTime = (time) => {
// Object.assign used for partially composed updates
const newTime = Object.assign(this.getTime(), time);
if (areTimePickerValsDifferent(this.getTime(), newTime)) {
this._time = newTime;
timeHistory.add(newTime);
this.emit('timeUpdate');
this.emit('fetch');
}
}
getRefreshInterval = () => {
return _.clone(this._refreshInterval);
}
/**
* Set timefilter refresh interval.
* @param {Object} refreshInterval
* @property {number} time.value
* @property {boolean} time.pause
*/
setRefreshInterval = (refreshInterval) => {
// Object.assign used for partially composed updates
const newRefreshInterval = Object.assign(this.getRefreshInterval(), refreshInterval);
if (areTimePickerValsDifferent(this.getRefreshInterval(), newRefreshInterval)) {
this._refreshInterval = newRefreshInterval;
this.emit('refreshIntervalUpdate');
if (!newRefreshInterval.pause && newRefreshInterval.value !== 0) {
this.emit('fetch');
}
}
}
toggleRefresh = () => {
this.setRefreshInterval({ pause: !this._refreshInterval.pause });
}
createFilter = (indexPattern, timeRange) => {
return getTime(indexPattern, timeRange ? timeRange : this._time, this.getForceNow());
}
getBounds = () => {
return this.calculateBounds(this._time);
}
getForceNow = () => {
const forceNow = parseQueryString().forceNow;
if (!forceNow) {
return;
}
const ticks = Date.parse(forceNow);
if (isNaN(ticks)) {
throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`);
}
return new Date(ticks);
}
calculateBounds = (timeRange) => {
return calculateBounds(timeRange, { forceNow: this.getForceNow() });
}
getActiveBounds = () => {
if (this.isTimeRangeSelectorEnabled) {
return this.getBounds();
}
}
/**
* Show the time bounds selector part of the time filter
*/
enableTimeRangeSelector = () => {
this.isTimeRangeSelectorEnabled = true;
this.emit('enabledUpdated');
}
/**
* Hide the time bounds selector part of the time filter
*/
disableTimeRangeSelector = () => {
this.isTimeRangeSelectorEnabled = false;
this.emit('enabledUpdated');
}
/**
* Show the auto refresh part of the time filter
*/
enableAutoRefreshSelector = () => {
this.isAutoRefreshSelectorEnabled = true;
this.emit('enabledUpdated');
}
/**
* Hide the auto refresh part of the time filter
*/
disableAutoRefreshSelector = () => {
this.isAutoRefreshSelectorEnabled = false;
this.emit('enabledUpdated');
}
}
export const timefilter = new Timefilter();
// TODO
// remove everything underneath once globalState is no longer an angular service
// and listener can be registered without angular.
function convertISO8601(stringTime) {
const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true);
return obj.isValid() ? obj : stringTime;
}
// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter
// and require it to be executed to properly function.
// This function is exposed for applications that do not use uiRoutes like APM
// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter
export const registerTimefilterWithGlobalState = _.once((globalState) => {
const uiSettings = chrome.getUiSettingsClient();
const timeDefaults = uiSettings.get('timepicker:timeDefaults');
const refreshIntervalDefaults = uiSettings.get('timepicker:refreshIntervalDefaults');
timefilter.setTime(_.defaults(globalState.time || {}, timeDefaults));
timefilter.setRefreshInterval(_.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults));
globalState.on('fetch_with_changes', () => {
// clone and default to {} in one
const newTime = _.defaults({}, globalState.time, timeDefaults);
const newRefreshInterval = _.defaults({}, globalState.refreshInterval, refreshIntervalDefaults);
if (newTime) {
if (newTime.to) newTime.to = convertISO8601(newTime.to);
if (newTime.from) newTime.from = convertISO8601(newTime.from);
}
timefilter.setTime(newTime);
timefilter.setRefreshInterval(newRefreshInterval);
});
});
uiRoutes
.addSetupWork(function (timefilter) {
return timefilter.init();
});
uiModules
.get('kibana')
.service('timefilter', function (Private, globalState, $rootScope, config, $location) {
const Events = Private(EventsProvider);
function convertISO8601(stringTime) {
const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true);
return obj.isValid() ? obj : stringTime;
}
createLegacyClass(Timefilter).inherits(Events);
function Timefilter() {
Timefilter.Super.call(this);
const self = this;
const diffTime = diffTimeFactory(self);
const diffInterval = diffIntervalFactory(self);
self.isTimeRangeSelectorEnabled = false;
self.isAutoRefreshSelectorEnabled = false;
self.init = _.once(function () {
const timeDefaults = config.get('timepicker:timeDefaults');
const refreshIntervalDefaults = config.get('timepicker:refreshIntervalDefaults');
// These can be date math strings or moments.
self.time = _.defaults(globalState.time || {}, timeDefaults);
self.refreshInterval = _.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults);
globalState.on('fetch_with_changes', function () {
// clone and default to {} in one
const newTime = _.defaults({}, globalState.time, timeDefaults);
const newRefreshInterval = _.defaults({}, globalState.refreshInterval, refreshIntervalDefaults);
if (newTime) {
if (newTime.to) newTime.to = convertISO8601(newTime.to);
if (newTime.from) newTime.from = convertISO8601(newTime.from);
}
self.time = newTime;
self.refreshInterval = newRefreshInterval;
});
});
$rootScope.$$timefilter = self;
$rootScope.$watchMulti([
'$$timefilter.time',
'$$timefilter.time.from',
'$$timefilter.time.to',
'$$timefilter.time.mode'
], diffTime);
$rootScope.$watchMulti([
'$$timefilter.refreshInterval',
'$$timefilter.refreshInterval.pause',
'$$timefilter.refreshInterval.value'
], diffInterval);
}
Timefilter.prototype.update = function () {
$rootScope.$apply();
};
Timefilter.prototype.getForceNow = function () {
const query = $location.search().forceNow;
if (!query) {
return;
}
const ticks = Date.parse(query);
if (isNaN(ticks)) {
throw new Error(`forceNow query parameter can't be parsed`);
}
return new Date(ticks);
};
Timefilter.prototype.get = function (indexPattern, timeRange) {
return getTime(indexPattern, timeRange ? timeRange : this.time, this.getForceNow());
};
Timefilter.prototype.calculateBounds = function (timeRange) {
return calculateBounds(timeRange, { forceNow: this.getForceNow() });
};
Timefilter.prototype.getBounds = function () {
return this.calculateBounds(this.time);
};
Timefilter.prototype.getActiveBounds = function () {
if (this.isTimeRangeSelectorEnabled) {
return this.getBounds();
}
};
/**
* Show the time bounds selector part of the time filter
*/
Timefilter.prototype.enableTimeRangeSelector = function () {
this.isTimeRangeSelectorEnabled = true;
};
/**
* Hide the time bounds selector part of the time filter
*/
Timefilter.prototype.disableTimeRangeSelector = function () {
this.isTimeRangeSelectorEnabled = false;
};
/**
* Show the auto refresh part of the time filter
*/
Timefilter.prototype.enableAutoRefreshSelector = function () {
this.isAutoRefreshSelectorEnabled = true;
};
/**
* Hide the auto refresh part of the time filter
*/
Timefilter.prototype.disableAutoRefreshSelector = function () {
this.isAutoRefreshSelectorEnabled = false;
};
return new Timefilter();
.addSetupWork((globalState) => {
return registerTimefilterWithGlobalState(globalState);
});

View file

@ -0,0 +1,265 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
jest.mock('ui/chrome',
() => ({
getUiSettingsClient: () => {
return {
get: (key) => {
switch(key) {
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now', mode: 'quick' };
case 'timepicker:refreshIntervalDefaults':
return { pause: false, value: 0 };
default:
throw new Error(`Unexpected config key: ${key}`);
}
}
};
},
}), { virtual: true });
jest.mock('ui/timefilter/lib/parse_querystring',
() => ({
parseQueryString: () => {
return {
// Can not access local variable from within a mock
forceNow: global.nowTime
};
},
}), { virtual: true });
import sinon from 'sinon';
import expect from 'expect.js';
import { timefilter } from './timefilter';
function stubNowTime(nowTime) {
global.nowTime = nowTime;
}
function clearNowTimeStub() {
delete global.nowTime;
}
describe('setTime', () => {
let update;
let fetch;
beforeEach(() => {
update = sinon.spy();
fetch = sinon.spy();
timefilter.setTime({
from: 0,
to: 1,
mode: 'absolute'
});
timefilter.on('timeUpdate', update);
timefilter.on('fetch', fetch);
});
test('should update time', () => {
timefilter.setTime({ from: 5, to: 10, mode: 'absolute' });
expect(timefilter.getTime()).to.eql({ from: 5, to: 10, mode: 'absolute' });
});
test('should allow partial updates to time', () => {
timefilter.setTime({ from: 5, to: 10 });
expect(timefilter.getTime()).to.eql({ from: 5, to: 10, mode: 'absolute' });
});
test('not emit anything if the time has not changed', () => {
timefilter.setTime({ from: 0, to: 1 });
expect(update.called).to.be(false);
expect(fetch.called).to.be(false);
});
test('emit update and fetch if the time has changed', () => {
timefilter.setTime({ from: 5, to: 10 });
expect(update.called).to.be(true);
expect(fetch.called).to.be(true);
});
});
describe('setRefreshInterval', () => {
let update;
let fetch;
beforeEach(() => {
update = sinon.spy();
fetch = sinon.spy();
timefilter.setRefreshInterval({
pause: false,
value: 0
});
timefilter.on('refreshIntervalUpdate', update);
timefilter.on('fetch', fetch);
});
test('should update refresh interval', () => {
timefilter.setRefreshInterval({ pause: true, value: 10 });
expect(timefilter.getRefreshInterval()).to.eql({ pause: true, value: 10 });
});
test('should allow partial updates to refresh interval', () => {
timefilter.setRefreshInterval({ value: 10 });
expect(timefilter.getRefreshInterval()).to.eql({ pause: false, value: 10 });
});
test('not emit anything if nothing has changed', () => {
timefilter.setRefreshInterval({ pause: false, value: 0 });
expect(update.called).to.be(false);
expect(fetch.called).to.be(false);
});
test('emit only an update when paused', () => {
timefilter.setRefreshInterval({ pause: true, value: 5000 });
expect(update.called).to.be(true);
expect(fetch.called).to.be(false);
});
test('emit update, not fetch, when switching to value: 0', () => {
timefilter.setRefreshInterval({ pause: false, value: 5000 });
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(true);
timefilter.setRefreshInterval({ pause: false, value: 0 });
expect(update.calledTwice).to.be(true);
expect(fetch.calledTwice).to.be(false);
});
test('should emit update, not fetch, when moving from unpaused to paused', () => {
timefilter.setRefreshInterval({ pause: false, value: 5000 });
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(true);
timefilter.setRefreshInterval({ pause: true, value: 5000 });
expect(update.calledTwice).to.be(true);
expect(fetch.calledTwice).to.be(false);
});
test('should emit update and fetch when unpaused', () => {
timefilter.setRefreshInterval({ pause: true, value: 5000 });
expect(update.calledOnce).to.be(true);
expect(fetch.calledOnce).to.be(false);
timefilter.setRefreshInterval({ pause: false, value: 5000 });
expect(update.calledTwice).to.be(true);
expect(fetch.calledOnce).to.be(true);
});
});
describe('isTimeRangeSelectorEnabled', () => {
let update;
beforeEach(() => {
update = sinon.spy();
timefilter.on('enabledUpdated', update);
});
test('should emit updated when disabled', () => {
timefilter.disableTimeRangeSelector();
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
expect(update.called).to.be(true);
});
test('should emit updated when enabled', () => {
timefilter.enableTimeRangeSelector();
expect(timefilter.isTimeRangeSelectorEnabled).to.be(true);
expect(update.called).to.be(true);
});
});
describe('isAutoRefreshSelectorEnabled', () => {
let update;
beforeEach(() => {
update = sinon.spy();
timefilter.on('enabledUpdated', update);
});
test('should emit updated when disabled', () => {
timefilter.disableAutoRefreshSelector();
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(false);
expect(update.called).to.be(true);
});
test('should emit updated when enabled', () => {
timefilter.enableAutoRefreshSelector();
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(true);
expect(update.called).to.be(true);
});
});
describe('calculateBounds', () => {
const fifteenMinutesInMilliseconds = 15 * 60 * 1000;
const clockNowTicks = new Date(2000, 1, 1, 0, 0, 0, 0).valueOf();
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(clockNowTicks);
});
afterEach(() => {
clock.restore();
clearNowTimeStub();
});
test('uses clock time by default', () => {
const timeRange = {
from: 'now-15m',
to: 'now'
};
stubNowTime(undefined);
const result = timefilter.calculateBounds(timeRange);
expect(result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds);
expect(result.max.valueOf()).to.eql(clockNowTicks);
});
test('uses forceNow string', () => {
const timeRange = {
from: 'now-15m',
to: 'now'
};
const forceNowString = '1999-01-01T00:00:00.000Z';
stubNowTime(forceNowString);
const result = timefilter.calculateBounds(timeRange);
const forceNowTicks = Date.parse(forceNowString);
expect(result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds);
expect(result.max.valueOf()).to.eql(forceNowTicks);
});
test(`throws Error if forceNow can't be parsed`, () => {
const timeRange = {
from: 'now-15m',
to: 'now'
};
stubNowTime('not_a_parsable_date');
expect(() => timefilter.calculateBounds(timeRange)).to.throwError();
});
});

View file

@ -17,27 +17,20 @@
* under the License.
*/
import moment from 'moment';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import $ from 'jquery';
import sinon from 'sinon';
import { timefilter } from 'ui/timefilter';
describe('kbnGlobalTimepicker', function () {
const sandbox = sinon.createSandbox();
let compile;
let scope;
beforeEach(() => {
ngMock.module('kibana');
ngMock.inject(($compile, $rootScope, timefilter) => {
ngMock.inject(($compile, $rootScope) => {
scope = $rootScope.$new();
compile = (timefilterStubProperties = {}) => {
Object.keys(timefilterStubProperties).forEach((key) => {
sandbox.stub(timefilter, key).value(timefilterStubProperties[key]);
});
compile = () => {
const $el = $('<kbn-global-timepicker></kbn-global-timepicker>');
$el.data('$kbnTopNavController', {}); // Mock the kbnTopNav
$compile($el)(scope);
@ -47,10 +40,6 @@ describe('kbnGlobalTimepicker', function () {
});
});
afterEach(() => {
sandbox.restore();
});
it('injects the timepicker into the DOM', () => {
const $el = compile();
expect($el.attr('data-test-subj')).to.be('globalTimepicker');
@ -59,17 +48,16 @@ describe('kbnGlobalTimepicker', function () {
it('sets data-shared-timefilter-* using the timefilter when auto-refresh selector is enabled', function () {
const minString = '2000-01-01T00:00:00Z';
const maxString = '2001-01-01T00:00:00Z';
const bounds = {
min: moment(minString),
max: moment(maxString),
};
const timefilter = {
isAutoRefreshSelectorEnabled: true,
isTimeRangeSelectorEnabled: false,
getBounds: () => bounds
};
const $el = compile(timefilter);
timefilter.enableAutoRefreshSelector();
timefilter.disableTimeRangeSelector();
timefilter.setTime({
from: minString,
to: maxString,
mode: 'absolute'
});
const $el = compile();
expect($el.attr('data-shared-timefilter-from')).to.eql(minString);
expect($el.attr('data-shared-timefilter-to')).to.eql(maxString);
@ -78,17 +66,16 @@ describe('kbnGlobalTimepicker', function () {
it('sets data-shared-timefilter-* using the timefilter when time range selector is enabled', function () {
const minString = '2000-01-01T00:00:00Z';
const maxString = '2001-01-01T00:00:00Z';
const bounds = {
min: moment(minString),
max: moment(maxString),
};
const timefilter = {
isAutoRefreshSelectorEnabled: false,
isTimeRangeSelectorEnabled: true,
getBounds: () => bounds
};
const $el = compile(timefilter);
timefilter.disableAutoRefreshSelector();
timefilter.enableTimeRangeSelector();
timefilter.setTime({
from: minString,
to: maxString,
mode: 'absolute'
});
const $el = compile();
expect($el.attr('data-shared-timefilter-from')).to.eql(minString);
expect($el.attr('data-shared-timefilter-to')).to.eql(maxString);
@ -97,17 +84,16 @@ describe('kbnGlobalTimepicker', function () {
it(`doesn't set data-shared-timefilter-* when auto-refresh and time range selectors are both disabled`, function () {
const minString = '2000-01-01T00:00:00Z';
const maxString = '2001-01-01T00:00:00Z';
const bounds = {
min: moment(minString),
max: moment(maxString),
};
const timefilter = {
isAutoRefreshSelectorEnabled: false,
isTimeRangeSelectorEnabled: false,
getBounds: () => bounds
};
const $el = compile(timefilter);
timefilter.disableAutoRefreshSelector();
timefilter.disableTimeRangeSelector();
timefilter.setTime({
from: minString,
to: maxString,
mode: 'absolute'
});
const $el = compile();
expect($el.attr('data-shared-timefilter-from')).to.eql('');
expect($el.attr('data-shared-timefilter-to')).to.eql('');

View file

@ -1,5 +1,5 @@
<div
ng-show="timefilter.isAutoRefreshSelectorEnabled || timefilter.isTimeRangeSelectorEnabled"
ng-show="timefilterValues.isAutoRefreshSelectorEnabled || timefilterValues.isTimeRangeSelectorEnabled"
data-shared-timefilter-from="{{ getSharedTimeFilterFromDate() }}"
data-shared-timefilter-to="{{ getSharedTimeFilterToDate() }}"
class="kuiLocalMenu"
@ -7,39 +7,39 @@
>
<button
class="kuiLocalMenuItem"
aria-label="{{ timefilter.refreshInterval.pause ? 'Resume refreshing data' : 'Pause refreshing data' }}"
aria-label="{{ timefilterValues.refreshInterval.pause ? 'Resume refreshing data' : 'Pause refreshing data' }}"
ng-click="toggleRefresh()"
ng-show="timefilter.isAutoRefreshSelectorEnabled && (timefilter.refreshInterval.value > 0)"
ng-show="timefilterValues.isAutoRefreshSelectorEnabled && (timefilterValues.refreshInterval.value > 0)"
data-test-subj="globalTimepickerAutoRefreshButton"
data-test-subj-state="{{ timefilter.refreshInterval.pause ? 'inactive' : 'active' }}"
data-test-subj-state="{{ timefilterValues.refreshInterval.pause ? 'inactive' : 'active' }}"
>
<span
class="kuiIcon"
aria-hidden="true"
ng-class="timefilter.refreshInterval.pause ? 'fa-play' : 'fa-pause'"
ng-class="timefilterValues.refreshInterval.pause ? 'fa-play' : 'fa-pause'"
></span>
</button>
<button
class="kuiLocalMenuItem navbar-timepicker-auto-refresh-desc"
ng-class="{'kuiLocalMenuItem-isSelected': kbnTopNav.isCurrent('interval') }"
ng-show="timefilter.isAutoRefreshSelectorEnabled"
ng-show="timefilterValues.isAutoRefreshSelectorEnabled"
ng-click="kbnTopNav.toggle('interval')"
>
<span ng-show="timefilter.refreshInterval.value === 0">
<span ng-show="timefilterValues.refreshInterval.value === 0">
<span aria-hidden="true" class="kuiIcon fa-repeat"></span> Auto-refresh
</span>
<span
ng-show="timefilter.refreshInterval.value > 0"
aria-label="{{ 'Data will refresh every ' + timefilter.refreshInterval.display }}"
ng-show="timefilterValues.refreshInterval.value > 0"
aria-label="{{ 'Data will refresh every ' + timefilterValues.refreshInterval.display }}"
>
{{ timefilter.refreshInterval.display }}
{{ timefilterValues.refreshInterval.display }}
</span>
</button>
<button
ng-show="timefilter.isTimeRangeSelectorEnabled"
ng-show="timefilterValues.isTimeRangeSelectorEnabled"
class="kuiLocalMenuItem"
ng-click="back()"
aria-label="Move backward in time"
@ -52,7 +52,7 @@
</button>
<button
ng-show="timefilter.isTimeRangeSelectorEnabled"
ng-show="timefilterValues.isTimeRangeSelectorEnabled"
data-test-subj="globalTimepickerButton"
class="kuiLocalMenuItem navbar-timepicker-time-desc"
ng-class="{'kuiLocalMenuItem-isSelected': kbnTopNav.isCurrent('filter')}"
@ -62,14 +62,14 @@
>
<span aria-hidden="true" class="kuiIcon fa-clock-o"></span>
<pretty-duration
from="timefilter.time.from"
to="timefilter.time.to"
from="timefilterValues.time.from"
to="timefilterValues.time.to"
data-test-subj="globalTimepickerRange"
></pretty-duration>
</button>
<button
ng-show="timefilter.isTimeRangeSelectorEnabled"
ng-show="timefilterValues.isTimeRangeSelectorEnabled"
class="kuiLocalMenuItem"
ng-click="forward()"
aria-label="Move forward in time"

View file

@ -18,50 +18,68 @@
*/
import { uiModules } from '../modules';
import { once, clone } from 'lodash';
import toggleHtml from './kbn_global_timepicker.html';
import { timeNavigation } from './time_navigation';
import { timefilter } from 'ui/timefilter';
uiModules
.get('kibana')
.directive('kbnGlobalTimepicker', (timefilter, globalState, $rootScope) => {
const listenForUpdates = once($scope => {
$scope.$listen(timefilter, 'update', () => {
globalState.time = clone(timefilter.time);
globalState.refreshInterval = clone(timefilter.refreshInterval);
.directive('kbnGlobalTimepicker', (globalState) => {
const listenForUpdates = ($scope) => {
$scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', () => {
globalState.refreshInterval = timefilter.getRefreshInterval();
globalState.time = timefilter.getTime();
globalState.save();
setTimefilterValues($scope);
});
});
$scope.$listenAndDigestAsync(timefilter, 'timeUpdate', () => {
globalState.refreshInterval = timefilter.getRefreshInterval();
globalState.time = timefilter.getTime();
globalState.save();
setTimefilterValues($scope);
});
$scope.$listenAndDigestAsync(timefilter, 'enabledUpdated', () => {
setTimefilterValues($scope);
});
};
function setTimefilterValues($scope) {
$scope.timefilterValues = {
refreshInterval: timefilter.getRefreshInterval(),
time: timefilter.getTime(),
isAutoRefreshSelectorEnabled: timefilter.isAutoRefreshSelectorEnabled,
isTimeRangeSelectorEnabled: timefilter.isTimeRangeSelectorEnabled,
};
}
return {
template: toggleHtml,
replace: true,
require: '^kbnTopNav',
link: ($scope, element, attributes, kbnTopNav) => {
listenForUpdates($rootScope);
listenForUpdates($scope);
$rootScope.timefilter = timefilter;
$rootScope.toggleRefresh = () => {
timefilter.refreshInterval.pause = !timefilter.refreshInterval.pause;
setTimefilterValues($scope);
$scope.toggleRefresh = () => {
timefilter.toggleRefresh();
};
$scope.forward = function () {
timefilter.time = timeNavigation.stepForward(timefilter.getBounds());
timefilter.setTime(timeNavigation.stepForward(timefilter.getBounds()));
};
$scope.back = function () {
timefilter.time = timeNavigation.stepBackward(timefilter.getBounds());
timefilter.setTime(timeNavigation.stepBackward(timefilter.getBounds()));
};
$scope.updateFilter = function (from, to) {
timefilter.time.from = from;
timefilter.time.to = to;
$scope.updateFilter = function (from, to, mode) {
timefilter.setTime({ from, to, mode });
kbnTopNav.close('filter');
};
$scope.updateInterval = function (interval) {
timefilter.refreshInterval = interval;
timefilter.setRefreshInterval(interval);
kbnTopNav.close('interval');
};

View file

@ -165,7 +165,7 @@ module.directive('kbnTimepicker', function (refreshIntervals) {
};
$scope.setQuick = function (from, to) {
$scope.onFilterSelect({ from, to });
$scope.onFilterSelect({ from, to, mode: TIME_MODES.QUICK });
};
$scope.setToNow = function (key) {
@ -203,7 +203,8 @@ module.directive('kbnTimepicker', function (refreshIntervals) {
$scope.applyRelative = function () {
$scope.onFilterSelect({
from: getRelativeString('from'),
to: getRelativeString('to')
to: getRelativeString('to'),
mode: TIME_MODES.RELATIVE,
});
};
@ -224,7 +225,8 @@ module.directive('kbnTimepicker', function (refreshIntervals) {
$scope.applyAbsolute = function () {
$scope.onFilterSelect({
from: moment($scope.absolute.from),
to: moment($scope.absolute.to)
to: moment($scope.absolute.to),
mode: TIME_MODES.ABSOLUTE,
});
};

View file

@ -1,254 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import expect from 'expect.js';
import moment from 'moment';
import ngMock from 'ng_mock';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { UtilsBrushEventProvider } from '../brush_event';
describe('brushEvent', function () {
const DAY_IN_MS = 24 * 60 * 60 * 1000;
const JAN_01_2014 = 1388559600000;
let brushEventFn;
let timefilter;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector, _timefilter_) {
brushEventFn = Private(UtilsBrushEventProvider);
timefilter = _timefilter_;
}));
it('is a function that returns a function', function () {
expect(brushEventFn).to.be.a(Function);
expect(brushEventFn({})).to.be.a(Function);
});
describe('returned function', function () {
let $state;
let brushEvent;
const baseState = {
filters: [],
};
const baseEvent = {
data: {
fieldFormatter: _.constant({}),
},
};
beforeEach(ngMock.inject(function (Private) {
baseEvent.data.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
$state = _.cloneDeep(baseState);
brushEvent = brushEventFn($state);
}));
it('should be a function', function () {
expect(brushEvent).to.be.a(Function);
});
it('ignores event when data.xAxisField not provided', function () {
const event = _.cloneDeep(baseEvent);
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});
describe('handles an event when the x-axis field is a date field', function () {
describe('date field is index pattern timefield', function () {
let dateEvent;
const dateField = {
name: 'time',
type: 'date'
};
beforeEach(ngMock.inject(function () {
dateEvent = _.cloneDeep(baseEvent);
dateEvent.data.xAxisField = dateField;
}));
it('by ignoring the event when range spans zero time', function () {
const event = _.cloneDeep(dateEvent);
event.range = [JAN_01_2014, JAN_01_2014];
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});
it('by updating the timefilter', function () {
const event = _.cloneDeep(dateEvent);
event.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
brushEvent(event);
expect(timefilter.time.mode).to.be('absolute');
expect(moment.isMoment(timefilter.time.from))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.time.from.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-01');
expect(moment.isMoment(timefilter.time.to))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.time.to.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-02');
});
});
describe('date field is not index pattern timefield', function () {
let dateEvent;
const dateField = {
name: 'anotherTimeField',
type: 'date'
};
beforeEach(ngMock.inject(function () {
dateEvent = _.cloneDeep(baseEvent);
dateEvent.data.xAxisField = dateField;
}));
it('creates a new range filter', function () {
const event = _.cloneDeep(dateEvent);
const rangeBegin = JAN_01_2014;
const rangeEnd = rangeBegin + DAY_IN_MS;
event.range = [rangeBegin, rangeEnd];
brushEvent(event);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.anotherTimeField.gte)
.to.equal(rangeBegin);
expect($state.$newFilters[0].range.anotherTimeField.lt)
.to.equal(rangeEnd);
expect($state.$newFilters[0].range.anotherTimeField).to.have.property('format');
expect($state.$newFilters[0].range.anotherTimeField.format)
.to.equal('epoch_millis');
});
it('converts Date fields to milliseconds', function () {
const event = _.cloneDeep(dateEvent);
const rangeBeginMs = JAN_01_2014;
const rangeEndMs = rangeBeginMs + DAY_IN_MS;
const rangeBegin = new Date(rangeBeginMs);
const rangeEnd = new Date(rangeEndMs);
event.range = [rangeBegin, rangeEnd];
brushEvent(event);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.anotherTimeField.gte)
.to.equal(rangeBeginMs);
expect($state.$newFilters[0].range.anotherTimeField.lt)
.to.equal(rangeEndMs);
});
});
});
describe('handles an event when the x-axis field is a number', function () {
let numberEvent;
const numberField = {
name: 'numberField',
type: 'number'
};
beforeEach(ngMock.inject(function () {
numberEvent = _.cloneDeep(baseEvent);
numberEvent.data.xAxisField = numberField;
}));
it('by ignoring the event when range does not span at least 2 values', function () {
const event = _.cloneDeep(numberEvent);
event.range = [1];
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});
it('by creating a new filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [1, 2, 3, 4];
brushEvent(event);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.gte)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.lt)
.to.equal(4);
expect($state.$newFilters[0].range.numberField).not.to.have.property('format');
});
it('by updating the existing range filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [3, 7];
$state.filters.push({
meta: {
key: 'numberField'
},
range: { gte: 1, lt: 4 }
});
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].range.numberField.gte)
.to.equal(3);
expect($state.filters[0].range.numberField.lt)
.to.equal(7);
});
it('by updating the existing scripted filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [3, 7];
$state.filters.push({
meta: {
key: 'numberField'
},
script: {
script: {
params: {
gte: 1,
lt: 4
}
}
}
});
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].script.script.params.gte)
.to.equal(3);
expect($state.filters[0].script.script.params.lt)
.to.equal(7);
});
});
});
});

View file

@ -0,0 +1,256 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
jest.mock('ui/chrome',
() => ({
getUiSettingsClient: () => {
return {
get: (key) => {
switch(key) {
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now', mode: 'quick' };
case 'timepicker:refreshIntervalDefaults':
return { display: 'Off', pause: false, value: 0 };
default:
throw new Error(`Unexpected config key: ${key}`);
}
}
};
},
}), { virtual: true });
import _ from 'lodash';
import expect from 'expect.js';
import moment from 'moment';
import { onBrushEvent } from '../brush_event';
import { timefilter } from 'ui/timefilter';
describe('brushEvent', () => {
const DAY_IN_MS = 24 * 60 * 60 * 1000;
const JAN_01_2014 = 1388559600000;
let $state;
const baseState = {
filters: [],
};
const baseEvent = {
data: {
fieldFormatter: _.constant({}),
},
};
beforeEach(() => {
baseEvent.data.indexPattern = {
id: 'logstash-*',
timeFieldName: 'time'
};
$state = _.cloneDeep(baseState);
});
test('should be a function', () => {
expect(onBrushEvent).to.be.a(Function);
});
test('ignores event when data.xAxisField not provided', () => {
const event = _.cloneDeep(baseEvent);
onBrushEvent(event, $state);
expect($state)
.not.have.property('$newFilters');
});
describe('handles an event when the x-axis field is a date field', () => {
describe('date field is index pattern timefield', () => {
let dateEvent;
const dateField = {
name: 'time',
type: 'date'
};
beforeEach(() => {
dateEvent = _.cloneDeep(baseEvent);
dateEvent.data.xAxisField = dateField;
});
test('by ignoring the event when range spans zero time', () => {
const event = _.cloneDeep(dateEvent);
event.range = [JAN_01_2014, JAN_01_2014];
onBrushEvent(event, $state);
expect($state)
.not.have.property('$newFilters');
});
test('by updating the timefilter', () => {
const event = _.cloneDeep(dateEvent);
event.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
onBrushEvent(event, $state);
expect(timefilter.getTime().mode).to.be('absolute');
expect(moment.isMoment(timefilter.getTime().from))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.getTime().from.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-01');
expect(moment.isMoment(timefilter.getTime().to))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.getTime().to.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-02');
});
});
describe('date field is not index pattern timefield', () => {
let dateEvent;
const dateField = {
name: 'anotherTimeField',
type: 'date'
};
beforeEach(() => {
dateEvent = _.cloneDeep(baseEvent);
dateEvent.data.xAxisField = dateField;
});
test('creates a new range filter', () => {
const event = _.cloneDeep(dateEvent);
const rangeBegin = JAN_01_2014;
const rangeEnd = rangeBegin + DAY_IN_MS;
event.range = [rangeBegin, rangeEnd];
onBrushEvent(event, $state);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.anotherTimeField.gte)
.to.equal(rangeBegin);
expect($state.$newFilters[0].range.anotherTimeField.lt)
.to.equal(rangeEnd);
expect($state.$newFilters[0].range.anotherTimeField).to.have.property('format');
expect($state.$newFilters[0].range.anotherTimeField.format)
.to.equal('epoch_millis');
});
test('converts Date fields to milliseconds', () => {
const event = _.cloneDeep(dateEvent);
const rangeBeginMs = JAN_01_2014;
const rangeEndMs = rangeBeginMs + DAY_IN_MS;
const rangeBegin = new Date(rangeBeginMs);
const rangeEnd = new Date(rangeEndMs);
event.range = [rangeBegin, rangeEnd];
onBrushEvent(event, $state);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.anotherTimeField.gte)
.to.equal(rangeBeginMs);
expect($state.$newFilters[0].range.anotherTimeField.lt)
.to.equal(rangeEndMs);
});
});
});
describe('handles an event when the x-axis field is a number', () => {
let numberEvent;
const numberField = {
name: 'numberField',
type: 'number'
};
beforeEach(() => {
numberEvent = _.cloneDeep(baseEvent);
numberEvent.data.xAxisField = numberField;
});
test('by ignoring the event when range does not span at least 2 values', () => {
const event = _.cloneDeep(numberEvent);
event.range = [1];
onBrushEvent(event, $state);
expect($state).not.have.property('$newFilters');
});
test('by creating a new filter', () => {
const event = _.cloneDeep(numberEvent);
event.range = [1, 2, 3, 4];
onBrushEvent(event, $state);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.gte)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.lt)
.to.equal(4);
expect($state.$newFilters[0].range.numberField).not.to.have.property('format');
});
test('by updating the existing range filter', () => {
const event = _.cloneDeep(numberEvent);
event.range = [3, 7];
$state.filters.push({
meta: {
key: 'numberField'
},
range: { gte: 1, lt: 4 }
});
onBrushEvent(event, $state);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].range.numberField.gte)
.to.equal(3);
expect($state.filters[0].range.numberField.lt)
.to.equal(7);
});
test('by updating the existing scripted filter', () => {
const event = _.cloneDeep(numberEvent);
event.range = [3, 7];
$state.filters.push({
meta: {
key: 'numberField'
},
script: {
script: {
params: {
gte: 1,
lt: 4
}
}
}
});
onBrushEvent(event, $state);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].script.script.params.gte)
.to.equal(3);
expect($state.filters[0].script.script.params.lt)
.to.equal(7);
});
});
});

View file

@ -20,73 +20,72 @@
import _ from 'lodash';
import moment from 'moment';
import { buildRangeFilter } from '../filter_manager/lib/range';
import { timefilter } from 'ui/timefilter';
export function UtilsBrushEventProvider(timefilter) {
return $state => {
return event => {
if (!event.data.xAxisField) {
return;
}
export function onBrushEvent(event, $state) {
if (!event.data.xAxisField) {
return;
}
const isDate = event.data.xAxisField.type === 'date';
const isNumber = event.data.xAxisField.type === 'number';
const isDate = event.data.xAxisField.type === 'date';
const isNumber = event.data.xAxisField.type === 'number';
if (isDate &&
event.data.xAxisField.name === event.data.indexPattern.timeFieldName) {
setTimefilter();
} else if (isDate || isNumber) {
setRange();
}
if (isDate &&
event.data.xAxisField.name === event.data.indexPattern.timeFieldName) {
setTimefilter();
} else if (isDate || isNumber) {
setRange();
}
function setTimefilter() {
const from = moment(event.range[0]);
const to = moment(event.range[1]);
function setTimefilter() {
const from = moment(event.range[0]);
const to = moment(event.range[1]);
if (to - from === 0) return;
if (to - from === 0) return;
timefilter.time.from = from;
timefilter.time.to = to;
timefilter.time.mode = 'absolute';
}
timefilter.setTime({
from,
to,
mode: 'absolute'
});
}
function setRange() {
if (event.range.length <= 1) return;
function setRange() {
if (event.range.length <= 1) return;
const existingFilter = $state.filters.find(filter => (
filter.meta && filter.meta.key === event.data.xAxisField.name
));
const existingFilter = $state.filters.find(filter => (
filter.meta && filter.meta.key === event.data.xAxisField.name
));
const min = event.range[0];
const max = event.range[event.range.length - 1];
let range;
if (isDate) {
range = {
gte: moment(min).valueOf(),
lt: moment(max).valueOf(),
format: 'epoch_millis'
};
} else {
range = {
gte: min,
lt: max
};
}
const min = event.range[0];
const max = event.range[event.range.length - 1];
let range;
if (isDate) {
range = {
gte: moment(min).valueOf(),
lt: moment(max).valueOf(),
format: 'epoch_millis'
};
} else {
range = {
gte: min,
lt: max
};
}
if (_.has(existingFilter, 'range')) {
existingFilter.range[event.data.xAxisField.name] = range;
} else if (_.has(existingFilter, 'script.script.params.gte')
&& _.has(existingFilter, 'script.script.params.lt')) {
existingFilter.script.script.params.gte = min;
existingFilter.script.script.params.lt = max;
} else {
const newFilter = buildRangeFilter(
event.data.xAxisField,
range,
event.data.indexPattern,
event.data.xAxisFormatter);
$state.$newFilters = [newFilter];
}
}
};
};
if (_.has(existingFilter, 'range')) {
existingFilter.range[event.data.xAxisField.name] = range;
} else if (_.has(existingFilter, 'script.script.params.gte')
&& _.has(existingFilter, 'script.script.params.lt')) {
existingFilter.script.script.params.gte = min;
existingFilter.script.script.params.lt = max;
} else {
const newFilter = buildRangeFilter(
event.data.xAxisField,
range,
event.data.indexPattern,
event.data.xAxisFormatter);
$state.$newFilters = [newFilter];
}
}
}

View file

@ -21,12 +21,13 @@ import _ from 'lodash';
import { SearchSourceProvider } from '../../courier/data_source/search_source';
import { VisRequestHandlersRegistryProvider } from '../../registry/vis_request_handlers';
import { calculateObjectHash } from '../lib/calculate_object_hash';
import { timefilter } from 'ui/timefilter';
import { getRequestInspectorStats, getResponseInspectorStats } from '../../courier/utils/courier_inspector_utils';
import { tabifyAggResponse } from '../../agg_response/tabify/tabify';
import { FormattedData } from '../../inspector/adapters';
const CourierRequestHandlerProvider = function (Private, courier, timefilter) {
const CourierRequestHandlerProvider = function (Private, courier) {
const SearchSource = Private(SearchSourceProvider);
/**
@ -105,7 +106,7 @@ const CourierRequestHandlerProvider = function (Private, courier, timefilter) {
});
timeFilterSearchSource.set('filter', () => {
return timefilter.get(searchSource.get('index'), timeRange);
return timefilter.createFilter(searchSource.get('index'), timeRange);
});
requestSearchSource.set('filter', filters);

View file

@ -32,13 +32,14 @@ import _ from 'lodash';
import { VisTypesRegistryProvider } from '../registry/vis_types';
import { AggConfigs } from './agg_configs';
import { PersistedState } from '../persisted_state';
import { UtilsBrushEventProvider } from '../utils/brush_event';
import { onBrushEvent } from '../utils/brush_event';
import { FilterBarQueryFilterProvider } from '../filter_bar/query_filter';
import { FilterBarClickHandlerProvider } from '../filter_bar/filter_bar_click_handler';
import { updateVisualizationConfig } from './vis_update';
import { queryManagerFactory } from '../query_manager';
import { SearchSourceProvider } from '../courier/data_source/search_source';
import { SavedObjectsClientProvider } from '../saved_objects';
import { timefilter } from 'ui/timefilter';
import { Inspector } from '../inspector';
import { RequestAdapter, DataAdapter } from '../inspector/adapters';
@ -55,9 +56,8 @@ const getTerms = (table, columnIndex, rowIndex) => {
}))];
};
export function VisProvider(Private, Promise, indexPatterns, timefilter, getAppState) {
export function VisProvider(Private, Promise, indexPatterns, getAppState) {
const visTypes = Private(VisTypesRegistryProvider);
const brushEvent = Private(UtilsBrushEventProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const filterBarClickHandler = Private(FilterBarClickHandlerProvider);
const SearchSource = Private(SearchSourceProvider);
@ -108,8 +108,7 @@ export function VisProvider(Private, Promise, indexPatterns, timefilter, getAppS
}
queryFilter.addFilters(filter);
}, brush: (event) => {
const appState = getAppState();
brushEvent(appState)(event);
onBrushEvent(event, getAppState());
}
},
createInheritedSearchSource: (parentSearchSource) => {

View file

@ -34,7 +34,7 @@ import {
uiModules
.get('kibana/directive', ['ngSanitize'])
.directive('visualize', function ($timeout, Notifier, Private, timefilter, getAppState, Promise) {
.directive('visualize', function ($timeout, Notifier, Private, getAppState, Promise) {
const notify = new Notifier({ location: 'Visualize' });
const requestHandlers = Private(VisRequestHandlersRegistryProvider);
const responseHandlers = Private(VisResponseHandlersRegistryProvider);

View file

@ -4,6 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
jest.mock(
'ui/chrome',
() => ({
getUiSettingsClient: () => {
return {
get: key => {
switch (key) {
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now', mode: 'quick' };
case 'timepicker:refreshIntervalDefaults':
return { display: 'Off', pause: false, value: 0 };
default:
throw new Error(`Unexpected config key: ${key}`);
}
}
};
}
}),
{ virtual: true }
);
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';

View file

@ -4,6 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
jest.mock(
'ui/chrome',
() => ({
getUiSettingsClient: () => {
return {
get: key => {
switch (key) {
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now', mode: 'quick' };
case 'timepicker:refreshIntervalDefaults':
return { display: 'Off', pause: false, value: 0 };
default:
throw new Error(`Unexpected config key: ${key}`);
}
}
};
}
}),
{ virtual: true }
);
import React from 'react';
import { shallow } from 'enzyme';
import TransactionOverview from '../view';

View file

@ -7,10 +7,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import CustomPlot from '../CustomPlot';
import { getTimefilter } from '../../../../utils/timepicker';
import { asMillis, tpmUnit, asInteger } from '../../../../utils/formatters';
import styled from 'styled-components';
import { units, unit, px } from '../../../../style/variables';
import { timefilter } from 'ui/timefilter';
import moment from 'moment';
const ChartsWrapper = styled.div`
display: flex;
@ -42,9 +43,12 @@ export class Charts extends Component {
onHover = hoverIndex => this.setState({ hoverIndex });
onMouseLeave = () => this.setState({ hoverIndex: null });
onSelectionEnd = selection => {
const timefilter = getTimefilter();
this.setState({ hoverIndex: null });
timefilter.setTime(selection.start, selection.end);
timefilter.setTime({
from: moment(selection.start).toISOString(),
to: moment(selection.end).toISOString(),
mode: 'absolute'
});
};
getResponseTimeTickFormatter = t => {

View file

@ -4,13 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment';
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import 'ui/autoload/all';
import { updateTimePicker } from '../../store/urlParams';
import { timefilter, registerTimefilterWithGlobalState } from 'ui/timefilter';
let globalTimefilter;
let currentInterval;
// hack to wait for angular template to be ready
@ -37,7 +36,7 @@ export function initTimepicker(history, dispatch, callback) {
uiModules
.get('app/apm', [])
.controller('TimePickerController', ($scope, timefilter, globalState) => {
.controller('TimePickerController', ($scope, globalState) => {
// Add APM feedback menu
// TODO: move this somewhere else
$scope.topNavMenu = [];
@ -49,55 +48,41 @@ export function initTimepicker(history, dispatch, callback) {
});
history.listen(() => {
updateRefreshRate(dispatch, timefilter);
updateRefreshRate(dispatch);
globalState.fetch();
});
timefilter.setTime = (from, to) => {
timefilter.time.from = moment(from).toISOString();
timefilter.time.to = moment(to).toISOString();
$scope.$apply();
};
timefilter.enableTimeRangeSelector();
timefilter.enableAutoRefreshSelector();
timefilter.init();
updateRefreshRate(dispatch, timefilter);
updateRefreshRate(dispatch);
timefilter.on('update', () => dispatch(getAction(timefilter)));
$scope.$listen(timefilter, 'timeUpdate', () =>
dispatch(updateTimePickerAction())
);
// hack to access timefilter outside Angular
globalTimefilter = timefilter;
registerTimefilterWithGlobalState(globalState);
Promise.all([waitForAngularReady]).then(callback);
});
}
function getAction(timefilter) {
function updateTimePickerAction() {
return updateTimePicker({
min: timefilter.getBounds().min.toISOString(),
max: timefilter.getBounds().max.toISOString()
});
}
function updateRefreshRate(dispatch, timefilter) {
const refreshInterval = timefilter.refreshInterval.value;
function updateRefreshRate(dispatch) {
const refreshInterval = timefilter.getRefreshInterval().value;
if (currentInterval) {
clearInterval(currentInterval);
}
if (refreshInterval > 0 && !timefilter.refreshInterval.pause) {
if (refreshInterval > 0 && !timefilter.getRefreshInterval().pause) {
currentInterval = setInterval(
() => dispatch(getAction(timefilter)),
() => dispatch(updateTimePickerAction()),
refreshInterval
);
}
}
export function getTimefilter() {
if (!globalTimefilter) {
throw new Error(
'Timepicker must be initialized before calling getTimefilter'
);
}
return globalTimefilter;
}

View file

@ -8,12 +8,12 @@
import 'ngreact';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
const module = uiModules.get('apps/ml', ['react']);
import { AnomaliesTable } from './anomalies_table';
module.directive('mlAnomaliesTable', function ($injector) {
const timefilter = $injector.get('timefilter');
const reactDirective = $injector.get('reactDirective');
return reactDirective(

View file

@ -18,7 +18,6 @@ import {
EuiPopover
} from '@elastic/eui';
import 'ui/timefilter';
import chrome from 'ui/chrome';
import { toastNotifications } from 'ui/notify';

View file

@ -21,10 +21,10 @@ import { calculateTextWidth } from 'plugins/ml/util/string_utils';
import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
const module = uiModules.get('apps/ml');
module.directive('mlDocumentCountChart', function (
timefilter,
Private,
mlChartTooltipService) {

View file

@ -11,7 +11,9 @@ import moment from 'moment';
import { ml } from 'plugins/ml/services/ml_api_service';
export function FullTimeRangeSelectorServiceProvider(timefilter, Notifier) {
import { timefilter } from 'ui/timefilter';
export function FullTimeRangeSelectorServiceProvider(Notifier) {
const notify = new Notifier();
function setFullTimeRange(indexPattern, query) {
@ -22,8 +24,10 @@ export function FullTimeRangeSelectorServiceProvider(timefilter, Notifier) {
query
})
.then((resp) => {
timefilter.time.from = moment(resp.start.epoch).toISOString();
timefilter.time.to = moment(resp.end.epoch).toISOString();
timefilter.setTime({
from: moment(resp.start.epoch).toISOString(),
to: moment(resp.end.epoch).toISOString()
});
})
.catch((resp) => {
notify.error(resp);

View file

@ -21,10 +21,11 @@ import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
import { mlJobService } from 'plugins/ml/services/job_service';
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
import { timefilter } from 'ui/timefilter';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.directive('mlJobSelectList', function (Private, timefilter) {
module.directive('mlJobSelectList', function (Private) {
return {
restrict: 'AE',
replace: true,
@ -254,8 +255,10 @@ module.directive('mlJobSelectList', function (Private, timefilter) {
if (times.length) {
const min = _.min(times);
const max = _.max(times);
timefilter.time.from = moment(min).toISOString();
timefilter.time.to = moment(max).toISOString();
timefilter.setTime({
from: moment(min).toISOString(),
to: moment(max).toISOString()
});
}
}
mlJobSelectService.jobSelectListState.applyTimeRange = $scope.applyTimeRange;
@ -360,8 +363,10 @@ module.directive('mlJobSelectList', function (Private, timefilter) {
}
$scope.useTimeRange = function (job) {
timefilter.time.from = job.timeRange.fromMoment.toISOString();
timefilter.time.to = job.timeRange.toMoment.toISOString();
timefilter.setTime({
from: job.timeRange.fromMoment.toISOString(),
to: job.timeRange.toMoment.toISOString()
});
};
},
link: function (scope, element, attrs) {

View file

@ -49,6 +49,7 @@ uiRoutes
}
});
import { timefilter } from 'ui/timefilter';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
@ -59,7 +60,6 @@ module
$timeout,
$window,
Private,
timefilter,
AppState) {
timefilter.enableTimeRangeSelector();
@ -143,7 +143,7 @@ module
// Refresh the data when the time range is altered.
$scope.$listen(timefilter, 'fetch', function () {
$scope.$listenAndDigestAsync(timefilter, 'fetch', function () {
$scope.earliest = timefilter.getActiveBounds().min.valueOf();
$scope.latest = timefilter.getActiveBounds().max.valueOf();
loadOverallStats();

View file

@ -19,13 +19,13 @@ import moment from 'moment';
import rison from 'rison-node';
import chrome from 'ui/chrome';
import 'ui/timefilter';
import { timefilter } from 'ui/timefilter';
import template from './explorer_charts_container.html';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.directive('mlExplorerChartsContainer', function ($window, timefilter) {
module.directive('mlExplorerChartsContainer', function ($window) {
function link(scope, element) {
// Create a div for the tooltip.

View file

@ -39,6 +39,7 @@ import { mlJobService } from 'plugins/ml/services/job_service';
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
import { isTimeSeriesViewDetector } from 'plugins/ml/../common/util/job_utils';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/explorer/?', {
@ -59,7 +60,6 @@ module.controller('MlExplorerController', function (
$timeout,
AppState,
Private,
timefilter,
mlCheckboxShowChartsService,
mlExplorerDashboardService,
mlSelectLimitService,
@ -246,7 +246,7 @@ module.controller('MlExplorerController', function (
};
// Refresh all the data when the time range is altered.
$scope.$listen(timefilter, 'fetch', () => {
$scope.$listenAndDigestAsync(timefilter, 'fetch', () => {
loadOverallData();
clearSelectedAnomalies();
});

View file

@ -18,6 +18,7 @@ import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
import { toLocaleString, mlEscape } from 'plugins/ml/util/string_utils';
import { copyTextToClipboard } from 'plugins/ml/util/clipboard_utils';
import { timefilter } from 'ui/timefilter';
import uiRoutes from 'ui/routes';
import { checkLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege, checkPermission, createPermissionFailureMessage } from 'plugins/ml/privilege/check_privilege';
@ -60,7 +61,6 @@ module.controller('MlJobsList',
$timeout,
$compile,
$modal,
timefilter,
kbnUrl,
Private,
mlDatafeedService) {

View file

@ -11,6 +11,7 @@ import angular from 'angular';
import 'ace';
import { parseInterval } from 'ui/utils/parse_interval';
import { timefilter } from 'ui/timefilter';
import uiRoutes from 'ui/routes';
import { checkLicense } from 'plugins/ml/license/check_license';
@ -69,7 +70,6 @@ module.controller('MlNewJob',
$route,
$location,
$modal,
timefilter,
mlDatafeedService,
mlConfirmModalService) {

View file

@ -15,7 +15,6 @@ import $ from 'jquery';
import d3 from 'd3';
import angular from 'angular';
import moment from 'moment';
import 'ui/timefilter';
import { TimeBuckets } from 'ui/time_buckets';
import { numTicksForDateFormat } from 'plugins/ml/util/chart_utils';

View file

@ -13,8 +13,9 @@ import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
import { calculateTextWidth } from 'plugins/ml/util/string_utils';
import { mlResultsService } from 'plugins/ml/services/results_service';
import { mlSimpleJobSearchService } from 'plugins/ml/jobs/new_job/simple/components/utils/search_service';
import { timefilter } from 'ui/timefilter';
export function ChartDataUtilsProvider(Private, timefilter) {
export function ChartDataUtilsProvider(Private) {
const TimeBuckets = Private(IntervalHelperProvider);
function loadDocCountData(formConfig, chartData) {

View file

@ -43,6 +43,7 @@ import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar
import { initPromise } from 'plugins/ml/util/promise';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './create_job.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job/simple/multi_metric', {
@ -65,7 +66,6 @@ module
.controller('MlCreateMultiMetricJob', function (
$scope,
$route,
timefilter,
Private,
AppState) {
@ -250,8 +250,8 @@ module
function setTime() {
$scope.ui.bucketSpanValid = true;
$scope.formConfig.start = dateMath.parse(timefilter.time.from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.time.to).valueOf();
$scope.formConfig.start = dateMath.parse(timefilter.getTime().from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.getTime().to).valueOf();
$scope.formConfig.format = 'epoch_millis';
const bucketSpanInterval = parseInterval($scope.formConfig.bucketSpan);
@ -688,7 +688,7 @@ module
populateAppStateSettings(appState, $scope);
});
$scope.$listen(timefilter, 'fetch', () => {
$scope.$listenAndDigestAsync(timefilter, 'fetch', () => {
$scope.loadVis();
if ($scope.formConfig.splitField !== undefined) {
$scope.setModelMemoryLimit();

View file

@ -42,6 +42,7 @@ import { FullTimeRangeSelectorServiceProvider } from 'plugins/ml/components/full
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
import { initPromise } from 'plugins/ml/util/promise';
import template from './create_job.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job/simple/population', {
@ -65,7 +66,6 @@ module
$scope,
$route,
$timeout,
timefilter,
Private,
AppState) {
@ -265,8 +265,8 @@ module
function setTime() {
$scope.ui.bucketSpanValid = true;
$scope.formConfig.start = dateMath.parse(timefilter.time.from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.time.to).valueOf();
$scope.formConfig.start = dateMath.parse(timefilter.getTime().from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.getTime().to).valueOf();
$scope.formConfig.format = 'epoch_millis';
const bucketSpanInterval = parseInterval($scope.formConfig.bucketSpan);
@ -695,7 +695,7 @@ module
populateAppStateSettings(appState, $scope);
});
$scope.$listen(timefilter, 'fetch', $scope.loadVis);
$scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis);
angular.element(window).resize(() => {
resize();

View file

@ -15,8 +15,9 @@ import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
import { mlJobService } from 'plugins/ml/services/job_service';
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { ml } from 'plugins/ml/services/ml_api_service';
import { timefilter } from 'ui/timefilter';
export function PopulationJobServiceProvider(timefilter, Private) {
export function PopulationJobServiceProvider(Private) {
const TimeBuckets = Private(IntervalHelperProvider);
const OVER_FIELD_EXAMPLES_COUNT = 40;

View file

@ -25,6 +25,7 @@ import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar
import { ml } from 'plugins/ml/services/ml_api_service';
import { initPromise } from 'plugins/ml/util/promise';
import template from './create_job.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job/simple/recognize', {
@ -47,13 +48,11 @@ module
$scope,
$window,
$route,
timefilter,
Private) {
const mlCreateRecognizerJobsService = Private(CreateRecognizerJobsServiceProvider);
timefilter.disableTimeRangeSelector();
timefilter.disableAutoRefreshSelector();
$scope.tt = timefilter;
const msgs = mlMessageBarService;
const SAVE_STATE = {
@ -344,8 +343,8 @@ module
$scope.formConfig.start = resp.start.epoch;
$scope.formConfig.end = resp.end.epoch;
} else {
$scope.formConfig.start = dateMath.parse(timefilter.time.from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.time.to).valueOf();
$scope.formConfig.start = dateMath.parse(timefilter.getTime().from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.getTime().to).valueOf();
}
let jobsCounter = 0;
let datafeedCounter = 0;

View file

@ -14,7 +14,6 @@ import $ from 'jquery';
import d3 from 'd3';
import angular from 'angular';
import moment from 'moment';
import 'ui/timefilter';
import { TimeBuckets } from 'ui/time_buckets';
import { drawLineChartDots, numTicksForDateFormat } from 'plugins/ml/util/chart_utils';

View file

@ -44,6 +44,8 @@ import { initPromise } from 'plugins/ml/util/promise';
import template from './create_job.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job/simple/single_metric', {
template,
@ -66,7 +68,6 @@ module
$scope,
$route,
$filter,
timefilter,
Private,
AppState) {
@ -245,8 +246,8 @@ module
function setTime() {
$scope.ui.bucketSpanValid = true;
$scope.formConfig.start = dateMath.parse(timefilter.time.from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.time.to).valueOf();
$scope.formConfig.start = dateMath.parse(timefilter.getTime().from).valueOf();
$scope.formConfig.end = dateMath.parse(timefilter.getTime().to).valueOf();
$scope.formConfig.format = 'epoch_millis';
const bucketSpanInterval = parseInterval($scope.formConfig.bucketSpan);
@ -566,7 +567,7 @@ module
mlFullTimeRangeSelectorService.setFullTimeRange($scope.ui.indexPattern, $scope.formConfig.combinedQuery);
};
$scope.$listen(timefilter, 'fetch', $scope.loadVis);
$scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis);
$scope.$on('$destroy', () => {
globalForceStop = true;

View file

@ -19,6 +19,7 @@ import { loadIndexPatterns, getIndexPatterns } from 'plugins/ml/util/index_utils
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { initPromise } from 'plugins/ml/util/promise';
import template from './index_or_search.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job', {
@ -42,10 +43,7 @@ import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.controller('MlNewJobStepIndexOrSearch',
function (
$scope,
$route,
timefilter) {
function ($scope) {
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page

View file

@ -21,6 +21,7 @@ import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed';
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { initPromise } from 'plugins/ml/util/promise';
import template from './job_type.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/jobs/new_job/step/job_type', {
@ -42,8 +43,7 @@ const module = uiModules.get('apps/ml');
module.controller('MlNewJobStepJobType',
function (
$scope,
$route,
timefilter) {
$route) {
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page

View file

@ -22,6 +22,8 @@ import { initPromise } from 'plugins/ml/util/promise';
import template from './calendars_list.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/settings/calendars_list', {
template,
@ -43,7 +45,6 @@ module.controller('MlCalendarsList',
$route,
$location,
pagerFactory,
timefilter,
mlConfirmModalService) {
$scope.permissions = {

View file

@ -14,6 +14,8 @@ import { initPromise } from 'plugins/ml/util/promise';
import template from './settings.html';
import { timefilter } from 'ui/timefilter';
uiRoutes
.when('/settings', {
template,
@ -29,9 +31,7 @@ import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.controller('MlSettings',
function (
$scope,
timefilter) {
function () {
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page

View file

@ -22,8 +22,6 @@ import {
EuiToolTip
} from '@elastic/eui';
import 'ui/timefilter';
// don't use something like plugins/ml/../common
// because it won't work with the jest tests
import { FORECAST_REQUEST_STATE, JOB_STATE } from '../../../common/constants/states';
@ -402,6 +400,7 @@ ForecastingModal.propTypes = {
detectorIndex: PropTypes.number,
entities: PropTypes.array,
loadForForecastId: PropTypes.func,
timefilter: PropTypes.object,
};
export { ForecastingModal };

View file

@ -7,13 +7,13 @@
import 'ngreact';
import { timefilter } from 'ui/timefilter';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import { ForecastingModal } from './forecasting_modal';
module.directive('mlForecastingModal', function ($injector) {
const timefilter = $injector.get('timefilter');
const reactDirective = $injector.get('reactDirective');
return reactDirective(
ForecastingModal,

View file

@ -22,7 +22,6 @@ import {
} from '@elastic/eui';
import { formatDate } from '@elastic/eui/lib/services/format';
import 'ui/timefilter';
const TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss';

View file

@ -16,7 +16,7 @@ import $ from 'jquery';
import angular from 'angular';
import d3 from 'd3';
import moment from 'moment';
import 'ui/timefilter';
import { timefilter } from 'ui/timefilter';
import { ResizeChecker } from 'ui/resize_checker';
@ -40,7 +40,6 @@ const module = uiModules.get('apps/ml');
module.directive('mlTimeseriesChart', function (
$compile,
$timeout,
timefilter,
Private,
mlChartTooltipService) {

View file

@ -20,7 +20,7 @@ import 'plugins/ml/components/controls';
import { notify } from 'ui/notify';
import uiRoutes from 'ui/routes';
import 'ui/timefilter';
import { timefilter } from 'ui/timefilter';
import { parseInterval } from 'ui/utils/parse_interval';
import { checkLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege';
@ -70,7 +70,6 @@ module.controller('MlTimeSeriesExplorerController', function (
$route,
$timeout,
Private,
timefilter,
AppState,
mlSelectIntervalService,
mlSelectSeverityService) {
@ -505,8 +504,8 @@ module.controller('MlTimeSeriesExplorerController', function (
forecastId
).then((resp) => {
const bounds = timefilter.getActiveBounds();
const earliest = moment(resp.earliest || timefilter.time.from);
const latest = moment(resp.latest || timefilter.time.to);
const earliest = moment(resp.earliest || timefilter.getTime().from);
const latest = moment(resp.latest || timefilter.getTime().to);
// Store forecast ID in the appState.
$scope.appState.mlTimeSeriesExplorer.forecastId = forecastId;
@ -529,8 +528,10 @@ module.controller('MlTimeSeriesExplorerController', function (
if (earliest.isBefore(bounds.min) || latest.isAfter(bounds.max)) {
const earliestMs = Math.min(earliest.valueOf(), bounds.min.valueOf());
const latestMs = Math.max(latest.valueOf(), bounds.max.valueOf());
timefilter.time.from = moment(earliestMs).toISOString();
timefilter.time.to = moment(latestMs).toISOString();
timefilter.setTime({
from: moment(earliestMs).toISOString(),
to: moment(latestMs).toISOString()
});
} else {
// Refresh to show the requested forecast data.
$scope.refresh();
@ -559,7 +560,7 @@ module.controller('MlTimeSeriesExplorerController', function (
};
// Refresh the data when the time range is altered.
$scope.$listen(timefilter, 'fetch', $scope.refresh);
$scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.refresh);
// Add a watcher for auto-refresh of the time filter to refresh all the data.
const refreshWatcher = Private(refreshIntervalWatcher);

View file

@ -22,7 +22,7 @@ const unitsDesc = dateMath.unitsDesc;
const largeMax = unitsDesc.indexOf('w'); // Multiple units of week or longer converted to days for ES intervals.
import { TimeBuckets } from 'ui/time_buckets';
export function IntervalHelperProvider(Private, timefilter, config) {
export function IntervalHelperProvider(Private, config) {
const calcAuto = Private(TimeBucketsCalcAutoIntervalProvider);
inherits(MlTimeBuckets, TimeBuckets);

View file

@ -4,37 +4,42 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { timefilter } from 'ui/timefilter';
/*
* Watches for changes to the refresh interval of the page time filter,
* so that listeners can be notified when the auto-refresh interval has elapsed.
*/
export function refreshIntervalWatcher($rootScope, $timeout) {
export function refreshIntervalWatcher($timeout) {
let refresher;
let listener;
function init(listener) {
const onRefreshIntervalChange = () => {
if (refresher) {
$timeout.cancel(refresher);
}
const interval = timefilter.getRefreshInterval();
if (interval.value > 0 && !interval.pause) {
function startRefresh() {
refresher = $timeout(() => {
startRefresh();
listener();
}, interval.value);
}
startRefresh();
}
};
$rootScope.$watchCollection('timefilter.refreshInterval', (interval) => {
if (refresher) {
$timeout.cancel(refresher);
}
if (interval.value > 0 && !interval.pause) {
function startRefresh() {
refresher = $timeout(() => {
startRefresh();
listener();
}, interval.value);
}
startRefresh();
}
});
function init(listenerCallback) {
listener = listenerCallback;
timefilter.on('refreshIntervalUpdate', onRefreshIntervalChange);
}
function cancel() {
$timeout.cancel(refresher);
timefilter.off('refreshIntervalUpdate', onRefreshIntervalChange);
}
return {

View file

@ -9,9 +9,10 @@ import moment from 'moment';
import { render } from 'react-dom';
import { uiModules } from 'ui/modules';
import { Beat } from 'plugins/monitoring/components/beats/beat';
import { timefilter } from 'ui/timefilter';
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringBeatsBeat', (timefilter) => {
uiModule.directive('monitoringBeatsBeat', () => {
return {
restrict: 'E',
scope: {
@ -20,10 +21,10 @@ uiModule.directive('monitoringBeatsBeat', (timefilter) => {
link(scope, $el) {
function onBrush({ xaxis }) {
scope.$evalAsync(() => {
timefilter.time.from = moment(xaxis.from);
timefilter.time.to = moment(xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute',
});
}

View file

@ -9,9 +9,10 @@ import moment from 'moment';
import { render } from 'react-dom';
import { uiModules } from 'ui/modules';
import { BeatsOverview } from 'plugins/monitoring/components/beats/overview';
import { timefilter } from 'ui/timefilter';
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringBeatsOverview', (timefilter) => {
uiModule.directive('monitoringBeatsOverview', () => {
return {
restrict: 'E',
scope: {
@ -20,10 +21,10 @@ uiModule.directive('monitoringBeatsOverview', (timefilter) => {
link(scope, $el) {
function onBrush({ xaxis }) {
scope.$evalAsync(() => {
timefilter.time.from = moment(xaxis.from);
timefilter.time.to = moment(xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute'
});
}

View file

@ -17,9 +17,10 @@ import {
import { Tooltip } from 'pivotal-ui/react/tooltip';
import { OverlayTrigger } from 'pivotal-ui/react/overlay-trigger';
import { KuiInfoButton } from '@kbn/ui-framework/components';
import { timefilter } from 'ui/timefilter';
const uiModule = uiModules.get('plugins/monitoring/directives', []);
uiModule.directive('monitoringChart', (timefilter) => {
uiModule.directive('monitoringChart', () => {
return {
restrict: 'E',
scope: {
@ -31,10 +32,10 @@ uiModule.directive('monitoringChart', (timefilter) => {
const units = getUnits(series);
function onBrush({ xaxis }) {
scope.$evalAsync(() => {
timefilter.time.from = moment(xaxis.from);
timefilter.time.to = moment(xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute'
});
}

View file

@ -23,6 +23,7 @@ import { MonitoringTable } from 'plugins/monitoring/components/table';
import { Sparkline } from 'plugins/monitoring/components/sparkline';
import { SORT_ASCENDING } from '../../../../common/constants';
import { formatMetric } from '../../../lib/format_number';
import { timefilter } from 'ui/timefilter';
const filterFields = [ 'id' ];
const columns = [
@ -105,7 +106,6 @@ const pipelineRowFactory = (onPipelineClick, onBrush, tooltipXValueFormatter, to
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringLogstashPipelineListing', ($injector) => {
const kbnUrl = $injector.get('kbnUrl');
const timefilter = $injector.get('timefilter');
const config = $injector.get('config');
const dateFormat = config.get('dateFormat');
@ -124,10 +124,10 @@ uiModule.directive('monitoringLogstashPipelineListing', ($injector) => {
link: function (scope, $el) {
function onBrush(xaxis) {
scope.$evalAsync(() => {
timefilter.time.from = moment(xaxis.from);
timefilter.time.to = moment(xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute'
});
}

View file

@ -10,10 +10,10 @@ import { uiModules } from 'ui/modules';
import { Sparkline } from 'plugins/monitoring/components/sparkline';
import moment from 'moment';
import { formatMetric } from '../../lib/format_number';
import { timefilter } from 'ui/timefilter';
const uiModule = uiModules.get('plugins/monitoring/directives', []);
uiModule.directive('sparkline', ($injector) => {
const timefilter = $injector.get('timefilter');
const config = $injector.get('config');
const dateFormat = config.get('dateFormat');
@ -27,10 +27,10 @@ uiModule.directive('sparkline', ($injector) => {
link(scope, $elem) {
function onBrush(xaxis) {
scope.$evalAsync(() => {
timefilter.time.from = moment(xaxis.from);
timefilter.time.to = moment(xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute'
});
}

View file

@ -5,11 +5,11 @@
*/
import { ajaxErrorHandlersProvider } from './ajax_error_handler';
import { timefilter } from 'ui/timefilter';
export function getPageData($injector, api) {
const $http = $injector.get('$http');
const globalState = $injector.get('globalState');
const timefilter = $injector.get('timefilter');
const timeBounds = timefilter.getBounds();
return $http

View file

@ -4,104 +4,111 @@
* you may not use this file except in compliance with the Elastic License.
*/
import ngMock from 'ng_mock';
import expect from 'expect.js';
import sinon from 'sinon';
import { executorProvider } from '../executor_provider';
import EventEmitter from 'events';
import Promise from 'bluebird';
import { timefilter } from 'ui/timefilter';
describe('$executor service', () => {
let scope;
let executor;
let timefilter;
let $timeout;
let onSpy;
let offSpy;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (_$rootScope_) {
scope = _$rootScope_.$new();
}));
beforeEach(() => {
$timeout = sinon.spy(setTimeout);
$timeout.cancel = (id) => clearTimeout(id);
timefilter = new EventEmitter();
onSpy = sinon.spy((...args) => timefilter.addListener(...args));
offSpy = sinon.spy((...args) => timefilter.removeListener(...args));
timefilter.on = onSpy;
timefilter.off = offSpy;
timefilter.refreshInterval = {
timefilter.setRefreshInterval({
pause: false,
value: 0
};
executor = executorProvider(Promise, $timeout, timefilter);
});
executor = executorProvider(Promise, $timeout);
});
afterEach(() => executor.destroy());
it('should register listener for fetch upon start', () => {
executor.start();
expect(onSpy.calledTwice).to.equal(true);
expect(onSpy.firstCall.args[0]).to.equal('fetch');
expect(onSpy.firstCall.args[1].name).to.equal('reFetch');
executor.start(scope);
const listeners = timefilter.listeners('fetch');
const handlerFunc = listeners.find(listener => {
return listener.name === 'reFetch';
});
expect(handlerFunc).to.not.be.null;
});
it('should register listener for update upon start', () => {
executor.start();
expect(onSpy.calledTwice).to.equal(true);
expect(onSpy.secondCall.args[0]).to.equal('update');
expect(onSpy.secondCall.args[1].name).to.equal('killIfPaused');
it('should register listener for refreshIntervalUpdate upon start', () => {
executor.start(scope);
const listeners = timefilter.listeners('refreshIntervalUpdate');
const handlerFunc = listeners.find(listener => {
return listener.name === 'killIfPaused';
});
expect(handlerFunc).to.not.be.null;
});
it('should not call $timeout if the timefilter is not paused and set to zero', () => {
executor.start();
executor.start(scope);
expect($timeout.callCount).to.equal(0);
});
it('should call $timeout if the timefilter is not paused and set to 1000ms', () => {
timefilter.refreshInterval.value = 1000;
executor.start();
timefilter.setRefreshInterval({
value: 1000
});
executor.start(scope);
expect($timeout.callCount).to.equal(1);
});
it('should execute function if ingorePause is passed (interval set to 1000ms)', (done) => {
timefilter.refreshInterval.value = 1000;
executor.register({ execute: () => Promise.resolve().then(() => done(), done) });
executor.start({ ignorePaused: true });
});
it('should execute function if timefilter is not paused and interval set to 1000ms', (done) => {
timefilter.refreshInterval.value = 1000;
timefilter.setRefreshInterval({
value: 1000
});
executor.register({ execute: () => Promise.resolve().then(() => done(), done) });
executor.start();
executor.start(scope);
});
it('should execute function multiple times', (done) => {
let calls = 0;
timefilter.refreshInterval.value = 10;
timefilter.setRefreshInterval({
value: 10
});
executor.register({ execute: () => {
if (calls++ > 1) { done(); }
return Promise.resolve();
} });
executor.start();
executor.start(scope);
});
it('should call handleResponse', (done) => {
timefilter.refreshInterval.value = 10;
timefilter.setRefreshInterval({
value: 10
});
executor.register({
execute: () => Promise.resolve(),
handleResponse: () => done()
});
executor.start();
executor.start(scope);
});
it('should call handleError', (done) => {
timefilter.refreshInterval.value = 10;
timefilter.setRefreshInterval({
value: 10
});
executor.register({
execute: () => Promise.reject(new Error('reject test')),
handleError: () => done()
});
executor.start();
executor.start(scope);
});
});

View file

@ -6,11 +6,11 @@
import { uiModules } from 'ui/modules';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
const uiModule = uiModules.get('monitoring/clusters');
uiModule.service('monitoringClusters', ($injector) => {
return (clusterUuid, ccs) => {
const timefilter = $injector.get('timefilter');
const { min, max } = timefilter.getBounds();
// append clusterUuid if the parameter is given

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { defaults } from 'lodash';
export function executorProvider(Promise, $timeout, timefilter) {
import { timefilter } from 'ui/timefilter';
export function executorProvider(Promise, $timeout) {
const queue = [];
let executionTimer;
@ -30,8 +30,6 @@ export function executorProvider(Promise, $timeout, timefilter) {
*/
function cancel() {
killTimer();
timefilter.off('update', killIfPaused);
timefilter.off('fetch', reFetch);
}
/**
@ -73,7 +71,7 @@ export function executorProvider(Promise, $timeout, timefilter) {
}
function killIfPaused() {
if (timefilter.refreshInterval.pause) {
if (timefilter.getRefreshInterval().pause) {
killTimer();
}
}
@ -83,10 +81,8 @@ export function executorProvider(Promise, $timeout, timefilter) {
* @returns {void}
*/
function start() {
timefilter.on('fetch', reFetch);
timefilter.on('update', killIfPaused);
if ((ignorePaused || timefilter.refreshInterval.pause === false) && timefilter.refreshInterval.value > 0) {
executionTimer = $timeout(run, timefilter.refreshInterval.value);
if ((ignorePaused || timefilter.getRefreshInterval().pause === false) && timefilter.getRefreshInterval().value > 0) {
executionTimer = $timeout(run, timefilter.getRefreshInterval().value);
}
}
@ -95,17 +91,9 @@ export function executorProvider(Promise, $timeout, timefilter) {
*/
return {
register,
start(options = {}) {
options = defaults(options, {
ignorePaused: false,
now: false
});
if (options.now) {
return run();
}
if (options.ignorePaused) {
ignorePaused = options.ignorePaused;
}
start($scope) {
$scope.$listenAndDigestAsync(timefilter, 'fetch', reFetch);
$scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', killIfPaused);
start();
},
run,

View file

@ -7,6 +7,7 @@
import { spy, stub } from 'sinon';
import expect from 'expect.js';
import { MonitoringViewBaseController } from '../';
import { timefilter } from 'ui/timefilter';
/*
* Mostly copied from base_table_controller test, with modifications
@ -17,28 +18,10 @@ describe('MonitoringViewBaseController', function () {
let $injector;
let $scope;
let opts;
let timefilter;
let titleService;
let executorService;
let isTimeRangeSelectorEnabled;
let isAutoRefreshSelectorEnabled;
before(() => {
timefilter = {
enableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = true;
},
enableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = true;
},
disableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = false;
},
disableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = false;
},
};
titleService = spy();
executorService = {
register: spy(),
@ -47,7 +30,6 @@ describe('MonitoringViewBaseController', function () {
const injectorGetStub = stub();
injectorGetStub.withArgs('title').returns(titleService);
injectorGetStub.withArgs('timefilter').returns(timefilter);
injectorGetStub.withArgs('$executor').returns(executorService);
injectorGetStub.withArgs('localStorage').throws('localStorage should not be used by this class');
$injector = { get: injectorGetStub };
@ -89,8 +71,8 @@ describe('MonitoringViewBaseController', function () {
describe('time filter', () => {
it('enables timepicker and auto refresh #1', () => {
expect(isTimeRangeSelectorEnabled).to.be(true);
expect(isAutoRefreshSelectorEnabled).to.be(true);
expect(timefilter.isTimeRangeSelectorEnabled).to.be(true);
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(true);
});
it('enables timepicker and auto refresh #2', () => {
@ -100,8 +82,8 @@ describe('MonitoringViewBaseController', function () {
};
ctrl = new MonitoringViewBaseController(opts);
expect(isTimeRangeSelectorEnabled).to.be(true);
expect(isAutoRefreshSelectorEnabled).to.be(true);
expect(timefilter.isTimeRangeSelectorEnabled).to.be(true);
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(true);
});
it('disables timepicker and enables auto refresh', () => {
@ -111,8 +93,8 @@ describe('MonitoringViewBaseController', function () {
};
ctrl = new MonitoringViewBaseController(opts);
expect(isTimeRangeSelectorEnabled).to.be(false);
expect(isAutoRefreshSelectorEnabled).to.be(true);
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(true);
});
it('enables timepicker and disables auto refresh', () => {
@ -122,8 +104,8 @@ describe('MonitoringViewBaseController', function () {
};
ctrl = new MonitoringViewBaseController(opts);
expect(isTimeRangeSelectorEnabled).to.be(true);
expect(isAutoRefreshSelectorEnabled).to.be(false);
expect(timefilter.isTimeRangeSelectorEnabled).to.be(true);
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(false);
});
it('disables timepicker and auto refresh', () => {
@ -136,8 +118,8 @@ describe('MonitoringViewBaseController', function () {
};
ctrl = new MonitoringViewBaseController(opts);
expect(isTimeRangeSelectorEnabled).to.be(false);
expect(isAutoRefreshSelectorEnabled).to.be(false);
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(false);
});
});

View file

@ -10,10 +10,10 @@ import template from './index.html';
import { MonitoringViewBaseController } from 'plugins/monitoring/views';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
function getPageData($injector) {
const globalState = $injector.get('globalState');
const timefilter = $injector.get('timefilter');
const $http = $injector.get('$http');
const Private = $injector.get('Private');
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/alerts`;

View file

@ -9,6 +9,7 @@ import moment from 'moment';
import { render, unmountComponentAtNode } from 'react-dom';
import { getPageData } from '../lib/get_page_data';
import { PageLoading } from 'plugins/monitoring/components';
import { timefilter } from 'ui/timefilter';
/**
* Class to manage common instantiation behaviors in a view controller
@ -69,7 +70,6 @@ export class MonitoringViewBaseController {
options = {}
}) {
const titleService = $injector.get('title');
const timefilter = $injector.get('timefilter');
const $executor = $injector.get('$executor');
titleService($scope.cluster, title);
@ -108,7 +108,7 @@ export class MonitoringViewBaseController {
$executor.register({
execute: () => this.updateData()
});
$executor.start();
$executor.start($scope);
$scope.$on('$destroy', () => {
if (this.reactNodeId) { // WIP https://github.com/elastic/x-pack-kibana/issues/5198
unmountComponentAtNode(document.getElementById(this.reactNodeId));
@ -119,10 +119,10 @@ export class MonitoringViewBaseController {
// needed for chart pages
this.onBrush = ({ xaxis }) => {
const { to, from } = xaxis;
$scope.$evalAsync(() => {
timefilter.time.from = moment(from);
timefilter.time.to = moment(to);
timefilter.time.mode = 'absolute';
timefilter.setTime({
from: moment(from),
to: moment(to),
mode: 'absolute'
});
};
}

View file

@ -5,13 +5,13 @@
*/
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
export function getPageData($injector) {
const $http = $injector.get('$http');
const $route = $injector.get('$route');
const globalState = $injector.get('globalState');
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beat/${$route.current.params.beatUuid}`;
const timefilter = $injector.get('timefilter');
const timeBounds = timefilter.getBounds();
return $http.post(url, {

View file

@ -5,11 +5,11 @@
*/
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
export function getPageData($injector) {
const $http = $injector.get('$http');
const globalState = $injector.get('globalState');
const timefilter = $injector.get('timefilter');
const timeBounds = timefilter.getBounds();
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beats`;

View file

@ -5,11 +5,11 @@
*/
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
export function getPageData($injector) {
const $http = $injector.get('$http');
const globalState = $injector.get('globalState');
const timefilter = $injector.get('timefilter');
const timeBounds = timefilter.getBounds();
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats`;

View file

@ -7,6 +7,7 @@
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
uiRoutes.when('/overview', {
template,
@ -21,7 +22,6 @@ uiRoutes.when('/overview', {
}
},
controller($injector, $scope) {
const timefilter = $injector.get('timefilter');
timefilter.enableTimeRangeSelector();
timefilter.enableAutoRefreshSelector();
@ -41,7 +41,7 @@ uiRoutes.when('/overview', {
}
});
$executor.start();
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
}

Some files were not shown because too many files have changed in this diff Show more