[ML] NavMenu conversion to React follow-up (#41054)

* remove old nav_menu direcory

* Bit of cleanup of types and in datavis controller

* replace angular service with importable observable

* wrap tabs in links
This commit is contained in:
Melissa Alvarez 2019-07-15 13:16:17 -04:00 committed by GitHub
parent 5875be54e2
commit 01d8f93c76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 58 additions and 149 deletions

View file

@ -1 +0,0 @@
@import 'nav_menu'

View file

@ -1,4 +0,0 @@
.disabled-nav-link {
color: $euiColorMediumShade;
pointer-events: none;
}

View file

@ -1,51 +0,0 @@
<kbn-top-nav name="dashboard" config="topNavMenu">
<div data-transclude-slots>
<!-- Tabs -->
<div data-transclude-slot="bottomRow">
<div ng-if="showTabs" class="kuiLocalTabs" role="tablist">
<a kbn-href="#/jobs" class="kuiLocalTab" role="tab" data-test-subj="mlTabJobManagement"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('jobs'), 'disabled-nav-link': disableLinks}">
<span
i18n-id="xpack.ml.navMenu.jobManagementTabLinkText"
i18n-default-message="Job Management"
></span>
</a>
<a kbn-href="#/explorer" class="kuiLocalTab" role="tab" data-test-subj="mlTabAnomalyExplorer"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('explorer'), 'disabled-nav-link': disableLinks}">
<span
i18n-id="xpack.ml.navMenu.anomalyExplorerTabLinkText"
i18n-default-message="Anomaly Explorer"
></span>
</a>
<a kbn-href="#/timeseriesexplorer" class="kuiLocalTab" role="tab" data-test-subj="mlTabSingleMetricViewer"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('timeseriesexplorer'), 'disabled-nav-link': disableLinks}">
<span
i18n-id="xpack.ml.navMenu.singleMetricViewerTabLinkText"
i18n-default-message="Single Metric Viewer"
></span>
</a>
<a kbn-href="#/data_frames" class="kuiLocalTab" role="tab" data-test-subj="mlTabDataFrames"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('data_frame')}">
<span
i18n-id="xpack.ml.navMenu.dataFrameTabLinkText"
i18n-default-message="Data Frames"
></span>
</a>
<a kbn-href="#/datavisualizer" class="kuiLocalTab" role="tab" data-test-subj="mlTabDataVisualizer"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('datavisualizer')}">
<span
i18n-id="xpack.ml.navMenu.dataVisualizerTabLinkText"
i18n-default-message="Data Visualizer"
></span>
</a>
<a kbn-href="#/settings" class="kuiLocalTab" role="tab" data-test-subj="mlTabSettings"
ng-class="{'kuiLocalTab-isSelected': isActiveTab('settings'), 'disabled-nav-link': disableLinks}">
<span
i18n-id="xpack.ml.navMenu.settingsTabLinkText"
i18n-default-message="Settings"
></span>
</a>
</div>
</div>
</div>
</kbn-top-nav>

View file

@ -1,44 +0,0 @@
/*
* 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 template from './nav_menu.html';
import { isFullLicense } from '../../license/check_license';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
import 'ui/directives/kbn_href';
module.directive('mlNavMenu', function () {
return {
restrict: 'E',
transclude: true,
template,
link: function (scope, el, attrs) {
// Tabs
scope.name = attrs.name;
scope.showTabs = false;
if (scope.name === 'jobs' ||
scope.name === 'settings' ||
scope.name === 'data_frame' ||
scope.name === 'datavisualizer' ||
scope.name === 'filedatavisualizer' ||
scope.name === 'timeseriesexplorer' ||
scope.name === 'access-denied' ||
scope.name === 'explorer') {
scope.showTabs = true;
}
scope.isActiveTab = function (path) {
return scope.name === path;
};
scope.disableLinks = (isFullLicense() === false);
}
};
});

View file

@ -12,13 +12,13 @@ import { isFullLicense } from '../../license/check_license';
import { timeHistory } from 'ui/timefilter/time_history';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
import { Subject } from 'rxjs';
const module = uiModules.get('apps/ml');
import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service';
import 'ui/directives/kbn_href';
module.directive('mlNavMenu', function (config, mlTimefilterRefreshService) {
module.directive('mlNavMenu', function (config) {
return {
restrict: 'E',
transclude: true,
@ -44,7 +44,7 @@ module.directive('mlNavMenu', function (config, mlTimefilterRefreshService) {
tabId: name,
timeHistory,
timefilter,
forceRefresh: () => mlTimefilterRefreshService.next()
forceRefresh: () => mlTimefilterRefresh$.next()
};
ReactDOM.render(React.createElement(NavigationMenu, props),
@ -57,7 +57,4 @@ module.directive('mlNavMenu', function (config, mlTimefilterRefreshService) {
});
}
};
})
.service('mlTimefilterRefreshService', function () {
return new Subject();
});
});

View file

@ -5,7 +5,7 @@
*/
import React, { FC, useState } from 'react';
import { EuiTabs, EuiTab } from '@elastic/eui';
import { EuiTabs, EuiTab, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import chrome from 'ui/chrome';
@ -15,10 +15,6 @@ interface Tab {
disabled: boolean;
}
interface TestSubjMap {
[key: string]: string;
}
interface Props {
disableLinks: boolean;
tabId: string;
@ -71,23 +67,20 @@ function getTabs(disableLinks: boolean): Tab[] {
];
}
const TAB_TEST_SUBJ_MAP: TestSubjMap = {
jobs: 'mlTabJobManagement',
explorer: 'mlTabAnomalyExplorer',
timeseriesexplorer: 'mlTabSingleMetricViewer',
data_frames: 'mlTabDataFrames',
datavisualizer: 'mlTabDataVisualizer',
settings: 'mlTabSettings',
};
function moveToSelectedTab(selectedTabId: string) {
window.location.href = `${chrome.getBasePath()}/app/ml#/${selectedTabId}`;
enum TAB_TEST_SUBJECT {
jobs = 'mlTabJobManagement',
explorer = 'mlTabAnomalyExplorer',
timeseriesexplorer = 'mlTabSingleMetricViewer',
data_frames = 'mlTabDataFrames', // eslint-disable-line
datavisualizer = 'mlTabDataVisualizer',
settings = 'mlTabSettings',
}
type TAB_TEST_SUBJECTS = keyof typeof TAB_TEST_SUBJECT;
export const Tabs: FC<Props> = ({ tabId, disableLinks }) => {
const [selectedTabId, setSelectedTabId] = useState(tabId);
function onSelectedTabChanged(id: string) {
moveToSelectedTab(id);
setSelectedTabId(id);
}
@ -95,18 +88,26 @@ export const Tabs: FC<Props> = ({ tabId, disableLinks }) => {
return (
<EuiTabs>
{tabs.map((tab: Tab) => (
<EuiTab
className="mlNavigationMenu__tab"
onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId}
disabled={tab.disabled}
key={`${tab.id}-key`}
data-test-subj={TAB_TEST_SUBJ_MAP[tab.id]}
>
{tab.name}
</EuiTab>
))}
{tabs.map((tab: Tab) => {
const id = tab.id;
return (
<EuiLink
data-test-subj={TAB_TEST_SUBJECT[id as TAB_TEST_SUBJECTS]}
href={`${chrome.getBasePath()}/app/ml#/${id}`}
key={`${id}-key`}
color="text"
>
<EuiTab
className="mlNavigationMenu__tab"
onClick={() => onSelectedTabChanged(id)}
isSelected={id === selectedTabId}
disabled={tab.disabled}
>
{tab.name}
</EuiTab>
</EuiLink>
);
})}
</EuiTabs>
);
};

View file

@ -16,7 +16,12 @@ interface Props {
timefilter: Timefilter;
}
function getRecentlyUsedRanges(timeHistory: TimeHistory): Array<{ start: string; end: string }> {
interface Duration {
start: string;
end: string;
}
function getRecentlyUsedRanges(timeHistory: TimeHistory): Duration[] {
return timeHistory.get().map(({ from, to }: TimeRange) => {
return {
start: from,
@ -60,7 +65,7 @@ export const TopNav: FC<Props> = ({ dateFormat, forceRefresh, timeHistory, timef
setIsTimeRangeSelectorEnabled(timefilter.isTimeRangeSelectorEnabled);
}
function updateFilter({ start, end }: { start: string; end: string }) {
function updateFilter({ start, end }: Duration) {
const newTime = { from: start, to: end };
// Update timefilter for controllers listening for changes
timefilter.setTime(newTime);

View file

@ -33,6 +33,7 @@ import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck }
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './datavisualizer.html';
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
uiRoutes
.when('/jobs/new_job/datavisualizer', {
@ -52,11 +53,16 @@ import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module
.controller('MlDataVisualizerViewFields', function ($injector, $scope, $timeout, $window, Private, AppState, config) {
.controller('MlDataVisualizerViewFields', function (
$scope,
$timeout,
$window,
Private,
AppState,
config) {
timefilter.enableTimeRangeSelector();
timefilter.enableAutoRefreshSelector();
const mlTimefilterRefreshService = $injector.get('mlTimefilterRefreshService');
const createSearchItems = Private(SearchItemsProvider);
const {
@ -152,11 +158,9 @@ module
}
// Refresh the data when the time range is altered.
$scope.$listenAndDigestAsync(timefilter, 'fetch', function () {
refresh();
});
$scope.$listenAndDigestAsync(timefilter, 'fetch', refresh);
const timefilterRefreshServiceSub = mlTimefilterRefreshService.subscribe(refresh);
const timefilterRefreshServiceSub = mlTimefilterRefresh$.subscribe(refresh);
$scope.$on('$destroy', () => {
timefilterRefreshServiceSub.unsubscribe();

View file

@ -30,6 +30,7 @@ import { checkGetJobsPrivilege } from '../privilege/check_privilege';
import { getIndexPatterns, loadIndexPatterns } from '../util/index_utils';
import { MlTimeBuckets } from 'plugins/ml/util/ml_time_buckets';
import { explorer$ } from './explorer_dashboard_service';
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
import { mlJobService } from '../services/job_service';
import { refreshIntervalWatcher } from '../util/refresh_interval_watcher';
@ -71,7 +72,6 @@ module.controller('MlExplorerController', function (
$injector.get('mlSelectSeverityService');
const mlJobSelectService = $injector.get('mlJobSelectService');
const mlTimefilterRefreshService = $injector.get('mlTimefilterRefreshService');
// $scope should only contain what's actually still necessary for the angular part.
// For the moment that's the job selector and the (hidden) filter bar.
@ -204,7 +204,7 @@ module.controller('MlExplorerController', function (
}
});
const timefilterRefreshServiceSub = mlTimefilterRefreshService.subscribe(() => {
const timefilterRefreshServiceSub = mlTimefilterRefresh$.subscribe(() => {
if ($scope.jobSelectionUpdateInProgress === false) {
explorer$.next({ action: EXPLORER_ACTION.RELOAD });
}

View file

@ -4,4 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './nav_menu';
import { Subject } from 'rxjs';
export const mlTimefilterRefresh$ = new Subject();

View file

@ -60,6 +60,7 @@ import { annotationsRefresh$ } from '../services/annotations_service';
import { interval$ } from '../components/controls/select_interval/select_interval';
import { severity$ } from '../components/controls/select_severity/select_severity';
import { setGlobalState, getSelectedJobIds } from '../components/job_selector/job_select_service_utils';
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
import chrome from 'ui/chrome';
@ -93,7 +94,6 @@ module.controller('MlTimeSeriesExplorerController', function (
$injector.get('mlSelectIntervalService');
$injector.get('mlSelectSeverityService');
const mlJobSelectService = $injector.get('mlJobSelectService');
const mlTimefilterRefreshService = $injector.get('mlTimefilterRefreshService');
$scope.timeFieldName = 'timestamp';
timefilter.enableTimeRangeSelector();
@ -712,7 +712,7 @@ module.controller('MlTimeSeriesExplorerController', function (
}
});
const timefilterRefreshServiceSub = mlTimefilterRefreshService.subscribe($scope.refresh);
const timefilterRefreshServiceSub = mlTimefilterRefresh$.subscribe($scope.refresh);
$scope.$on('$destroy', () => {
refreshWatcher.cancel();