diff --git a/src/legacy/ui/public/timefilter/timefilter.d.ts b/src/legacy/ui/public/timefilter/timefilter.d.ts index b032caf684cb1..1a82c3c67eafa 100644 --- a/src/legacy/ui/public/timefilter/timefilter.d.ts +++ b/src/legacy/ui/public/timefilter/timefilter.d.ts @@ -33,11 +33,15 @@ export interface Timefilter { setTime: (timeRange: TimeRange) => void; setRefreshInterval: (refreshInterval: RefreshInterval) => void; getRefreshInterval: () => RefreshInterval; + getActiveBounds: () => void; disableAutoRefreshSelector: () => void; disableTimeRangeSelector: () => void; enableAutoRefreshSelector: () => void; + enableTimeRangeSelector: () => void; off: (event: string, reload: () => void) => void; on: (event: string, reload: () => void) => void; + isAutoRefreshSelectorEnabled: boolean; + isTimeRangeSelectorEnabled: boolean; } export const timefilter: Timefilter; diff --git a/x-pack/plugins/ml/common/timefilter.ts b/x-pack/plugins/ml/common/timefilter.ts new file mode 100644 index 0000000000000..221e24418cc9e --- /dev/null +++ b/x-pack/plugins/ml/common/timefilter.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Subject } from 'rxjs'; +import { timefilter as timefilterDep } from 'ui/timefilter'; +import { RefreshInterval } from 'src/legacy/ui/public/timefilter/timefilter'; +import { TimeRange } from 'src/legacy/ui/public/timefilter/time_history'; + +const timefilterUpdate$ = new Subject(); +const refreshIntervalUpdate$ = new Subject(); + +class MlTimefilter { + constructor() { + // listen for original timefilter emitted events + timefilterDep.on('fetch', this.emitUpdate); // setTime or setRefreshInterval called + timefilterDep.on('refreshIntervalUpdate', () => this.emitRefreshIntervalUpdate()); // setRefreshInterval called + } + + emitUpdate() { + timefilterUpdate$.next(); + } + + emitRefreshIntervalUpdate() { + refreshIntervalUpdate$.next(); + } + + disableAutoRefreshSelector() { + return timefilterDep.disableAutoRefreshSelector(); + } + + disableTimeRangeSelector() { + return timefilterDep.disableTimeRangeSelector(); + } + + enableAutoRefreshSelector() { + return timefilterDep.enableAutoRefreshSelector(); + } + + enableTimeRangeSelector() { + return timefilterDep.enableTimeRangeSelector(); + } + + isAutoRefreshSelectorEnabled() { + return timefilterDep.isAutoRefreshSelectorEnabled; + } + + isTimeRangeSelectorEnabled() { + return timefilterDep.isAutoRefreshSelectorEnabled; + } + + getActiveBounds() { + return timefilterDep.getActiveBounds(); + } + + getRefreshInterval() { + return timefilterDep.getRefreshInterval(); + } + + getTime() { + return timefilterDep.getTime(); + } + + off(event: string) { + timefilterDep.off(event, this.emitRefreshIntervalUpdate); + } + + // in timefilter dependency - 'fetch', 'refreshIntervalUpdate' emitted + setRefreshInterval(interval: RefreshInterval) { + timefilterDep.setRefreshInterval(interval); + } + // in timefilter dependency - 'fetch' is emitted + setTime(time: TimeRange) { + timefilterDep.setTime(time); + } + // consumer must call unsubscribe on return value + subscribeToUpdates(callback: () => void) { + return timefilterUpdate$.subscribe(callback); + } + // consumer must call unsubscribe on return value + subscribeToRefreshIntervalUpdate(callback: () => void) { + return refreshIntervalUpdate$.subscribe(callback); + } +} + +export const timefilter = new MlTimefilter(); diff --git a/x-pack/plugins/ml/public/app.js b/x-pack/plugins/ml/public/app.js index f5f36bffbba3c..d54441e1994ab 100644 --- a/x-pack/plugins/ml/public/app.js +++ b/x-pack/plugins/ml/public/app.js @@ -27,7 +27,7 @@ import 'plugins/ml/components/form_label'; import 'plugins/ml/components/json_tooltip'; import 'plugins/ml/components/tooltip'; import 'plugins/ml/components/confirm_modal'; -import 'plugins/ml/components/nav_menu'; +import 'plugins/ml/components/navigation_menu'; import 'plugins/ml/components/loading_indicator'; import 'plugins/ml/settings'; import 'plugins/ml/file_datavisualizer'; diff --git a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table_directive.js b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table_directive.js index 5772bc98959e4..1ab7f9b1530c7 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table_directive.js +++ b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table_directive.js @@ -9,7 +9,7 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../common/timefilter'; const module = uiModules.get('apps/ml', ['react']); import { AnomaliesTable } from './anomalies_table'; diff --git a/x-pack/plugins/ml/public/components/field_data_card/document_count_chart_directive.js b/x-pack/plugins/ml/public/components/field_data_card/document_count_chart_directive.js index 1550d8077afda..9bf1e2801ea82 100644 --- a/x-pack/plugins/ml/public/components/field_data_card/document_count_chart_directive.js +++ b/x-pack/plugins/ml/public/components/field_data_card/document_count_chart_directive.js @@ -24,7 +24,7 @@ import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tool import { formatHumanReadableDateTime } from '../../util/date_utils'; import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../common/timefilter'; const module = uiModules.get('apps/ml'); module.directive('mlDocumentCountChart', function () { diff --git a/x-pack/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_service.ts b/x-pack/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_service.ts index 32606d2db425e..86f1064916093 100644 --- a/x-pack/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_service.ts +++ b/x-pack/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_service.ts @@ -9,8 +9,8 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { IndexPattern } from 'ui/index_patterns'; import { toastNotifications } from 'ui/notify'; -import { timefilter } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; +import { timefilter } from '../../../common/timefilter'; import { ml } from '../../services/ml_api_service'; export function setFullTimeRange(indexPattern: IndexPattern, query: Query) { diff --git a/x-pack/plugins/ml/public/components/job_selector/job_selector.js b/x-pack/plugins/ml/public/components/job_selector/job_selector.js index 0432e6de4c70f..8e97ba3fc1e4f 100644 --- a/x-pack/plugins/ml/public/components/job_selector/job_selector.js +++ b/x-pack/plugins/ml/public/components/job_selector/job_selector.js @@ -14,7 +14,7 @@ import { ml } from '../../services/ml_api_service'; import { JobSelectorTable } from './job_selector_table/'; import { IdBadges } from './id_badges'; import { NewSelectionIdBadges } from './new_selection_id_badges'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../common/timefilter'; import { getGroupsFromJobs, normalizeTimes, setGlobalState } from './job_select_service_utils'; import { toastNotifications } from 'ui/notify'; import { diff --git a/x-pack/plugins/ml/public/components/navigation_menu/_index.scss b/x-pack/plugins/ml/public/components/navigation_menu/_index.scss new file mode 100644 index 0000000000000..5135bba535dd9 --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/_index.scss @@ -0,0 +1 @@ +@import 'navigation_menu' diff --git a/x-pack/plugins/ml/public/components/navigation_menu/_navigation_menu.scss b/x-pack/plugins/ml/public/components/navigation_menu/_navigation_menu.scss new file mode 100644 index 0000000000000..08dcbfc014499 --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/_navigation_menu.scss @@ -0,0 +1,7 @@ +.mlNavigationMenu__tab { + padding-bottom: 0; +} + +.mlNavigationMenu__topNav { + padding-top: $euiSizeS; +} diff --git a/x-pack/plugins/ml/public/components/navigation_menu/index.ts b/x-pack/plugins/ml/public/components/navigation_menu/index.ts new file mode 100644 index 0000000000000..55fe88ec90869 --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './navigation_menu_react_wrapper_directive'; diff --git a/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu.tsx b/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu.tsx new file mode 100644 index 0000000000000..38e8aeb354dcf --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, SFC, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { TopNav } from './top_nav'; + +interface Tab { + id: string; + name: any; + disabled: boolean; +} + +interface Props { + dateFormat: string; + disableLinks: boolean; + showTabs: boolean; + tabId: string; + timeHistory: any; +} + +function moveToSelectedTab(selectedTabId: string) { + window.location.href = `${chrome.getBasePath()}/app/ml#/${selectedTabId}`; +} + +function getTabs(disableLinks: boolean): Tab[] { + return [ + { + id: 'jobs', + name: i18n.translate('xpack.ml.navMenu.jobManagementTabLinkText', { + defaultMessage: 'Job Management', + }), + disabled: disableLinks, + }, + { + id: 'explorer', + name: i18n.translate('xpack.ml.navMenu.anomalyExplorerTabLinkText', { + defaultMessage: 'Anomaly Explorer', + }), + disabled: disableLinks, + }, + { + id: 'timeseriesexplorer', + name: i18n.translate('xpack.ml.navMenu.singleMetricViewerTabLinkText', { + defaultMessage: 'Single Metric Viewer', + }), + disabled: disableLinks, + }, + { + id: 'data_frames', + name: i18n.translate('xpack.ml.navMenu.dataFrameTabLinkText', { + defaultMessage: 'Data Frames', + }), + disabled: false, + }, + { + id: 'datavisualizer', + name: i18n.translate('xpack.ml.navMenu.dataVisualizerTabLinkText', { + defaultMessage: 'Data Visualizer', + }), + disabled: false, + }, + { + id: 'settings', + name: i18n.translate('xpack.ml.navMenu.settingsTabLinkText', { + defaultMessage: 'Settings', + }), + disabled: disableLinks, + }, + ]; +} + +export const NavigationMenu: SFC = ({ + dateFormat, + disableLinks, + showTabs, + tabId, + timeHistory, +}) => { + const [tabs] = useState(getTabs(disableLinks)); + const [selectedTabId, setSelectedTabId] = useState(tabId); + + function onSelectedTabChanged(id: string) { + moveToSelectedTab(id); + setSelectedTabId(id); + } + + function renderTabs() { + return tabs.map((tab: Tab) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + key={`${tab.id}-key`} + > + {tab.name} + + )); + } + + return ( + + + + + + + {showTabs && {renderTabs()}} + + ); +}; diff --git a/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js b/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js new file mode 100644 index 0000000000000..b9a911e9051f4 --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { NavigationMenu } from './navigation_menu'; +import { isFullLicense } from '../../license/check_license'; +import { timeHistory } from 'ui/timefilter/time_history'; +import { uiModules } from 'ui/modules'; +const module = uiModules.get('apps/ml'); + +import 'ui/directives/kbn_href'; + + +module.directive('mlNavMenu', function (config) { + return { + restrict: 'E', + transclude: true, + link: function (scope, element, attrs) { + const { name } = attrs; + let showTabs = false; + + if (name === 'jobs' || + name === 'settings' || + name === 'data_frame' || + name === 'datavisualizer' || + name === 'filedatavisualizer' || + name === 'timeseriesexplorer' || + name === 'access-denied' || + name === 'explorer') { + showTabs = true; + } + const props = { + dateFormat: config.get('dateFormat'), + disableLinks: (isFullLicense() === false), + showTabs, + tabId: name, + timeHistory + }; + + ReactDOM.render(React.createElement(NavigationMenu, props), + element[0] + ); + + element.on('$destroy', () => { + ReactDOM.unmountComponentAtNode(element[0]); + scope.$destroy(); + }); + } + }; +}); diff --git a/x-pack/plugins/ml/public/components/navigation_menu/top_nav/index.ts b/x-pack/plugins/ml/public/components/navigation_menu/top_nav/index.ts new file mode 100644 index 0000000000000..56d6651da7309 --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/top_nav/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TopNav } from './top_nav'; diff --git a/x-pack/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx new file mode 100644 index 0000000000000..f8a7ffbfd560a --- /dev/null +++ b/x-pack/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { SFC, useState, useEffect } from 'react'; +import { EuiSuperDatePicker } from '@elastic/eui'; +import { TimeHistory, TimeRange } from 'src/legacy/ui/public/timefilter/time_history'; +import { timefilter } from '../../../../common/timefilter'; + +interface Props { + dateFormat: string; + timeHistory: TimeHistory; +} + +function getRecentlyUsedRanges(timeHistory: TimeHistory): Array<{ start: string; end: string }> { + return timeHistory.get().map(({ from, to }: TimeRange) => { + return { + start: from, + end: to, + }; + }); +} + +export const TopNav: SFC = ({ dateFormat, timeHistory }) => { + const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval()); + const [time, setTime] = useState(timefilter.getTime()); + const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges(timeHistory)); + + useEffect(() => { + const subscription = timefilter.subscribeToUpdates(timefilterUpdateListener); + + return function cleanup() { + subscription.unsubscribe(); + }; + }, []); + + function timefilterUpdateListener() { + setTime(timefilter.getTime()); + setRefreshInterval(timefilter.getRefreshInterval()); + } + + function updateFilter({ start, end }: { start: string; end: string }) { + const newTime = { from: start, to: end }; + // Update timefilter for controllers listening for changes + timefilter.setTime(newTime); + setTime(newTime); + setRecentlyUsedRanges(getRecentlyUsedRanges(timeHistory)); + } + + function updateInterval({ + isPaused, + refreshInterval: interval, + }: { + isPaused: boolean; + refreshInterval: number; + }) { + const newInterval = { + pause: isPaused, + value: interval, + }; + // Update timefilter for controllers listening for changes + timefilter.setRefreshInterval(newInterval); + // Update state + setRefreshInterval(newInterval); + } + + return ( + (timefilter.isAutoRefreshSelectorEnabled || timefilter.isTimeRangeSelectorEnabled) && ( + + ) + ); +}; diff --git a/x-pack/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx b/x-pack/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx index 3b86138027d1c..bbade806af6ca 100644 --- a/x-pack/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx +++ b/x-pack/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx @@ -14,7 +14,7 @@ const module = uiModules.get('apps/ml', ['react']); import { IndexPattern } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { IPrivate } from 'ui/private'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../common/timefilter'; import { InjectorService } from '../../../../common/types/angular'; // @ts-ignore diff --git a/x-pack/plugins/ml/public/data_frame/pages/job_management/components/job_list/use_refresh_interval.ts b/x-pack/plugins/ml/public/data_frame/pages/job_management/components/job_list/use_refresh_interval.ts index 00449c23f212c..69441313066cc 100644 --- a/x-pack/plugins/ml/public/data_frame/pages/job_management/components/job_list/use_refresh_interval.ts +++ b/x-pack/plugins/ml/public/data_frame/pages/job_management/components/job_list/use_refresh_interval.ts @@ -6,7 +6,7 @@ import React, { useEffect } from 'react'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; import { DEFAULT_REFRESH_INTERVAL_MS, @@ -21,6 +21,7 @@ export const useRefreshInterval = ( ) => { useEffect(() => { let jobsRefreshInterval: null | number = null; + let timefilterSubscriber: { unsubscribe: () => void }; timefilter.disableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); @@ -44,7 +45,7 @@ export const useRefreshInterval = ( function initAutoRefreshUpdate() { // update the interval if it changes - timefilter.on('refreshIntervalUpdate', () => { + timefilterSubscriber = timefilter.subscribeToRefreshIntervalUpdate(function triggerUpdate() { setAutoRefresh(); }); } @@ -80,6 +81,8 @@ export const useRefreshInterval = ( // useEffect cleanup return () => { clearRefreshInterval(); + timefilterSubscriber.unsubscribe(); + timefilter.off('refreshIntervalUpdate'); }; }, []); // [] as comparator makes sure this only runs once }; diff --git a/x-pack/plugins/ml/public/datavisualizer/datavisualizer_controller.js b/x-pack/plugins/ml/public/datavisualizer/datavisualizer_controller.js index 7e32a35692bf0..25efd7cf74caf 100644 --- a/x-pack/plugins/ml/public/datavisualizer/datavisualizer_controller.js +++ b/x-pack/plugins/ml/public/datavisualizer/datavisualizer_controller.js @@ -47,7 +47,7 @@ uiRoutes } }); -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml'); @@ -144,9 +144,9 @@ module .value(); $scope.indexedFieldTypes = indexedFieldTypes.sort(); - - // Refresh the data when the time range is altered. - $scope.$listenAndDigestAsync(timefilter, 'fetch', function () { + // Refresh all the data when the time range is altered. Replaces listen for 'fetch' emitted by legacy timefilter + // which would occur on setTime or setRefreshInterval + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { $scope.earliest = timefilter.getActiveBounds().min.valueOf(); $scope.latest = timefilter.getActiveBounds().max.valueOf(); loadOverallStats(); @@ -675,4 +675,7 @@ module loadOverallStats(); + $scope.$on('$destroy', () => { + timefilterSubscriber.unsubscribe(); + }); }); diff --git a/x-pack/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js b/x-pack/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js index 02f230458237b..9cc6ad2a27422 100644 --- a/x-pack/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js +++ b/x-pack/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js @@ -22,7 +22,7 @@ import { import { isFullLicense } from '../../license/check_license'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../common/timefilter'; function startTrialDescription() { return ( diff --git a/x-pack/plugins/ml/public/explorer/explorer.js b/x-pack/plugins/ml/public/explorer/explorer.js index 375040301a0a3..56628858782bc 100644 --- a/x-pack/plugins/ml/public/explorer/explorer.js +++ b/x-pack/plugins/ml/public/explorer/explorer.js @@ -91,7 +91,7 @@ import { ExplorerChartsContainer } from './explorer_charts/explorer_charts_conta // Anomalies Table import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { toastNotifications } from 'ui/notify'; function getExplorerDefaultState() { diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js index 78fcadf56873e..150e80629d5d3 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js +++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js @@ -53,7 +53,7 @@ jest.mock('ui/chrome', }), { virtual: true }); import moment from 'moment'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../common/timefilter'; timefilter.enableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); diff --git a/x-pack/plugins/ml/public/explorer/explorer_controller.js b/x-pack/plugins/ml/public/explorer/explorer_controller.js index 1bdff98b0aec2..104d4b5bcdc41 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_controller.js +++ b/x-pack/plugins/ml/public/explorer/explorer_controller.js @@ -33,7 +33,7 @@ import { explorer$ } from './explorer_dashboard_service'; import { mlFieldFormatService } from 'plugins/ml/services/field_format_service'; import { mlJobService } from '../services/job_service'; import { refreshIntervalWatcher } from '../util/refresh_interval_watcher'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { APP_STATE_ACTION, EXPLORER_ACTION } from './explorer_constants'; @@ -192,6 +192,13 @@ module.controller('MlExplorerController', function ( } const explorerSubscriber = explorer$.subscribe(loadJobsListener); + // Refresh all the data when the time range is altered. Replaces listen for 'fetch' emitted by legacy timefilter + // which would occur on setTime or setRefreshInterval + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { + if ($scope.jobSelectionUpdateInProgress === false) { + explorer$.next({ action: EXPLORER_ACTION.RELOAD }); + } + }); // Listen for changes to job selection. $scope.jobSelectionUpdateInProgress = false; @@ -203,13 +210,6 @@ module.controller('MlExplorerController', function ( } }); - // Refresh all the data when the time range is altered. - $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { - if ($scope.jobSelectionUpdateInProgress === false) { - explorer$.next({ action: EXPLORER_ACTION.RELOAD }); - } - }); - // Add a watcher for auto-refresh of the time filter to refresh all the data. const refreshWatcher = Private(refreshIntervalWatcher); refreshWatcher.init(async () => { @@ -287,6 +287,7 @@ module.controller('MlExplorerController', function ( $scope.$on('$destroy', () => { explorerSubscriber.unsubscribe(); jobSelectServiceSub.unsubscribe(); + timefilterSubscriber.unsubscribe(); refreshWatcher.cancel(); $(window).off('resize', jqueryRedrawOnResize); // Cancel listening for updates to the global nav state. diff --git a/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js index 3a0eeba4890dd..45a2492b9e2f3 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js @@ -7,7 +7,7 @@ import { FileDataVisualizerView } from './components/file_datavisualizer_view'; import React from 'react'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; export function FileDataVisualizerPage({ indexPatterns, kibanaConfig }) { timefilter.disableTimeRangeSelector(); diff --git a/x-pack/plugins/ml/public/index.scss b/x-pack/plugins/ml/public/index.scss index 0332eb5756e45..a8b16b5de56d6 100644 --- a/x-pack/plugins/ml/public/index.scss +++ b/x-pack/plugins/ml/public/index.scss @@ -40,11 +40,11 @@ @import 'components/form_label/index'; @import 'components/influencers_list/index'; @import 'components/items_grid/index'; - @import 'components/job_selector/index'; // TODO: remove above two once react conversion of job selector is done + @import 'components/job_selector/index'; @import 'components/json_tooltip/index'; // SASSTODO: This file overwrites EUI directly @import 'components/loading_indicator/index'; // SASSTODO: This component should be replaced with EuiLoadingSpinner @import 'components/messagebar/index'; - @import 'components/nav_menu/index'; + @import 'components/navigation_menu/index'; @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly // Hacks are last so they can overwrite anything above if needed diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 69696e07ee8ff..fdd2c39cb7b93 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -5,7 +5,7 @@ */ -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../common/timefilter'; import { ml } from 'plugins/ml/services/ml_api_service'; import { loadFullJob, filterJobs, checkForAutoStartDatafeed } from '../utils'; @@ -68,6 +68,7 @@ export class JobsListView extends Component { this.showCreateWatchFlyout = () => {}; this.blockRefresh = false; + this.timefilterSubscriber = null; } componentDidMount() { @@ -95,6 +96,9 @@ export class JobsListView extends Component { componentWillUnmount() { timefilter.off('refreshIntervalUpdate'); + if (this.timefilterSubscriber !== null) { + this.timefilterSubscriber.unsubscribe(); + } deletingJobsRefreshTimeout = null; this.clearRefreshInterval(); } @@ -115,8 +119,9 @@ export class JobsListView extends Component { initAutoRefreshUpdate() { // update the interval if it changes - timefilter.on('refreshIntervalUpdate', () => { - this.setAutoRefresh(); + const self = this; + this.timefilterSubscriber = timefilter.subscribeToRefreshIntervalUpdate(function triggerUpdate() { + self.setAutoRefresh(); }); } diff --git a/x-pack/plugins/ml/public/jobs/new_job/advanced/new_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/advanced/new_job_controller.js index 5724c5cd8a670..a2514b44b7b16 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/advanced/new_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/advanced/new_job_controller.js @@ -14,7 +14,7 @@ import 'ui/angular_ui_select'; import 'ui/directives/input_focus'; import { parseInterval } from 'ui/utils/parse_interval'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../common/timefilter'; import uiRoutes from 'ui/routes'; import { checkFullLicense } from 'plugins/ml/license/check_license'; diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/chart_data_utils.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/chart_data_utils.js index 24aedb95acaa6..d0080dc6fbd44 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/chart_data_utils.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/chart_data_utils.js @@ -13,7 +13,7 @@ import { MlTimeBuckets } 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'; +import { timefilter } from '../../../../../../common/timefilter'; export function ChartDataUtilsProvider() { diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js index e0df234a5139c..1a82c01b45d70 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js @@ -43,7 +43,7 @@ import { MultiMetricJobServiceProvider } from './create_job_service'; import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; import { ml } from 'plugins/ml/services/ml_api_service'; import template from './create_job.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; uiRoutes .when('/jobs/new_job/simple/multi_metric', { @@ -757,7 +757,7 @@ module preLoadJob($scope, appState); }); - $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { $scope.loadVis(); if ($scope.formConfig.splitField !== undefined) { $scope.setModelMemoryLimit(); @@ -766,6 +766,7 @@ module $scope.$on('$destroy', () => { globalForceStop = true; + timefilterSubscriber.unsubscribe(); angular.element(window).off('resize'); }); }); diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js index b3235ee6b374e..76aacb1375251 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js @@ -42,7 +42,7 @@ import { preLoadJob } from 'plugins/ml/jobs/new_job/simple/components/utils/prep import { PopulationJobServiceProvider } from './create_job_service'; import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; import template from './create_job.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; uiRoutes .when('/jobs/new_job/simple/population', { @@ -737,10 +737,13 @@ module preLoadJob($scope, appState); }); - $scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis); + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { + $scope.loadVis(); + }); $scope.$on('$destroy', () => { globalForceStop = true; + timefilterSubscriber.unsubscribe(); angular.element(window).off('resize'); }); }); diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_service.js b/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_service.js index f4d1f8a2d462a..fab7b22009c03 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_service.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_service.js @@ -15,7 +15,7 @@ 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'; +import { timefilter } from '../../../../../../common/timefilter'; export function PopulationJobServiceProvider() { diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js index 317b9cff49897..3cdc38b624457 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js @@ -28,7 +28,7 @@ import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar import { ml } from 'plugins/ml/services/ml_api_service'; import template from './create_job.html'; import { toastNotifications } from 'ui/notify'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; uiRoutes .when('/jobs/new_job/simple/recognize', { diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js index de4a0a2e6cdb7..78e5e3866f7b1 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js @@ -44,7 +44,7 @@ import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar import template from './create_job.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; uiRoutes .when('/jobs/new_job/simple/single_metric', { @@ -621,10 +621,13 @@ module moveToAdvancedJobCreation(job); }; - $scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis); + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { + $scope.loadVis(); + }); $scope.$on('$destroy', () => { globalForceStop = true; + timefilterSubscriber.unsubscribe(); }); $scope.$evalAsync(() => { diff --git a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search_controller.js b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search_controller.js index bc688c07cab2d..3d911f27851be 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search_controller.js @@ -24,7 +24,7 @@ import { import { loadIndexPatterns, getIndexPatterns } from 'plugins/ml/util/index_utils'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import template from './index_or_search.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; import 'ui/directives/paginated_selectable_list'; import 'ui/directives/saved_object_finder'; diff --git a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js index df7768ee9f0c8..ac10f493cd744 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js @@ -21,7 +21,7 @@ import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import template from './job_type.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../../../common/timefilter'; uiRoutes .when('/jobs/new_job/step/job_type', { diff --git a/x-pack/plugins/ml/public/settings/settings_directive.js b/x-pack/plugins/ml/public/settings/settings_directive.js index 68ac533089558..3b67c3bf39176 100644 --- a/x-pack/plugins/ml/public/settings/settings_directive.js +++ b/x-pack/plugins/ml/public/settings/settings_directive.js @@ -19,7 +19,7 @@ import { getSettingsBreadcrumbs } from './breadcrumbs'; import { I18nContext } from 'ui/i18n'; import uiRoutes from 'ui/routes'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; const template = `
diff --git a/x-pack/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js b/x-pack/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js index cf101256724e9..821b3379a6fda 100644 --- a/x-pack/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js +++ b/x-pack/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js @@ -8,7 +8,7 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../common/timefilter'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); diff --git a/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart_directive.js b/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart_directive.js index e4bfe294f91e3..19b86d55156eb 100644 --- a/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart_directive.js +++ b/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart_directive.js @@ -17,7 +17,7 @@ import ReactDOM from 'react-dom'; import { TimeseriesChart } from './timeseries_chart'; import angular from 'angular'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../../../common/timefilter'; import { ResizeChecker } from 'ui/resize_checker'; diff --git a/x-pack/plugins/ml/public/timeseriesexplorer/timeseriesexplorer_controller.js b/x-pack/plugins/ml/public/timeseriesexplorer/timeseriesexplorer_controller.js index 1224edbc441e0..eb6b97c20c7af 100644 --- a/x-pack/plugins/ml/public/timeseriesexplorer/timeseriesexplorer_controller.js +++ b/x-pack/plugins/ml/public/timeseriesexplorer/timeseriesexplorer_controller.js @@ -23,7 +23,7 @@ import 'plugins/ml/components/controls'; import { toastNotifications } from 'ui/notify'; import uiRoutes from 'ui/routes'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { parseInterval } from 'ui/utils/parse_interval'; import { checkFullLicense } from 'plugins/ml/license/check_license'; import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege'; @@ -678,9 +678,6 @@ module.controller('MlTimeSeriesExplorerController', function ( }, 0); }; - // Refresh the data when the time range is altered. - $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); refreshWatcher.init(() => { @@ -697,6 +694,9 @@ module.controller('MlTimeSeriesExplorerController', function ( const intervalSub = interval$.subscribe(tableControlsListener); const severitySub = severity$.subscribe(tableControlsListener); const annotationsRefreshSub = annotationsRefresh$.subscribe($scope.refresh); + const timefilterSubscriber = timefilter.subscribeToUpdates(function triggerRelad() { + $scope.refresh(); + }); // Listen for changes to job selection. const jobSelectServiceSub = mlJobSelectService.subscribe(({ selection }) => { // Clear the detectorIndex, entities and forecast info. @@ -717,6 +717,7 @@ module.controller('MlTimeSeriesExplorerController', function ( severitySub.unsubscribe(); annotationsRefreshSub.unsubscribe(); jobSelectServiceSub.unsubscribe(); + timefilterSubscriber.unsubscribe(); }); $scope.$on('contextChartSelected', function (event, selection) { diff --git a/x-pack/plugins/ml/public/util/chart_utils.js b/x-pack/plugins/ml/public/util/chart_utils.js index c73c89ad8c16c..32355fa2b8b31 100644 --- a/x-pack/plugins/ml/public/util/chart_utils.js +++ b/x-pack/plugins/ml/public/util/chart_utils.js @@ -13,7 +13,7 @@ import moment from 'moment'; import rison from 'rison-node'; import chrome from 'ui/chrome'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { CHART_TYPE } from '../explorer/explorer_constants'; diff --git a/x-pack/plugins/ml/public/util/chart_utils.test.js b/x-pack/plugins/ml/public/util/chart_utils.test.js index 6699512cbd37c..db689466322e5 100644 --- a/x-pack/plugins/ml/public/util/chart_utils.test.js +++ b/x-pack/plugins/ml/public/util/chart_utils.test.js @@ -45,7 +45,7 @@ import moment from 'moment'; import { mount } from 'enzyme'; import React from 'react'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; import { getExploreSeriesLink, diff --git a/x-pack/plugins/ml/public/util/refresh_interval_watcher.js b/x-pack/plugins/ml/public/util/refresh_interval_watcher.js index 2ddcb15bf933b..ec5a1f0269491 100644 --- a/x-pack/plugins/ml/public/util/refresh_interval_watcher.js +++ b/x-pack/plugins/ml/public/util/refresh_interval_watcher.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { timefilter } from 'ui/timefilter'; +import { timefilter } from '../../common/timefilter'; /* * Watches for changes to the refresh interval of the page time filter, @@ -15,6 +15,7 @@ export function refreshIntervalWatcher($timeout) { let refresher; let listener; + let timefilterSubscriber; const onRefreshIntervalChange = () => { if (refresher) { @@ -34,11 +35,14 @@ export function refreshIntervalWatcher($timeout) { function init(listenerCallback) { listener = listenerCallback; - timefilter.on('refreshIntervalUpdate', onRefreshIntervalChange); + timefilterSubscriber = timefilter.subscribeToRefreshIntervalUpdate(function triggerUpdate() { + onRefreshIntervalChange(); + }); } function cancel() { $timeout.cancel(refresher); + timefilterSubscriber.unsubscribe(); timefilter.off('refreshIntervalUpdate', onRefreshIntervalChange); }