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 20cc30f687003d3..16724d5fa60972a 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/api/__tests__/xsrf.js b/src/legacy/ui/public/chrome/api/__tests__/xsrf.js index 52da8fb545c7d52..54255562cb6110b 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 70aa3f8d86c2bfb..d01ed1a355614e1 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -17,128 +17,24 @@ * 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'; import { registerSubUrlHooks } from './sub_url_hooks'; - -const URL_LIMIT_WARN_WITHIN = 1000; +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; - } + .run(internals.$initNavLinksDeepWatch); - 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); - }); - - directivesProvider(chrome, internals); 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 e8995edf46d6d8f..87201d0e758c166 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 a51df213c681ac8..2fafae89f2c9ea4 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 adc3d4ba567e26f..112f8d3d6e3d208 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 54b31bacc0ce19b..979993c76d5a5c8 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/xsrf.js b/src/legacy/ui/public/chrome/api/xsrf.js index ac59d6367aeae85..5086903604667d3 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 c1cfcd77021d43b..e282a898ff0db38 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 682af0abc47d9a1..5bc3d8a28bac8ca 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -54,10 +54,14 @@ export function kbnChromeProvider(chrome, internals) { }, controllerAs: 'chrome', - controller($scope) { + 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 000000000000000..9073d7588ddcf75 --- /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 000000000000000..18791372a5c15b8 --- /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/legacy_compat/index.ts b/src/legacy/ui/public/legacy_compat/index.ts new file mode 100644 index 000000000000000..b29056954051baf --- /dev/null +++ b/src/legacy/ui/public/legacy_compat/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { configureAppAngularModule } from './angular_config';