diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js
index 20cc30f687003d..16724d5fa60972 100644
--- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js
+++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js
@@ -50,6 +50,7 @@ new CoreSystem({
legacyMetadata: {
version: '1.2.3',
buildNum: 1234,
+ devMode: true,
uiSettings: {
defaults: ${JSON.stringify(defaultUiSettings, null, 2).split('\n').join('\n ')},
user: {}
diff --git a/src/legacy/ui/public/chrome/directives/__tests__/sub_url_route_filter.js b/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js
similarity index 98%
rename from src/legacy/ui/public/chrome/directives/__tests__/sub_url_route_filter.js
rename to src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js
index 5ce42b9c729160..46421fdbd22802 100644
--- a/src/legacy/ui/public/chrome/directives/__tests__/sub_url_route_filter.js
+++ b/src/legacy/ui/public/chrome/api/__tests__/sub_url_route_filter.js
@@ -20,7 +20,7 @@
import ngMock from 'ng_mock';
import expect from '@kbn/expect';
-import { SubUrlRouteFilterProvider } from '../sub_url_route_filter';
+import { SubUrlRouteFilterProvider } from '../sub_url_hooks';
describe('kbn-chrome subUrlRouteFilter()', () => {
describe('no ngRoute', () => {
diff --git a/src/legacy/ui/public/chrome/api/__tests__/xsrf.js b/src/legacy/ui/public/chrome/api/__tests__/xsrf.js
index 52da8fb545c7d5..54255562cb6110 100644
--- a/src/legacy/ui/public/chrome/api/__tests__/xsrf.js
+++ b/src/legacy/ui/public/chrome/api/__tests__/xsrf.js
@@ -17,16 +17,12 @@
* under the License.
*/
-import $ from 'jquery';
import expect from '@kbn/expect';
import sinon from 'sinon';
-import ngMock from 'ng_mock';
import { initChromeXsrfApi } from '../xsrf';
import { version } from '../../../../../utils/package_json';
-const xsrfHeader = 'kbn-version';
-
describe('chrome xsrf apis', function () {
const sandbox = sinon.createSandbox();
@@ -41,116 +37,4 @@ describe('chrome xsrf apis', function () {
expect(chrome.getXsrfToken()).to.be(version);
});
});
-
- describe('jQuery support', function () {
- it('adds a global jQuery prefilter', function () {
- sandbox.stub($, 'ajaxPrefilter');
- initChromeXsrfApi({}, { version });
- expect($.ajaxPrefilter.callCount).to.be(1);
- });
-
- describe('jQuery prefilter', function () {
- let prefilter;
-
- beforeEach(function () {
- sandbox.stub($, 'ajaxPrefilter');
- initChromeXsrfApi({}, { version });
- prefilter = $.ajaxPrefilter.args[0][0];
- });
-
- it(`sets the ${xsrfHeader} header`, function () {
- const setHeader = sinon.stub();
- prefilter({}, {}, { setRequestHeader: setHeader });
-
- expect(setHeader.callCount).to.be(1);
- expect(setHeader.args[0]).to.eql([
- xsrfHeader,
- version
- ]);
- });
-
- it('can be canceled by setting the kbnXsrfToken option', function () {
- const setHeader = sinon.stub();
- prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader });
- expect(setHeader.callCount).to.be(0);
- });
- });
-
- describe('Angular support', function () {
-
- let $http;
- let $httpBackend;
-
- beforeEach(function () {
- sandbox.stub($, 'ajaxPrefilter');
- const chrome = {};
- initChromeXsrfApi(chrome, { version });
- ngMock.module(chrome.$setupXsrfRequestInterceptor);
- });
-
- beforeEach(ngMock.inject(function ($injector) {
- $http = $injector.get('$http');
- $httpBackend = $injector.get('$httpBackend');
-
- $httpBackend
- .when('POST', '/api/test')
- .respond('ok');
- }));
-
- afterEach(function () {
- $httpBackend.verifyNoOutstandingExpectation();
- $httpBackend.verifyNoOutstandingRequest();
- });
-
- it(`injects a ${xsrfHeader} header on every request`, function () {
- $httpBackend.expectPOST('/api/test', undefined, function (headers) {
- return headers[xsrfHeader] === version;
- }).respond(200, '');
-
- $http.post('/api/test');
- $httpBackend.flush();
- });
-
- it('skips requests with the kbnXsrfToken set falsy', function () {
- $httpBackend.expectPOST('/api/test', undefined, function (headers) {
- return !(xsrfHeader in headers);
- }).respond(200, '');
-
- $http({
- method: 'POST',
- url: '/api/test',
- kbnXsrfToken: 0
- });
-
- $http({
- method: 'POST',
- url: '/api/test',
- kbnXsrfToken: ''
- });
-
- $http({
- method: 'POST',
- url: '/api/test',
- kbnXsrfToken: false
- });
-
- $httpBackend.flush();
- });
-
- it('treats the kbnXsrfToken option as boolean-y', function () {
- const customToken = `custom:${version}`;
- $httpBackend.expectPOST('/api/test', undefined, function (headers) {
- return headers[xsrfHeader] === version;
- }).respond(200, '');
-
- $http({
- method: 'POST',
- url: '/api/test',
- kbnXsrfToken: customToken
- });
-
- $httpBackend.flush();
- });
- });
- });
});
diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js
index 145653ad261128..d01ed1a355614e 100644
--- a/src/legacy/ui/public/chrome/api/angular.js
+++ b/src/legacy/ui/public/chrome/api/angular.js
@@ -17,125 +17,23 @@
* under the License.
*/
-import React, { Fragment } from 'react';
-import _ from 'lodash';
-import { modifyUrl } from 'ui/url';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-
import { uiModules } from '../../modules';
-import { toastNotifications } from '../../notify';
-import { UrlOverflowServiceProvider } from '../../error_url_overflow';
import { directivesProvider } from '../directives';
-
-const URL_LIMIT_WARN_WITHIN = 1000;
+import { registerSubUrlHooks } from './sub_url_hooks';
+import { configureAppAngularModule } from 'ui/legacy_compat';
export function initAngularApi(chrome, internals) {
- chrome.getFirstPathSegment = _.noop;
-
chrome.setupAngular = function () {
const kibana = uiModules.get('kibana');
- _.forOwn(chrome.getInjected(), function (val, name) {
- kibana.value(name, val);
- });
+ configureAppAngularModule(kibana);
kibana
- .value('kbnVersion', internals.version)
- .value('buildNum', internals.buildNum)
- .value('buildSha', internals.buildSha)
- .value('serverName', internals.serverName)
- .value('sessionId', Date.now())
.value('chrome', chrome)
- .value('esUrl', (function () {
- const a = document.createElement('a');
- a.href = chrome.addBasePath('/elasticsearch');
- const protocolPort = /https/.test(a.protocol) ? 443 : 80;
- const port = a.port || protocolPort;
- return {
- host: a.hostname,
- port,
- protocol: a.protocol,
- pathname: a.pathname
- };
- }()))
- .config($locationProvider => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
- })
- .config(chrome.$setupXsrfRequestInterceptor)
- .config(function ($compileProvider, $locationProvider) {
- if (!internals.devMode) {
- $compileProvider.debugInfoEnabled(false);
- }
-
- $locationProvider.hashPrefix('');
- })
- .run(internals.capture$httpLoadingCount)
- .run(internals.$setupBreadcrumbsAutoClear)
- .run(internals.$setupHelpExtensionAutoClear)
- .run(internals.$initNavLinksDeepWatch)
- .run(($location, $rootScope, Private, config) => {
- chrome.getFirstPathSegment = () => {
- return $location.path().split('/')[1];
- };
-
- const urlOverflow = Private(UrlOverflowServiceProvider);
- const check = () => {
- // disable long url checks when storing state in session storage
- if (config.get('state:storeInSessionStorage')) {
- return;
- }
-
- if ($location.path() === '/error/url-overflow') {
- return;
- }
-
- try {
- if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) {
- toastNotifications.addWarning({
- title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', {
- defaultMessage: 'The URL is big and Kibana might stop working'
- }),
- text: (
-
- state:storeInSessionStorage,
- advancedSettingsLink: (
-
-
-
- )
- }}
- />
-
- ),
- });
- }
- } catch (e) {
- window.location.href = modifyUrl(window.location.href, parts => {
- parts.hash = '#/error/url-overflow';
- });
- // force the browser to reload to that Kibana's potentially unstable state is unloaded
- window.location.reload();
- }
- };
-
- $rootScope.$on('$routeUpdate', check);
- $rootScope.$on('$routeChangeStart', check);
- });
+ .run(internals.$initNavLinksDeepWatch);
+ registerSubUrlHooks(kibana, internals);
directivesProvider(chrome, internals);
uiModules.link(kibana);
diff --git a/src/legacy/ui/public/chrome/api/breadcrumbs.ts b/src/legacy/ui/public/chrome/api/breadcrumbs.ts
index e8995edf46d6d8..87201d0e758c16 100644
--- a/src/legacy/ui/public/chrome/api/breadcrumbs.ts
+++ b/src/legacy/ui/public/chrome/api/breadcrumbs.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-import { IRootScopeService } from 'angular';
-import { fatalError } from 'ui/notify/fatal_error';
import { ChromeBreadcrumb, ChromeSetup } from '../../../../../core/public';
export type Breadcrumb = ChromeBreadcrumb;
@@ -34,16 +32,12 @@ export function __newPlatformInit__(instance: ChromeSetup) {
}
function createBreadcrumbsApi(chrome: { [key: string]: any }) {
- // A flag used to determine if we should automatically
- // clear the breadcrumbs between angular route changes.
- let breadcrumbSetSinceRouteChange = false;
let currentBreadcrumbs: Breadcrumb[] = [];
// reset breadcrumbSetSinceRouteChange any time the breadcrumbs change, even
// if it was done directly through the new platform
newPlatformChrome.getBreadcrumbs$().subscribe({
next(nextBreadcrumbs) {
- breadcrumbSetSinceRouteChange = true;
currentBreadcrumbs = nextBreadcrumbs;
},
});
@@ -79,39 +73,6 @@ function createBreadcrumbsApi(chrome: { [key: string]: any }) {
newPlatformChrome.setBreadcrumbs(currentBreadcrumbs.filter(fn));
},
},
-
- /**
- * internal angular run function that will be called when angular bootstraps and
- * lets us integrate with the angular router so that we can automatically clear
- * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly
- */
- $setupBreadcrumbsAutoClear: ($rootScope: IRootScopeService, $injector: any) => {
- const $route = $injector.has('$route') ? $injector.get('$route') : {};
-
- $rootScope.$on('$routeChangeStart', () => {
- breadcrumbSetSinceRouteChange = false;
- });
-
- $rootScope.$on('$routeChangeSuccess', () => {
- const current = $route.current || {};
-
- if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
- return;
- }
-
- const k7BreadcrumbsProvider = current.k7Breadcrumbs;
- if (!k7BreadcrumbsProvider) {
- newPlatformChrome.setBreadcrumbs([]);
- return;
- }
-
- try {
- chrome.breadcrumbs.set($injector.invoke(k7BreadcrumbsProvider));
- } catch (error) {
- fatalError(error);
- }
- });
- },
};
}
@@ -119,7 +80,6 @@ export function initBreadcrumbsApi(
chrome: { [key: string]: any },
internals: { [key: string]: any }
) {
- const { breadcrumbs, $setupBreadcrumbsAutoClear } = createBreadcrumbsApi(chrome);
+ const { breadcrumbs } = createBreadcrumbsApi(chrome);
chrome.breadcrumbs = breadcrumbs;
- internals.$setupBreadcrumbsAutoClear = $setupBreadcrumbsAutoClear;
}
diff --git a/src/legacy/ui/public/chrome/api/help_extension.ts b/src/legacy/ui/public/chrome/api/help_extension.ts
index a51df213c681ac..2fafae89f2c9ea 100644
--- a/src/legacy/ui/public/chrome/api/help_extension.ts
+++ b/src/legacy/ui/public/chrome/api/help_extension.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-import { IRootScopeService } from 'angular';
-
import { ChromeHelpExtension, ChromeSetup } from '../../../../../core/public';
let newPlatformChrome: ChromeSetup;
@@ -34,17 +32,6 @@ export type HelpExtensionApi = ReturnType['helpEx
export type HelpExtension = ChromeHelpExtension;
function createHelpExtensionApi() {
- /**
- * reset helpExtensionSetSinceRouteChange any time the helpExtension changes, even
- * if it was done directly through the new platform
- */
- let helpExtensionSetSinceRouteChange = false;
- newPlatformChrome.getHelpExtension$().subscribe({
- next() {
- helpExtensionSetSinceRouteChange = true;
- },
- });
-
return {
helpExtension: {
/**
@@ -60,30 +47,6 @@ function createHelpExtensionApi() {
*/
get$: () => newPlatformChrome.getHelpExtension$(),
},
-
- /**
- * internal angular run function that will be called when angular bootstraps and
- * lets us integrate with the angular router so that we can automatically clear
- * the helpExtension if we switch to a Kibana app that does not set its own
- * helpExtension
- */
- $setupHelpExtensionAutoClear: ($rootScope: IRootScopeService, $injector: any) => {
- const $route = $injector.has('$route') ? $injector.get('$route') : {};
-
- $rootScope.$on('$routeChangeStart', () => {
- helpExtensionSetSinceRouteChange = false;
- });
-
- $rootScope.$on('$routeChangeSuccess', () => {
- const current = $route.current || {};
-
- if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
- return;
- }
-
- newPlatformChrome.setHelpExtension(current.helpExtension);
- });
- },
};
}
@@ -91,7 +54,6 @@ export function initHelpExtensionApi(
chrome: { [key: string]: any },
internal: { [key: string]: any }
) {
- const { helpExtension, $setupHelpExtensionAutoClear } = createHelpExtensionApi();
+ const { helpExtension } = createHelpExtensionApi();
chrome.helpExtension = helpExtension;
- internal.$setupHelpExtensionAutoClear = $setupHelpExtensionAutoClear;
}
diff --git a/src/legacy/ui/public/chrome/api/injected_vars.ts b/src/legacy/ui/public/chrome/api/injected_vars.ts
index adc3d4ba567e26..112f8d3d6e3d20 100644
--- a/src/legacy/ui/public/chrome/api/injected_vars.ts
+++ b/src/legacy/ui/public/chrome/api/injected_vars.ts
@@ -31,7 +31,7 @@ export function __newPlatformInit__(instance: InjectedMetadataSetup) {
}
export function initChromeInjectedVarsApi(chrome: { [key: string]: any }) {
- chrome.getInjected = (name: string, defaultValue: any) =>
+ chrome.getInjected = (name?: string, defaultValue?: any) =>
cloneDeep(
name
? newPlatformInjectedVars.getInjectedVar(name, defaultValue)
diff --git a/src/legacy/ui/public/chrome/api/loading_count.js b/src/legacy/ui/public/chrome/api/loading_count.js
index 54b31bacc0ce19..979993c76d5a5c 100644
--- a/src/legacy/ui/public/chrome/api/loading_count.js
+++ b/src/legacy/ui/public/chrome/api/loading_count.js
@@ -19,8 +19,6 @@
import * as Rx from 'rxjs';
-import { isSystemApiRequest } from '../../system_api';
-
let newPlatformHttp;
export function __newPlatformInit__(instance) {
@@ -30,27 +28,7 @@ export function __newPlatformInit__(instance) {
newPlatformHttp = instance;
}
-export function initLoadingCountApi(chrome, internals) {
- /**
- * Injected into angular module by ui/chrome angular integration
- * and adds a root-level watcher that will capture the count of
- * active $http requests on each digest loop and expose the count to
- * the core.loadingCount api
- * @param {Angular.Scope} $rootScope
- * @param {HttpService} $http
- * @return {undefined}
- */
- internals.capture$httpLoadingCount = function ($rootScope, $http) {
- newPlatformHttp.addLoadingCount(new Rx.Observable(observer => {
- const unwatch = $rootScope.$watch(() => {
- const reqs = $http.pendingRequests || [];
- observer.next(reqs.filter(req => !isSystemApiRequest(req)).length);
- });
-
- return unwatch;
- }));
- };
-
+export function initLoadingCountApi(chrome) {
const manualCount$ = new Rx.BehaviorSubject(0);
newPlatformHttp.addLoadingCount(manualCount$);
diff --git a/src/legacy/ui/public/chrome/api/sub_url_hooks.js b/src/legacy/ui/public/chrome/api/sub_url_hooks.js
new file mode 100644
index 00000000000000..beb762a1235e6e
--- /dev/null
+++ b/src/legacy/ui/public/chrome/api/sub_url_hooks.js
@@ -0,0 +1,84 @@
+/*
+ * 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 url from 'url';
+
+import {
+ getUnhashableStatesProvider,
+ unhashUrl,
+} from '../../state_management/state_hashing';
+
+export function registerSubUrlHooks(angularModule, internals) {
+ angularModule.run(($rootScope, Private, $location) => {
+ const getUnhashableStates = Private(getUnhashableStatesProvider);
+ const subUrlRouteFilter = Private(SubUrlRouteFilterProvider);
+
+ function updateSubUrls() {
+ const urlWithHashes = window.location.href;
+ const urlWithStates = unhashUrl(urlWithHashes, getUnhashableStates());
+ internals.trackPossibleSubUrl(urlWithStates);
+ }
+
+ function onRouteChange($event) {
+ if (subUrlRouteFilter($event)) {
+ updateSubUrls();
+ }
+ }
+
+ $rootScope.$on('$locationChangeStart', (e, newUrl) => {
+ // This handler fixes issue #31238 where browser back navigation
+ // fails due to angular 1.6 parsing url encoded params wrong.
+ const parsedAbsUrl = url.parse($location.absUrl());
+ const absUrlHash = parsedAbsUrl.hash ? parsedAbsUrl.hash.slice(1) : '';
+ const decodedAbsUrlHash = decodeURIComponent(absUrlHash);
+
+ const parsedNewUrl = url.parse(newUrl);
+ const newHash = parsedNewUrl.hash ? parsedNewUrl.hash.slice(1) : '';
+ const decodedHash = decodeURIComponent(newHash);
+
+ if (absUrlHash !== newHash && decodedHash === decodedAbsUrlHash) {
+ // replace the urlencoded hash with the version that angular sees.
+ $location.url(absUrlHash).replace();
+ }
+ });
+
+ $rootScope.$on('$routeChangeSuccess', onRouteChange);
+ $rootScope.$on('$routeUpdate', onRouteChange);
+ updateSubUrls(); // initialize sub urls
+ });
+}
+
+/**
+ * Creates a function that will be called on each route change
+ * to determine if the event should be used to update the last
+ * subUrl of chrome links/tabs
+ * @injected
+ */
+export function SubUrlRouteFilterProvider($injector) {
+ if (!$injector.has('$route')) {
+ return function alwaysUpdate() {
+ return true;
+ };
+ }
+
+ const $route = $injector.get('$route');
+ return function ignoreRedirectToRoutes() {
+ return Boolean($route.current && !$route.current.redirectTo);
+ };
+}
diff --git a/src/legacy/ui/public/chrome/api/xsrf.js b/src/legacy/ui/public/chrome/api/xsrf.js
index ac59d6367aeae8..5086903604667d 100644
--- a/src/legacy/ui/public/chrome/api/xsrf.js
+++ b/src/legacy/ui/public/chrome/api/xsrf.js
@@ -17,32 +17,8 @@
* under the License.
*/
-import $ from 'jquery';
-import { set } from 'lodash';
-
export function initChromeXsrfApi(chrome, internals) {
-
chrome.getXsrfToken = function () {
return internals.version;
};
-
- $.ajaxPrefilter(function ({ kbnXsrfToken = true }, originalOptions, jqXHR) {
- if (kbnXsrfToken) {
- jqXHR.setRequestHeader('kbn-version', internals.version);
- }
- });
-
- chrome.$setupXsrfRequestInterceptor = function ($httpProvider) {
- $httpProvider.interceptors.push(function () {
- return {
- request: function (opts) {
- const { kbnXsrfToken = true } = opts;
- if (kbnXsrfToken) {
- set(opts, ['headers', 'kbn-version'], internals.version);
- }
- return opts;
- }
- };
- });
- };
}
diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.html b/src/legacy/ui/public/chrome/directives/kbn_chrome.html
index c1cfcd77021d43..e282a898ff0db3 100644
--- a/src/legacy/ui/public/chrome/directives/kbn_chrome.html
+++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.html
@@ -18,7 +18,7 @@
diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js
index 7d7f703ccc8805..5bc3d8a28bac8c 100644
--- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js
+++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js
@@ -20,19 +20,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
-import url from 'url';
import { uiModules } from '../../modules';
-import {
- getUnhashableStatesProvider,
- unhashUrl,
-} from '../../state_management/state_hashing';
+
import {
notify,
GlobalBannerList,
banners,
} from '../../notify';
-import { SubUrlRouteFilterProvider } from './sub_url_route_filter';
+
import { I18nContext } from '../../i18n';
export function kbnChromeProvider(chrome, internals) {
@@ -58,42 +54,14 @@ export function kbnChromeProvider(chrome, internals) {
},
controllerAs: 'chrome',
- controller($scope, $rootScope, Private, $location) {
- const getUnhashableStates = Private(getUnhashableStatesProvider);
- const subUrlRouteFilter = Private(SubUrlRouteFilterProvider);
-
- function updateSubUrls() {
- const urlWithHashes = window.location.href;
- const urlWithStates = unhashUrl(urlWithHashes, getUnhashableStates());
- internals.trackPossibleSubUrl(urlWithStates);
- }
-
- function onRouteChange($event) {
- if (subUrlRouteFilter($event)) {
- updateSubUrls();
- }
- }
-
- $rootScope.$on('$locationChangeStart', (e, newUrl) => {
- // This handler fixes issue #31238 where browser back navigation
- // fails due to angular 1.6 parsing url encoded params wrong.
- const absUrlHash = url.parse($location.absUrl()).hash.slice(1);
- const decodedAbsUrlHash = decodeURIComponent(absUrlHash);
- const hash = url.parse(newUrl).hash.slice(1);
- const decodedHash = decodeURIComponent(hash);
- if (absUrlHash !== hash && decodedHash === decodedAbsUrlHash) {
- // replace the urlencoded hash with the version that angular sees.
- $location.url(absUrlHash).replace();
- }
- });
-
- $rootScope.$on('$routeChangeSuccess', onRouteChange);
- $rootScope.$on('$routeUpdate', onRouteChange);
- updateSubUrls(); // initialize sub urls
-
+ controller($scope, $location) {
// Notifications
$scope.notifList = notify._notifs;
+ $scope.getFirstPathSegment = () => {
+ return $location.path().split('/')[1];
+ };
+
// Non-scope based code (e.g., React)
// Banners
diff --git a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js
new file mode 100644
index 00000000000000..9073d7588ddcf7
--- /dev/null
+++ b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js
@@ -0,0 +1,153 @@
+/*
+ * 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 'jquery';
+import expect from '@kbn/expect';
+import sinon from 'sinon';
+import ngMock from 'ng_mock';
+
+import { $setupXsrfRequestInterceptor } from '../angular_config';
+import { version } from '../../../../utils/package_json';
+
+const xsrfHeader = 'kbn-version';
+const newPlatform = {
+ injectedMetadata: {
+ getLegacyMetadata() {
+ return { version };
+ }
+ }
+};
+
+describe('chrome xsrf apis', function () {
+ const sandbox = sinon.createSandbox();
+
+ afterEach(function () {
+ sandbox.restore();
+ });
+
+ describe('jQuery support', function () {
+ it('adds a global jQuery prefilter', function () {
+ sandbox.stub($, 'ajaxPrefilter');
+ $setupXsrfRequestInterceptor(newPlatform);
+ expect($.ajaxPrefilter.callCount).to.be(1);
+ });
+
+ describe('jQuery prefilter', function () {
+ let prefilter;
+
+ beforeEach(function () {
+ sandbox.stub($, 'ajaxPrefilter');
+ $setupXsrfRequestInterceptor(newPlatform);
+ prefilter = $.ajaxPrefilter.args[0][0];
+ });
+
+ it(`sets the ${xsrfHeader} header`, function () {
+ const setHeader = sinon.stub();
+ prefilter({}, {}, { setRequestHeader: setHeader });
+
+ expect(setHeader.callCount).to.be(1);
+ expect(setHeader.args[0]).to.eql([
+ xsrfHeader,
+ version
+ ]);
+ });
+
+ it('can be canceled by setting the kbnXsrfToken option', function () {
+ const setHeader = sinon.stub();
+ prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader });
+ expect(setHeader.callCount).to.be(0);
+ });
+ });
+
+ describe('Angular support', function () {
+
+ let $http;
+ let $httpBackend;
+
+ beforeEach(function () {
+ sandbox.stub($, 'ajaxPrefilter');
+ ngMock.module($setupXsrfRequestInterceptor(newPlatform));
+ });
+
+ beforeEach(ngMock.inject(function ($injector) {
+ $http = $injector.get('$http');
+ $httpBackend = $injector.get('$httpBackend');
+
+ $httpBackend
+ .when('POST', '/api/test')
+ .respond('ok');
+ }));
+
+ afterEach(function () {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it(`injects a ${xsrfHeader} header on every request`, function () {
+ $httpBackend.expectPOST('/api/test', undefined, function (headers) {
+ return headers[xsrfHeader] === version;
+ }).respond(200, '');
+
+ $http.post('/api/test');
+ $httpBackend.flush();
+ });
+
+ it('skips requests with the kbnXsrfToken set falsy', function () {
+ $httpBackend.expectPOST('/api/test', undefined, function (headers) {
+ return !(xsrfHeader in headers);
+ }).respond(200, '');
+
+ $http({
+ method: 'POST',
+ url: '/api/test',
+ kbnXsrfToken: 0
+ });
+
+ $http({
+ method: 'POST',
+ url: '/api/test',
+ kbnXsrfToken: ''
+ });
+
+ $http({
+ method: 'POST',
+ url: '/api/test',
+ kbnXsrfToken: false
+ });
+
+ $httpBackend.flush();
+ });
+
+ it('treats the kbnXsrfToken option as boolean-y', function () {
+ const customToken = `custom:${version}`;
+ $httpBackend.expectPOST('/api/test', undefined, function (headers) {
+ return headers[xsrfHeader] === version;
+ }).respond(200, '');
+
+ $http({
+ method: 'POST',
+ url: '/api/test',
+ kbnXsrfToken: customToken
+ });
+
+ $httpBackend.flush();
+ });
+ });
+ });
+});
diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx
new file mode 100644
index 00000000000000..18791372a5c15b
--- /dev/null
+++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx
@@ -0,0 +1,300 @@
+/*
+ * 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 {
+ ICompileProvider,
+ IHttpProvider,
+ IHttpService,
+ ILocationProvider,
+ ILocationService,
+ IModule,
+ IRootScopeService,
+} from 'angular';
+import $ from 'jquery';
+import { cloneDeep, forOwn, set } from 'lodash';
+import React, { Fragment } from 'react';
+import * as Rx from 'rxjs';
+
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { CoreSetup } from 'kibana/public';
+
+import { fatalError } from 'ui/notify';
+// @ts-ignore
+import { modifyUrl } from 'ui/url';
+// @ts-ignore
+import { UrlOverflowServiceProvider } from '../error_url_overflow';
+import { getNewPlatform } from '../new_platform';
+import { toastNotifications } from '../notify';
+// @ts-ignore
+import { isSystemApiRequest } from '../system_api';
+
+const URL_LIMIT_WARN_WITHIN = 1000;
+
+export const configureAppAngularModule = (angularModule: IModule) => {
+ const newPlatform = getNewPlatform().setup.core;
+ const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata();
+
+ forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => {
+ if (name !== undefined) {
+ // The legacy platform modifies some of these values, clone to an unfrozen object.
+ angularModule.value(name, cloneDeep(val));
+ }
+ });
+
+ angularModule
+ .value('kbnVersion', newPlatform.injectedMetadata.getKibanaVersion())
+ .value('buildNum', legacyMetadata.buildNum)
+ .value('buildSha', legacyMetadata.buildSha)
+ .value('serverName', legacyMetadata.serverName)
+ .value('sessionId', Date.now())
+ .value('esUrl', getEsUrl(newPlatform))
+ .config(setupCompileProvider(newPlatform))
+ .config(setupLocationProvider(newPlatform))
+ .config($setupXsrfRequestInterceptor(newPlatform))
+ .run(capture$httpLoadingCount(newPlatform))
+ .run($setupBreadcrumbsAutoClear(newPlatform))
+ .run($setupHelpExtensionAutoClear(newPlatform))
+ .run($setupUrlOverflowHandling(newPlatform));
+};
+
+const getEsUrl = (newPlatform: CoreSetup) => {
+ const a = document.createElement('a');
+ a.href = newPlatform.basePath.addToPath('/elasticsearch');
+ const protocolPort = /https/.test(a.protocol) ? 443 : 80;
+ const port = a.port || protocolPort;
+ return {
+ host: a.hostname,
+ port,
+ protocol: a.protocol,
+ pathname: a.pathname,
+ };
+};
+
+const setupCompileProvider = (newPlatform: CoreSetup) => ($compileProvider: ICompileProvider) => {
+ if (!newPlatform.injectedMetadata.getLegacyMetadata().devMode) {
+ $compileProvider.debugInfoEnabled(false);
+ }
+};
+
+const setupLocationProvider = (newPlatform: CoreSetup) => (
+ $locationProvider: ILocationProvider
+) => {
+ $locationProvider.html5Mode({
+ enabled: false,
+ requireBase: false,
+ rewriteLinks: false,
+ });
+
+ $locationProvider.hashPrefix('');
+};
+
+export const $setupXsrfRequestInterceptor = (newPlatform: CoreSetup) => {
+ const version = newPlatform.injectedMetadata.getLegacyMetadata().version;
+
+ // Configure jQuery prefilter
+ $.ajaxPrefilter(({ kbnXsrfToken = true }: any, originalOptions, jqXHR) => {
+ if (kbnXsrfToken) {
+ jqXHR.setRequestHeader('kbn-version', version);
+ }
+ });
+
+ return ($httpProvider: IHttpProvider) => {
+ // Configure $httpProvider interceptor
+ $httpProvider.interceptors.push(() => {
+ return {
+ request(opts) {
+ const { kbnXsrfToken = true } = opts as any;
+ if (kbnXsrfToken) {
+ set(opts, ['headers', 'kbn-version'], version);
+ }
+ return opts;
+ },
+ };
+ });
+ };
+};
+
+/**
+ * Injected into angular module by ui/chrome angular integration
+ * and adds a root-level watcher that will capture the count of
+ * active $http requests on each digest loop and expose the count to
+ * the core.loadingCount api
+ * @param {Angular.Scope} $rootScope
+ * @param {HttpService} $http
+ * @return {undefined}
+ */
+const capture$httpLoadingCount = (newPlatform: CoreSetup) => (
+ $rootScope: IRootScopeService,
+ $http: IHttpService
+) => {
+ newPlatform.http.addLoadingCount(
+ new Rx.Observable(observer => {
+ const unwatch = $rootScope.$watch(() => {
+ const reqs = $http.pendingRequests || [];
+ observer.next(reqs.filter(req => !isSystemApiRequest(req)).length);
+ });
+
+ return unwatch;
+ })
+ );
+};
+
+/**
+ * internal angular run function that will be called when angular bootstraps and
+ * lets us integrate with the angular router so that we can automatically clear
+ * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly
+ */
+const $setupBreadcrumbsAutoClear = (newPlatform: CoreSetup) => (
+ $rootScope: IRootScopeService,
+ $injector: any
+) => {
+ // A flag used to determine if we should automatically
+ // clear the breadcrumbs between angular route changes.
+ let breadcrumbSetSinceRouteChange = false;
+ const $route = $injector.has('$route') ? $injector.get('$route') : {};
+
+ // reset breadcrumbSetSinceRouteChange any time the breadcrumbs change, even
+ // if it was done directly through the new platform
+ newPlatform.chrome.getBreadcrumbs$().subscribe({
+ next() {
+ breadcrumbSetSinceRouteChange = true;
+ },
+ });
+
+ $rootScope.$on('$routeChangeStart', () => {
+ breadcrumbSetSinceRouteChange = false;
+ });
+
+ $rootScope.$on('$routeChangeSuccess', () => {
+ const current = $route.current || {};
+
+ if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
+ return;
+ }
+
+ const k7BreadcrumbsProvider = current.k7Breadcrumbs;
+ if (!k7BreadcrumbsProvider) {
+ newPlatform.chrome.setBreadcrumbs([]);
+ return;
+ }
+
+ try {
+ newPlatform.chrome.setBreadcrumbs($injector.invoke(k7BreadcrumbsProvider));
+ } catch (error) {
+ fatalError(error);
+ }
+ });
+};
+
+/**
+ * internal angular run function that will be called when angular bootstraps and
+ * lets us integrate with the angular router so that we can automatically clear
+ * the helpExtension if we switch to a Kibana app that does not set its own
+ * helpExtension
+ */
+const $setupHelpExtensionAutoClear = (newPlatform: CoreSetup) => (
+ $rootScope: IRootScopeService,
+ $injector: any
+) => {
+ /**
+ * reset helpExtensionSetSinceRouteChange any time the helpExtension changes, even
+ * if it was done directly through the new platform
+ */
+ let helpExtensionSetSinceRouteChange = false;
+ newPlatform.chrome.getHelpExtension$().subscribe({
+ next() {
+ helpExtensionSetSinceRouteChange = true;
+ },
+ });
+
+ const $route = $injector.has('$route') ? $injector.get('$route') : {};
+
+ $rootScope.$on('$routeChangeStart', () => {
+ helpExtensionSetSinceRouteChange = false;
+ });
+
+ $rootScope.$on('$routeChangeSuccess', () => {
+ const current = $route.current || {};
+
+ if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
+ return;
+ }
+
+ newPlatform.chrome.setHelpExtension(current.helpExtension);
+ });
+};
+
+const $setupUrlOverflowHandling = (newPlatform: CoreSetup) => (
+ $location: ILocationService,
+ $rootScope: IRootScopeService,
+ Private: any,
+ config: any
+) => {
+ const urlOverflow = Private(UrlOverflowServiceProvider);
+ const check = () => {
+ // disable long url checks when storing state in session storage
+ if (config.get('state:storeInSessionStorage')) {
+ return;
+ }
+
+ if ($location.path() === '/error/url-overflow') {
+ return;
+ }
+
+ try {
+ if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) {
+ toastNotifications.addWarning({
+ title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', {
+ defaultMessage: 'The URL is big and Kibana might stop working',
+ }),
+ text: (
+
+ state:storeInSessionStorage,
+ advancedSettingsLink: (
+
+
+
+ ),
+ }}
+ />
+
+ ),
+ });
+ }
+ } catch (e) {
+ window.location.href = modifyUrl(window.location.href, (parts: any) => {
+ parts.hash = '#/error/url-overflow';
+ });
+ // force the browser to reload to that Kibana's potentially unstable state is unloaded
+ window.location.reload();
+ }
+ };
+
+ $rootScope.$on('$routeUpdate', check);
+ $rootScope.$on('$routeChangeStart', check);
+};
diff --git a/src/legacy/ui/public/chrome/directives/sub_url_route_filter.js b/src/legacy/ui/public/legacy_compat/index.ts
similarity index 61%
rename from src/legacy/ui/public/chrome/directives/sub_url_route_filter.js
rename to src/legacy/ui/public/legacy_compat/index.ts
index 0ccd10d97ad25c..b29056954051ba 100644
--- a/src/legacy/ui/public/chrome/directives/sub_url_route_filter.js
+++ b/src/legacy/ui/public/legacy_compat/index.ts
@@ -17,21 +17,4 @@
* under the License.
*/
-/**
- * Creates a function that will be called on each route change
- * to determine if the event should be used to update the last
- * subUrl of chrome links/tabs
- * @injected
- */
-export function SubUrlRouteFilterProvider($injector) {
- if (!$injector.has('$route')) {
- return function alwaysUpdate() {
- return true;
- };
- }
-
- const $route = $injector.get('$route');
- return function ignoreRedirectToRoutes() {
- return Boolean($route.current && !$route.current.redirectTo);
- };
-}
+export { configureAppAngularModule } from './angular_config';