+ Existing links retrieved from storage. The links that were generated from legacy
+ generators are in red. This can be useful for developers to know they will have to
+ migrate persisted state or in a future version of Kibana, these links may no longer
+ work. They still work now because legacy url generators must provide a state
+ migration function.
+
+
+ {buildingLinks ? (
+
loading...
+ ) : (
+ migratedLinks.map(link => (
+
+
+ {link.linkText}
+
+
+
+ ))
+ )}
+
+
+
+
+ );
+};
+
+export const renderApp = (props: Props, { element }: AppMountParameters) => {
+ ReactDOM.render(, element);
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+};
diff --git a/src/legacy/core_plugins/visualizations/public/legacy_mocks.ts b/examples/url_generators_explorer/public/index.ts
similarity index 83%
rename from src/legacy/core_plugins/visualizations/public/legacy_mocks.ts
rename to examples/url_generators_explorer/public/index.ts
index 6cd57bb88bc26..30ff481dbe3a5 100644
--- a/src/legacy/core_plugins/visualizations/public/legacy_mocks.ts
+++ b/examples/url_generators_explorer/public/index.ts
@@ -17,5 +17,6 @@
* under the License.
*/
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-export { searchSourceMock } from '../../../../plugins/data/public/search/search_source/mocks';
+import { AccessLinksExplorerPlugin } from './plugin';
+
+export const plugin = () => new AccessLinksExplorerPlugin();
diff --git a/examples/url_generators_explorer/public/page.tsx b/examples/url_generators_explorer/public/page.tsx
new file mode 100644
index 0000000000000..90bea35804822
--- /dev/null
+++ b/examples/url_generators_explorer/public/page.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 React from 'react';
+
+import {
+ EuiPageBody,
+ EuiPageContent,
+ EuiPageContentBody,
+ EuiPageHeader,
+ EuiPageHeaderSection,
+ EuiTitle,
+} from '@elastic/eui';
+
+interface PageProps {
+ title: string;
+ children: React.ReactNode;
+}
+
+export function Page({ title, children }: PageProps) {
+ return (
+
+
+
+
+
{title}
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/plugins/data/server/search/es_search/es_search_service.ts b/examples/url_generators_explorer/public/plugin.tsx
similarity index 52%
rename from src/plugins/data/server/search/es_search/es_search_service.ts
rename to examples/url_generators_explorer/public/plugin.tsx
index b33b6c6ecd318..1fe70476b8e79 100644
--- a/src/plugins/data/server/search/es_search/es_search_service.ts
+++ b/examples/url_generators_explorer/public/plugin.tsx
@@ -17,26 +17,32 @@
* under the License.
*/
-import { ISearchSetup } from '../i_search_setup';
-import { PluginInitializerContext, CoreSetup, Plugin } from '../../../../../core/server';
-import { esSearchStrategyProvider } from './es_search_strategy';
-import { ES_SEARCH_STRATEGY } from '../../../common/search';
+import { SharePluginStart } from 'src/plugins/share/public';
+import { Plugin, CoreSetup, AppMountParameters } from '../../../src/core/public';
-interface IEsSearchDependencies {
- search: ISearchSetup;
+interface StartDeps {
+ share: SharePluginStart;
}
-export class EsSearchService implements Plugin {
- constructor(private initializerContext: PluginInitializerContext) {}
-
- public setup(core: CoreSetup, deps: IEsSearchDependencies) {
- deps.search.registerSearchStrategyProvider(
- this.initializerContext.opaqueId,
- ES_SEARCH_STRATEGY,
- esSearchStrategyProvider
- );
+export class AccessLinksExplorerPlugin implements Plugin {
+ public setup(core: CoreSetup) {
+ core.application.register({
+ id: 'urlGeneratorsExplorer',
+ title: 'Access links explorer',
+ async mount(params: AppMountParameters) {
+ const depsStart = (await core.getStartServices())[1];
+ const { renderApp } = await import('./app');
+ return renderApp(
+ {
+ getLinkGenerator: depsStart.share.urlGenerators.getUrlGenerator,
+ },
+ params
+ );
+ },
+ });
}
public start() {}
+
public stop() {}
}
diff --git a/examples/url_generators_explorer/tsconfig.json b/examples/url_generators_explorer/tsconfig.json
new file mode 100644
index 0000000000000..091130487791b
--- /dev/null
+++ b/examples/url_generators_explorer/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "index.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "server/**/*.ts",
+ "../../typings/**/*"
+ ],
+ "exclude": []
+}
diff --git a/package.json b/package.json
index 0f04a2fba3b65..2c401724c72cd 100644
--- a/package.json
+++ b/package.json
@@ -323,6 +323,7 @@
"@types/fetch-mock": "^7.3.1",
"@types/flot": "^0.0.31",
"@types/getopts": "^2.0.1",
+ "@types/getos": "^3.0.0",
"@types/glob": "^7.1.1",
"@types/globby": "^8.0.0",
"@types/graphql": "^0.13.2",
@@ -349,6 +350,7 @@
"@types/mustache": "^0.8.31",
"@types/node": "^10.12.27",
"@types/node-forge": "^0.9.0",
+ "@types/normalize-path": "^3.0.0",
"@types/numeral": "^0.0.26",
"@types/opn": "^5.1.0",
"@types/pegjs": "^0.10.1",
@@ -384,7 +386,7 @@
"@typescript-eslint/parser": "^2.15.0",
"angular-mocks": "^1.7.9",
"archiver": "^3.1.1",
- "axe-core": "^3.3.2",
+ "axe-core": "^3.4.1",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-plugin-dynamic-import-node": "^2.3.0",
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts
index 66fa55479f3b9..817c4796562e8 100644
--- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import './legacy/styles.scss';
import { fooLibFn } from '../../foo/public/index';
export * from './lib';
export { fooLibFn };
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss
new file mode 100644
index 0000000000000..e71a2d485a2f8
--- /dev/null
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss
@@ -0,0 +1,4 @@
+body {
+ width: $globalStyleConstant;
+ background-image: url("ui/icon.svg");
+}
diff --git a/src/legacy/ui/public/time_buckets/index.d.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts
similarity index 91%
rename from src/legacy/ui/public/time_buckets/index.d.ts
rename to packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts
index 70b9495b81f0e..9a51937cbac1e 100644
--- a/src/legacy/ui/public/time_buckets/index.d.ts
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts
@@ -17,6 +17,4 @@
* under the License.
*/
-declare module 'ui/time_buckets' {
- export const TimeBuckets: any;
-}
+export function foo() {}
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts
index 9d3871df24739..1ba0b69681152 100644
--- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts
@@ -19,3 +19,7 @@
export * from './lib';
export * from './ext';
+
+export async function getFoo() {
+ return await import('./async_import');
+}
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/icon.svg b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/icon.svg
new file mode 100644
index 0000000000000..ae7d5b958bbad
--- /dev/null
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/styles/_styling_constants.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/styles/_styling_constants.scss
new file mode 100644
index 0000000000000..83995ca65211b
--- /dev/null
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/legacy/ui/public/styles/_styling_constants.scss
@@ -0,0 +1 @@
+$globalStyleConstant: 10;
diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap
index 706f79978beee..d52d89eebe2f1 100644
--- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap
+++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap
@@ -1,557 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`builds expected bundles, saves bundle counts to metadata: 1 async bundle 1`] = `"(window[\\"foo_bundle_jsonpfunction\\"]=window[\\"foo_bundle_jsonpfunction\\"]||[]).push([[1],{3:function(module,exports,__webpack_require__){\\"use strict\\";Object.defineProperty(exports,\\"__esModule\\",{value:true});exports.foo=foo;function foo(){}}}]);"`;
+
exports[`builds expected bundles, saves bundle counts to metadata: OptimizerConfig 1`] = `
OptimizerConfig {
"bundles": Array [
Bundle {
"cache": BundleCache {
- "path": /plugins/bar/target/public/.kbn-optimizer-cache,
+ "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public/.kbn-optimizer-cache,
"state": undefined,
},
- "contextDir": /plugins/bar,
+ "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar,
"entry": "./public/index",
"id": "bar",
- "outputDir": /plugins/bar/target/public,
- "sourceRoot": ,
+ "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public,
+ "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo,
"type": "plugin",
},
Bundle {
"cache": BundleCache {
- "path": /plugins/foo/target/public/.kbn-optimizer-cache,
+ "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public/.kbn-optimizer-cache,
"state": undefined,
},
- "contextDir": /plugins/foo,
+ "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo,
"entry": "./public/index",
"id": "foo",
- "outputDir": /plugins/foo/target/public,
- "sourceRoot": ,
+ "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public,
+ "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo,
"type": "plugin",
},
],
"cache": true,
- "dist": false,
+ "dist": true,
"inspectWorkers": false,
"maxWorkerCount": 1,
"plugins": Array [
Object {
- "directory": /plugins/bar,
+ "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar,
"id": "bar",
"isUiPlugin": true,
},
Object {
- "directory": /plugins/baz,
+ "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/baz,
"id": "baz",
"isUiPlugin": false,
},
Object {
- "directory": /plugins/foo,
+ "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo,
"id": "foo",
"isUiPlugin": true,
},
],
"profileWebpack": false,
- "repoRoot": ,
+ "repoRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo,
"watch": false,
}
`;
-exports[`builds expected bundles, saves bundle counts to metadata: bar bundle 1`] = `
-"var __kbnBundles__ = typeof __kbnBundles__ === \\"object\\" ? __kbnBundles__ : {}; __kbnBundles__[\\"plugin/bar\\"] =
-/******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId]) {
-/******/ return installedModules[moduleId].exports;
-/******/ }
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ i: moduleId,
-/******/ l: false,
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-/******/
-/******/ // define getter function for harmony exports
-/******/ __webpack_require__.d = function(exports, name, getter) {
-/******/ if(!__webpack_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
-/******/ }
-/******/ };
-/******/
-/******/ // define __esModule on exports
-/******/ __webpack_require__.r = function(exports) {
-/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
-/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
-/******/ }
-/******/ Object.defineProperty(exports, '__esModule', { value: true });
-/******/ };
-/******/
-/******/ // create a fake namespace object
-/******/ // mode & 1: value is a module id, require it
-/******/ // mode & 2: merge all properties of value into the ns
-/******/ // mode & 4: return value when already ns object
-/******/ // mode & 8|1: behave like require
-/******/ __webpack_require__.t = function(value, mode) {
-/******/ if(mode & 1) value = __webpack_require__(value);
-/******/ if(mode & 8) return value;
-/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
-/******/ var ns = Object.create(null);
-/******/ __webpack_require__.r(ns);
-/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
-/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
-/******/ return ns;
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __webpack_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __webpack_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = \\"__REPLACE_WITH_PUBLIC_PATH__\\";
-/******/
-/******/
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = \\"./public/index.ts\\");
-/******/ })
-/************************************************************************/
-/******/ ({
-
-/***/ \\"../foo/public/ext.ts\\":
-/*!****************************!*\\\\
- !*** ../foo/public/ext.ts ***!
- \\\\****************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-exports.ext = void 0;
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the \\"License\\"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * \\"AS IS\\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-const ext = 'TRUE';
-exports.ext = ext;
-
-/***/ }),
-
-/***/ \\"../foo/public/index.ts\\":
-/*!******************************!*\\\\
- !*** ../foo/public/index.ts ***!
- \\\\******************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-
-var _lib = __webpack_require__(/*! ./lib */ \\"../foo/public/lib.ts\\");
-
-Object.keys(_lib).forEach(function (key) {
- if (key === \\"default\\" || key === \\"__esModule\\") return;
- Object.defineProperty(exports, key, {
- enumerable: true,
- get: function () {
- return _lib[key];
- }
- });
-});
-
-var _ext = __webpack_require__(/*! ./ext */ \\"../foo/public/ext.ts\\");
-
-Object.keys(_ext).forEach(function (key) {
- if (key === \\"default\\" || key === \\"__esModule\\") return;
- Object.defineProperty(exports, key, {
- enumerable: true,
- get: function () {
- return _ext[key];
- }
- });
-});
-
-/***/ }),
-
-/***/ \\"../foo/public/lib.ts\\":
-/*!****************************!*\\\\
- !*** ../foo/public/lib.ts ***!
- \\\\****************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-exports.fooLibFn = fooLibFn;
-
-/*
- * 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.
- */
-function fooLibFn() {
- return 'foo';
-}
-
-/***/ }),
-
-/***/ \\"./public/index.ts\\":
-/*!*************************!*\\\\
- !*** ./public/index.ts ***!
- \\\\*************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-var _exportNames = {
- fooLibFn: true
-};
-Object.defineProperty(exports, \\"fooLibFn\\", {
- enumerable: true,
- get: function () {
- return _index.fooLibFn;
- }
-});
-
-var _index = __webpack_require__(/*! ../../foo/public/index */ \\"../foo/public/index.ts\\");
-
-var _lib = __webpack_require__(/*! ./lib */ \\"./public/lib.ts\\");
-
-Object.keys(_lib).forEach(function (key) {
- if (key === \\"default\\" || key === \\"__esModule\\") return;
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
- Object.defineProperty(exports, key, {
- enumerable: true,
- get: function () {
- return _lib[key];
- }
- });
-});
-
-/***/ }),
-
-/***/ \\"./public/lib.ts\\":
-/*!***********************!*\\\\
- !*** ./public/lib.ts ***!
- \\\\***********************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-exports.barLibFn = barLibFn;
-
-/*
- * 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.
- */
-function barLibFn() {
- return 'bar';
-}
-
-/***/ })
-
-/******/ })[\\"plugin\\"];
-//# sourceMappingURL=bar.plugin.js.map"
-`;
-
-exports[`builds expected bundles, saves bundle counts to metadata: foo bundle 1`] = `
-"var __kbnBundles__ = typeof __kbnBundles__ === \\"object\\" ? __kbnBundles__ : {}; __kbnBundles__[\\"plugin/foo\\"] =
-/******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId]) {
-/******/ return installedModules[moduleId].exports;
-/******/ }
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ i: moduleId,
-/******/ l: false,
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-/******/
-/******/ // define getter function for harmony exports
-/******/ __webpack_require__.d = function(exports, name, getter) {
-/******/ if(!__webpack_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
-/******/ }
-/******/ };
-/******/
-/******/ // define __esModule on exports
-/******/ __webpack_require__.r = function(exports) {
-/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
-/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
-/******/ }
-/******/ Object.defineProperty(exports, '__esModule', { value: true });
-/******/ };
-/******/
-/******/ // create a fake namespace object
-/******/ // mode & 1: value is a module id, require it
-/******/ // mode & 2: merge all properties of value into the ns
-/******/ // mode & 4: return value when already ns object
-/******/ // mode & 8|1: behave like require
-/******/ __webpack_require__.t = function(value, mode) {
-/******/ if(mode & 1) value = __webpack_require__(value);
-/******/ if(mode & 8) return value;
-/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
-/******/ var ns = Object.create(null);
-/******/ __webpack_require__.r(ns);
-/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
-/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
-/******/ return ns;
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __webpack_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __webpack_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = \\"__REPLACE_WITH_PUBLIC_PATH__\\";
-/******/
-/******/
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = \\"./public/index.ts\\");
-/******/ })
-/************************************************************************/
-/******/ ({
-
-/***/ \\"./public/ext.ts\\":
-/*!***********************!*\\\\
- !*** ./public/ext.ts ***!
- \\\\***********************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
-
-\\"use strict\\";
-
-
-Object.defineProperty(exports, \\"__esModule\\", {
- value: true
-});
-exports.ext = void 0;
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the \\"License\\"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * \\"AS IS\\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-const ext = 'TRUE';
-exports.ext = ext;
-
-/***/ }),
-
-/***/ \\"./public/index.ts\\":
-/*!*************************!*\\\\
- !*** ./public/index.ts ***!
- \\\\*************************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
+exports[`builds expected bundles, saves bundle counts to metadata: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i {
await del(TMP_DIR);
@@ -51,20 +51,25 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
repoRoot: MOCK_REPO_DIR,
pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')],
maxWorkerCount: 1,
+ dist: true,
});
expect(config).toMatchSnapshot('OptimizerConfig');
- const msgs = await runOptimizer(config)
- .pipe(
- tap(state => {
- if (state.event?.type === 'worker stdio') {
- // eslint-disable-next-line no-console
- console.log('worker', state.event.stream, state.event.chunk.toString('utf8'));
+ const log = new ToolingLog({
+ level: 'error',
+ writeTo: {
+ write(chunk) {
+ if (chunk.endsWith('\n')) {
+ chunk = chunk.slice(0, -1);
}
- }),
- toArray()
- )
+ // eslint-disable-next-line no-console
+ console.error(chunk);
+ },
+ },
+ });
+ const msgs = await runOptimizer(config)
+ .pipe(logOptimizerState(log, config), toArray())
.toPromise();
const assert = (statement: string, truth: boolean, altStates?: OptimizerUpdate[]) => {
@@ -123,6 +128,10 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/foo.plugin.js'), 'utf8')
).toMatchSnapshot('foo bundle');
+ expect(
+ Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/1.plugin.js'), 'utf8')
+ ).toMatchSnapshot('1 async bundle');
+
expect(
Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/bar/target/public/bar.plugin.js'), 'utf8')
).toMatchSnapshot('bar bundle');
@@ -130,26 +139,36 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
const foo = config.bundles.find(b => b.id === 'foo')!;
expect(foo).toBeTruthy();
foo.cache.refresh();
- expect(foo.cache.getModuleCount()).toBe(3);
+ expect(foo.cache.getModuleCount()).toBe(4);
expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /plugins/foo/public/ext.ts,
- /plugins/foo/public/index.ts,
- /plugins/foo/public/lib.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts,
]
`);
const bar = config.bundles.find(b => b.id === 'bar')!;
expect(bar).toBeTruthy();
bar.cache.refresh();
- expect(bar.cache.getModuleCount()).toBe(5);
+ expect(bar.cache.getModuleCount()).toBe(
+ // code + styles + style/css-loader runtimes
+ 15
+ );
+
expect(bar.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /plugins/foo/public/ext.ts,
- /plugins/foo/public/index.ts,
- /plugins/foo/public/lib.ts,
- /plugins/bar/public/index.ts,
- /plugins/bar/public/lib.ts,
+ /node_modules/css-loader/package.json,
+ /node_modules/style-loader/package.json,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/legacy/styles.scss,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/lib.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/legacy/ui/public/icon.svg,
]
`);
});
@@ -159,6 +178,7 @@ it('uses cache on second run and exist cleanly', async () => {
repoRoot: MOCK_REPO_DIR,
pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')],
maxWorkerCount: 1,
+ dist: true,
});
const msgs = await runOptimizer(config)
diff --git a/packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap b/packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap
new file mode 100644
index 0000000000000..2973ac116d6bd
--- /dev/null
+++ b/packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap
@@ -0,0 +1,156 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`parseDirPath() parses / 1`] = `
+Object {
+ "dirs": Array [],
+ "filename": undefined,
+ "root": "/",
+}
+`;
+
+exports[`parseDirPath() parses /foo 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ ],
+ "filename": undefined,
+ "root": "/",
+}
+`;
+
+exports[`parseDirPath() parses /foo/bar/baz 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ "baz",
+ ],
+ "filename": undefined,
+ "root": "/",
+}
+`;
+
+exports[`parseDirPath() parses /foo/bar/baz/ 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ "baz",
+ ],
+ "filename": undefined,
+ "root": "/",
+}
+`;
+
+exports[`parseDirPath() parses c:\\ 1`] = `
+Object {
+ "dirs": Array [],
+ "filename": undefined,
+ "root": "c:",
+}
+`;
+
+exports[`parseDirPath() parses c:\\foo 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ ],
+ "filename": undefined,
+ "root": "c:",
+}
+`;
+
+exports[`parseDirPath() parses c:\\foo\\bar\\baz 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ "baz",
+ ],
+ "filename": undefined,
+ "root": "c:",
+}
+`;
+
+exports[`parseDirPath() parses c:\\foo\\bar\\baz\\ 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ "baz",
+ ],
+ "filename": undefined,
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses /foo 1`] = `
+Object {
+ "dirs": Array [],
+ "filename": "foo",
+ "root": "/",
+}
+`;
+
+exports[`parseFilePath() parses /foo/bar/baz 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz",
+ "root": "/",
+}
+`;
+
+exports[`parseFilePath() parses /foo/bar/baz.json 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "root": "/",
+}
+`;
+
+exports[`parseFilePath() parses c:/foo/bar/baz.json 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses c:\\foo 1`] = `
+Object {
+ "dirs": Array [],
+ "filename": "foo",
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses c:\\foo\\bar\\baz 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz",
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses c:\\foo\\bar\\baz.json 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "root": "c:",
+}
+`;
diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts b/packages/kbn-optimizer/src/worker/parse_path.test.ts
similarity index 57%
rename from src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts
rename to packages/kbn-optimizer/src/worker/parse_path.test.ts
index a0b9d7c779b02..72197e8c8fb07 100644
--- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts
+++ b/packages/kbn-optimizer/src/worker/parse_path.test.ts
@@ -17,26 +17,20 @@
* under the License.
*/
-import {
- notificationServiceMock,
- overlayServiceMock,
- httpServiceMock,
- injectedMetadataServiceMock,
-} from '../../../../../../../core/public/mocks';
+import { parseFilePath, parseDirPath } from './parse_path';
-jest.doMock('ui/new_platform', () => {
- return {
- npSetup: {
- core: {
- notifications: notificationServiceMock.createSetupContract(),
- },
- },
- npStart: {
- core: {
- overlays: overlayServiceMock.createStartContract(),
- http: httpServiceMock.createStartContract({ basePath: 'path' }),
- injectedMetadata: injectedMetadataServiceMock.createStartContract(),
- },
- },
- };
+const DIRS = ['/', '/foo/bar/baz/', 'c:\\', 'c:\\foo\\bar\\baz\\'];
+const AMBIGUOUS = ['/foo', '/foo/bar/baz', 'c:\\foo', 'c:\\foo\\bar\\baz'];
+const FILES = ['/foo/bar/baz.json', 'c:/foo/bar/baz.json', 'c:\\foo\\bar\\baz.json'];
+
+describe('parseFilePath()', () => {
+ it.each([...FILES, ...AMBIGUOUS])('parses %s', path => {
+ expect(parseFilePath(path)).toMatchSnapshot();
+ });
+});
+
+describe('parseDirPath()', () => {
+ it.each([...DIRS, ...AMBIGUOUS])('parses %s', path => {
+ expect(parseDirPath(path)).toMatchSnapshot();
+ });
});
diff --git a/packages/kbn-optimizer/src/worker/parse_path.ts b/packages/kbn-optimizer/src/worker/parse_path.ts
new file mode 100644
index 0000000000000..88152df55b84f
--- /dev/null
+++ b/packages/kbn-optimizer/src/worker/parse_path.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 normalizePath from 'normalize-path';
+
+/**
+ * Parse an absolute path, supporting normalized paths from webpack,
+ * into a list of directories and root
+ */
+export function parseDirPath(path: string) {
+ const filePath = parseFilePath(path);
+ return {
+ ...filePath,
+ dirs: [...filePath.dirs, ...(filePath.filename ? [filePath.filename] : [])],
+ filename: undefined,
+ };
+}
+
+export function parseFilePath(path: string) {
+ const normalized = normalizePath(path);
+ const [root, ...others] = normalized.split('/');
+ return {
+ root: root === '' ? '/' : root,
+ dirs: others.slice(0, -1),
+ filename: others[others.length - 1] || undefined,
+ };
+}
diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts
index 7dcce8a0fae8d..e87ddc7d0185c 100644
--- a/packages/kbn-optimizer/src/worker/run_compilers.ts
+++ b/packages/kbn-optimizer/src/worker/run_compilers.ts
@@ -27,9 +27,10 @@ import webpack, { Stats } from 'webpack';
import * as Rx from 'rxjs';
import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators';
-import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig } from '../common';
+import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig, ascending } from '../common';
import { getWebpackConfig } from './webpack.config';
import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers';
+import { parseFilePath } from './parse_path';
import {
isExternalModule,
isNormalModule,
@@ -108,26 +109,25 @@ const observeCompiler = (
for (const module of normalModules) {
const path = getModulePath(module);
+ const parsedPath = parseFilePath(path);
- const parsedPath = Path.parse(path);
- const dirSegments = parsedPath.dir.split(Path.sep);
- if (!dirSegments.includes('node_modules')) {
+ if (!parsedPath.dirs.includes('node_modules')) {
referencedFiles.add(path);
continue;
}
- const nmIndex = dirSegments.lastIndexOf('node_modules');
- const isScoped = dirSegments[nmIndex + 1].startsWith('@');
+ const nmIndex = parsedPath.dirs.lastIndexOf('node_modules');
+ const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@');
referencedFiles.add(
Path.join(
parsedPath.root,
- ...dirSegments.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)),
+ ...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)),
'package.json'
)
);
}
- const files = Array.from(referencedFiles);
+ const files = Array.from(referencedFiles).sort(ascending(p => p));
const mtimes = new Map(
files.map((path): [string, number | undefined] => {
try {
diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts
index 3c6ae78bc4d91..5d8ef7626f630 100644
--- a/packages/kbn-optimizer/src/worker/webpack.config.ts
+++ b/packages/kbn-optimizer/src/worker/webpack.config.ts
@@ -30,6 +30,7 @@ import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import * as SharedDeps from '@kbn/ui-shared-deps';
import { Bundle, WorkerConfig } from '../common';
+import { parseDirPath } from './parse_path';
const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE;
const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset');
@@ -135,7 +136,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) {
}
// manually force ui/* urls in legacy styles to resolve to ui/legacy/public
- if (uri.startsWith('ui/') && base.split(Path.sep).includes('legacy')) {
+ if (uri.startsWith('ui/') && parseDirPath(base).dirs.includes('legacy')) {
return Path.resolve(
worker.repoRoot,
'src/legacy/ui/public',
@@ -150,7 +151,9 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) {
{
loader: 'sass-loader',
options: {
- sourceMap: !worker.dist,
+ // must always be enabled as long as we're using the `resolve-url-loader` to
+ // rewrite `ui/*` urls. They're dropped by subsequent loaders though
+ sourceMap: true,
prependData(loaderContext: webpack.loader.LoaderContext) {
return `@import ${stringifyRequest(
loaderContext,
diff --git a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
index 771bf43c4020a..d7d4dc14519c3 100644
--- a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
+++ b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
@@ -102,6 +102,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
'start',
'--optimize.enabled=false',
'--logging.json=false',
+ '--logging.verbose=true',
'--migrations.skip=true',
],
cwd: generatedPath,
diff --git a/packages/kbn-storybook/storybook_config/config.js b/packages/kbn-storybook/storybook_config/config.js
index a7975773e675b..d97bd3f7c2dcc 100644
--- a/packages/kbn-storybook/storybook_config/config.js
+++ b/packages/kbn-storybook/storybook_config/config.js
@@ -55,7 +55,7 @@ addParameters({
brandTitle: 'Kibana Storybook',
brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook',
}),
- showPanel: true,
+ showPanel: false,
isFullscreen: false,
panelPosition: 'bottom',
isToolshown: true,
diff --git a/src/plugins/ui_actions/public/tests/test_samples/restricted_action.ts b/packages/kbn-test/src/ci_parallel_process_prefix.ts
similarity index 66%
rename from src/plugins/ui_actions/public/tests/test_samples/restricted_action.ts
rename to packages/kbn-test/src/ci_parallel_process_prefix.ts
index aa65d3af98163..67dafc7e85324 100644
--- a/src/plugins/ui_actions/public/tests/test_samples/restricted_action.ts
+++ b/packages/kbn-test/src/ci_parallel_process_prefix.ts
@@ -17,14 +17,14 @@
* under the License.
*/
-import { Action, createAction } from '../../actions';
+const job = process.env.JOB ? `job-${process.env.JOB}-` : '';
+const num = process.env.CI_PARALLEL_PROCESS_NUMBER
+ ? `worker-${process.env.CI_PARALLEL_PROCESS_NUMBER}-`
+ : '';
-export const RESTRICTED_ACTION = 'RESTRICTED_ACTION';
-
-export function createRestrictedAction(isCompatibleIn: (context: C) => boolean): Action {
- return createAction({
- type: RESTRICTED_ACTION,
- isCompatible: async context => isCompatibleIn(context),
- execute: async () => {},
- });
-}
+/**
+ * A prefix string that is unique for each parallel CI process that
+ * is an empty string outside of CI, so it can be safely injected
+ * into strings as a prefix
+ */
+export const CI_PARALLEL_PROCESS_PREFIX = `${job}${num}`;
diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts
index d8a952bee42e5..7da79b5b67e63 100644
--- a/packages/kbn-test/src/failed_tests_reporter/github_api.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts
@@ -33,6 +33,15 @@ export interface GithubIssue {
body: string;
}
+/**
+ * Minimal GithubIssue type that can be easily replicated by dry-run helpers
+ */
+export interface GithubIssueMini {
+ number: GithubIssue['number'];
+ body: GithubIssue['body'];
+ html_url: GithubIssue['html_url'];
+}
+
type RequestOptions = AxiosRequestConfig & {
safeForDryRun?: boolean;
maxAttempts?: number;
@@ -162,7 +171,7 @@ export class GithubApi {
}
async createIssue(title: string, body: string, labels?: string[]) {
- const resp = await this.request(
+ const resp = await this.request(
{
method: 'POST',
url: Url.resolve(BASE_URL, 'issues'),
@@ -173,11 +182,13 @@ export class GithubApi {
},
},
{
+ body,
+ number: 999,
html_url: 'https://dryrun',
}
);
- return resp.data.html_url;
+ return resp.data;
}
private async request(
diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
index ef6ab3c51ab19..5bbc72fe04e86 100644
--- a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
@@ -78,9 +78,7 @@ describe('updateFailureIssue()', () => {
'https://build-url',
{
html_url: 'https://github.com/issues/1234',
- labels: ['some-label'],
number: 1234,
- title: 'issue title',
body: dedent`
# existing issue body
diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
index 97e9d517576fc..1413d05498459 100644
--- a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
@@ -18,7 +18,7 @@
*/
import { TestFailure } from './get_failures';
-import { GithubIssue, GithubApi } from './github_api';
+import { GithubIssueMini, GithubApi } from './github_api';
import { getIssueMetadata, updateIssueMetadata } from './issue_metadata';
export async function createFailureIssue(buildUrl: string, failure: TestFailure, api: GithubApi) {
@@ -44,7 +44,7 @@ export async function createFailureIssue(buildUrl: string, failure: TestFailure,
return await api.createIssue(title, body, ['failed-test']);
}
-export async function updateFailureIssue(buildUrl: string, issue: GithubIssue, api: GithubApi) {
+export async function updateFailureIssue(buildUrl: string, issue: GithubIssueMini, api: GithubApi) {
// Increment failCount
const newCount = getIssueMetadata(issue.body, 'test.failCount', 0) + 1;
const newBody = updateIssueMetadata(issue.body, {
diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
index fc52fa6cbf9e7..9324f9eb42aa5 100644
--- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
@@ -20,8 +20,8 @@
import { REPO_ROOT, run, createFailError, createFlagError } from '@kbn/dev-utils';
import globby from 'globby';
-import { getFailures } from './get_failures';
-import { GithubApi } from './github_api';
+import { getFailures, TestFailure } from './get_failures';
+import { GithubApi, GithubIssueMini } from './github_api';
import { updateFailureIssue, createFailureIssue } from './report_failure';
import { getIssueMetadata } from './issue_metadata';
import { readTestReport } from './test_report';
@@ -73,6 +73,11 @@ export function runFailedTestsReporterCli() {
absolute: true,
});
+ const newlyCreatedIssues: Array<{
+ failure: TestFailure;
+ newIssue: GithubIssueMini;
+ }> = [];
+
for (const reportPath of reportPaths) {
const report = await readTestReport(reportPath);
const messages = Array.from(getReportMessageIter(report));
@@ -94,12 +99,22 @@ export function runFailedTestsReporterCli() {
continue;
}
- const existingIssue = await githubApi.findFailedTestIssue(
+ let existingIssue: GithubIssueMini | undefined = await githubApi.findFailedTestIssue(
i =>
getIssueMetadata(i.body, 'test.class') === failure.classname &&
getIssueMetadata(i.body, 'test.name') === failure.name
);
+ if (!existingIssue) {
+ const newlyCreated = newlyCreatedIssues.find(
+ ({ failure: f }) => f.classname === failure.classname && f.name === failure.name
+ );
+
+ if (newlyCreated) {
+ existingIssue = newlyCreated.newIssue;
+ }
+ }
+
if (existingIssue) {
const newFailureCount = await updateFailureIssue(buildUrl, existingIssue, githubApi);
const url = existingIssue.html_url;
@@ -110,11 +125,12 @@ export function runFailedTestsReporterCli() {
continue;
}
- const newIssueUrl = await createFailureIssue(buildUrl, failure, githubApi);
+ const newIssue = await createFailureIssue(buildUrl, failure, githubApi);
pushMessage('Test has not failed recently on tracked branches');
if (updateGithub) {
- pushMessage(`Created new issue: ${newIssueUrl}`);
+ pushMessage(`Created new issue: ${newIssue.html_url}`);
}
+ newlyCreatedIssues.push({ failure, newIssue });
}
// mutates report to include messages and writes updated report to disk
diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js
index 136704a639bff..5f58190078f0d 100644
--- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js
+++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js
@@ -42,10 +42,11 @@ export async function runElasticsearch({ config, options }) {
esFrom: esFrom || config.get('esTestCluster.from'),
dataArchive: config.get('esTestCluster.dataArchive'),
esArgs,
+ esEnvVars,
ssl,
});
- await cluster.start(esArgs, esEnvVars);
+ await cluster.start();
if (isSecurityEnabled) {
await setupUsers({
diff --git a/packages/kbn-test/src/junit_report_path.ts b/packages/kbn-test/src/junit_report_path.ts
index 11eaf3d2b14a5..90405d7a89c02 100644
--- a/packages/kbn-test/src/junit_report_path.ts
+++ b/packages/kbn-test/src/junit_report_path.ts
@@ -18,15 +18,13 @@
*/
import { resolve } from 'path';
-
-const job = process.env.JOB ? `job-${process.env.JOB}-` : '';
-const num = process.env.CI_WORKER_NUMBER ? `worker-${process.env.CI_WORKER_NUMBER}-` : '';
+import { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix';
export function makeJunitReportPath(rootDirectory: string, reportName: string) {
return resolve(
rootDirectory,
'target/junit',
process.env.JOB || '.',
- `TEST-${job}${num}${reportName}.xml`
+ `TEST-${CI_PARALLEL_PROCESS_PREFIX}${reportName}.xml`
);
}
diff --git a/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
index 355304c86a3c3..f795b32d78b8e 100644
--- a/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
+++ b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
@@ -22,6 +22,7 @@ import { format } from 'url';
import { get } from 'lodash';
import toPath from 'lodash/internal/toPath';
import { Cluster } from '@kbn/es';
+import { CI_PARALLEL_PROCESS_PREFIX } from '../ci_parallel_process_prefix';
import { esTestConfig } from './es_test_config';
import { KIBANA_ROOT } from '../';
@@ -38,14 +39,22 @@ export function createLegacyEsTestCluster(options = {}) {
basePath = resolve(KIBANA_ROOT, '.es'),
esFrom = esTestConfig.getBuildFrom(),
dataArchive,
- esArgs,
+ esArgs: customEsArgs = [],
+ esEnvVars,
+ clusterName: customClusterName = 'es-test-cluster',
ssl,
} = options;
- const randomHash = Math.random()
- .toString(36)
- .substring(2);
- const clusterName = `test-${randomHash}`;
+ const clusterName = `${CI_PARALLEL_PROCESS_PREFIX}${customClusterName}`;
+
+ const esArgs = [
+ `cluster.name=${clusterName}`,
+ `http.port=${port}`,
+ 'discovery.type=single-node',
+ `transport.port=${esTestConfig.getTransportPort()}`,
+ ...customEsArgs,
+ ];
+
const config = {
version: esTestConfig.getVersion(),
installPath: resolve(basePath, clusterName),
@@ -55,7 +64,6 @@ export function createLegacyEsTestCluster(options = {}) {
basePath,
esArgs,
};
- const transportPort = esTestConfig.getTransportPort();
const cluster = new Cluster({ log, ssl });
@@ -67,7 +75,7 @@ export function createLegacyEsTestCluster(options = {}) {
return esFrom === 'snapshot' ? 3 * minute : 6 * minute;
}
- async start(esArgs = [], esEnvVars) {
+ async start() {
let installPath;
if (esFrom === 'source') {
@@ -86,13 +94,7 @@ export function createLegacyEsTestCluster(options = {}) {
await cluster.start(installPath, {
password: config.password,
- esArgs: [
- `cluster.name=${clusterName}`,
- `http.port=${port}`,
- 'discovery.type=single-node',
- `transport.port=${transportPort}`,
- ...esArgs,
- ],
+ esArgs,
esEnvVars,
});
}
diff --git a/renovate.json5 b/renovate.json5
index 58a64a5d0f967..ca2cd2e6bcd93 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -665,6 +665,14 @@
'@types/nodemailer',
],
},
+ {
+ groupSlug: 'normalize-path',
+ groupName: 'normalize-path related packages',
+ packageNames: [
+ 'normalize-path',
+ '@types/normalize-path',
+ ],
+ },
{
groupSlug: 'numeral',
groupName: 'numeral related packages',
diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md
index 2769079757bc3..0f592d108c561 100644
--- a/src/core/CONVENTIONS.md
+++ b/src/core/CONVENTIONS.md
@@ -148,8 +148,8 @@ import { MyAppRoot } from './components/app.ts';
/**
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
*/
-export const renderApp = (core: CoreStart, deps: MyPluginDepsStart, { element, appBasePath }: AppMountParams) => {
- ReactDOM.render(, element);
+export const renderApp = (core: CoreStart, deps: MyPluginDepsStart, { element, history }: AppMountParams) => {
+ ReactDOM.render(, element);
return () => ReactDOM.unmountComponentAtNode(element);
}
```
diff --git a/src/core/TESTING.md b/src/core/TESTING.md
index 9abc2bb77d7d1..cb38dac0e20ce 100644
--- a/src/core/TESTING.md
+++ b/src/core/TESTING.md
@@ -453,7 +453,7 @@ describe('Plugin', () => {
const [coreStartMock, startDepsMock] = await coreSetup.getStartServices();
const unmountMock = jest.fn();
renderAppMock.mockReturnValue(unmountMock);
- const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' };
+ const params = coreMock.createAppMountParamters('/fake/base/path');
new Plugin(coreMock.createPluginInitializerContext()).setup(coreSetup);
// Grab registered mount function
@@ -478,7 +478,7 @@ import ReactDOM from 'react-dom';
import { AppMountParams, CoreStart } from 'src/core/public';
import { AppRoot } from './components/app_root';
-export const renderApp = ({ element, appBasePath }: AppMountParams, core: CoreStart, plugins: MyPluginDepsStart) => {
+export const renderApp = ({ element, history }: AppMountParams, core: CoreStart, plugins: MyPluginDepsStart) => {
// Hide the chrome while this app is mounted for a full screen experience
core.chrome.setIsVisible(false);
@@ -491,7 +491,7 @@ export const renderApp = ({ element, appBasePath }: AppMountParams, core: CoreSt
// Render app
ReactDOM.render(
- ,
+ ,
element
);
@@ -512,12 +512,14 @@ In testing `renderApp` you should be verifying that:
```typescript
/** public/application.test.ts */
+import { createMemoryHistory } from 'history';
+import { ScopedHistory } from 'src/core/public';
import { coreMock } from 'src/core/public/mocks';
import { renderApp } from './application';
describe('renderApp', () => {
it('mounts and unmounts UI', () => {
- const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' };
+ const params = coreMock.createAppMountParamters('/fake/base/path');
const core = coreMock.createStart();
// Verify some expected DOM element is rendered into the element
@@ -529,7 +531,7 @@ describe('renderApp', () => {
});
it('unsubscribes from uiSettings', () => {
- const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' };
+ const params = coreMock.createAppMountParamters('/fake/base/path');
const core = coreMock.createStart();
// Create a fake Subject you can use to monitor observers
const settings$ = new Subject();
@@ -544,7 +546,7 @@ describe('renderApp', () => {
});
it('resets chrome visibility', () => {
- const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' };
+ const params = coreMock.createAppMountParamters('/fake/base/path');
const core = coreMock.createStart();
// Verify stateful Core API was called on mount
diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts
index facb818c60ff9..318afb652999e 100644
--- a/src/core/public/application/types.ts
+++ b/src/core/public/application/types.ts
@@ -347,7 +347,6 @@ export interface AppMountParameters {
*
* export renderApp = ({ element, history }: AppMountParameters) => {
* ReactDOM.render(
- * // pass `appBasePath` to `basename`
*
*
* ,
@@ -429,7 +428,7 @@ export interface AppMountParameters {
* import { CoreStart, AppMountParams } from 'src/core/public';
* import { MyPluginDepsStart } from './plugin';
*
- * export renderApp = ({ appBasePath, element, onAppLeave }: AppMountParams) => {
+ * export renderApp = ({ element, history, onAppLeave }: AppMountParams) => {
* const { renderApp, hasUnsavedChanges } = await import('./application');
* onAppLeave(actions => {
* if(hasUnsavedChanges()) {
@@ -437,7 +436,7 @@ export interface AppMountParameters {
* }
* return actions.default();
* });
- * return renderApp(params);
+ * return renderApp({ element, history });
* }
* ```
*/
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 3521d7ef9c66e..9b672d40961d8 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -120,6 +120,7 @@ export class DocLinksService {
},
management: {
kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`,
+ dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`,
},
},
});
diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts
index efd9fdd053674..f223956075e97 100644
--- a/src/core/public/http/fetch.test.ts
+++ b/src/core/public/http/fetch.test.ts
@@ -21,6 +21,7 @@
import fetchMock from 'fetch-mock/es5/client';
import { readFileSync } from 'fs';
import { join } from 'path';
+import { first } from 'rxjs/operators';
import { Fetch } from './fetch';
import { BasePath } from './base_path';
@@ -30,9 +31,11 @@ function delay(duration: number) {
return new Promise(r => setTimeout(r, duration));
}
+const BASE_PATH = 'http://localhost/myBase';
+
describe('Fetch', () => {
const fetchInstance = new Fetch({
- basePath: new BasePath('http://localhost/myBase'),
+ basePath: new BasePath(BASE_PATH),
kibanaVersion: 'VERSION',
});
afterEach(() => {
@@ -40,6 +43,79 @@ describe('Fetch', () => {
fetchInstance.removeAllInterceptors();
});
+ describe('getRequestCount$', () => {
+ const getCurrentRequestCount = () =>
+ fetchInstance
+ .getRequestCount$()
+ .pipe(first())
+ .toPromise();
+
+ it('should increase and decrease when request receives success response', async () => {
+ fetchMock.get('*', 200);
+
+ const fetchResponse = fetchInstance.fetch('/path');
+ expect(await getCurrentRequestCount()).toEqual(1);
+
+ await expect(fetchResponse).resolves.not.toThrow();
+ expect(await getCurrentRequestCount()).toEqual(0);
+ });
+
+ it('should increase and decrease when request receives error response', async () => {
+ fetchMock.get('*', 500);
+
+ const fetchResponse = fetchInstance.fetch('/path');
+ expect(await getCurrentRequestCount()).toEqual(1);
+
+ await expect(fetchResponse).rejects.toThrow();
+ expect(await getCurrentRequestCount()).toEqual(0);
+ });
+
+ it('should increase and decrease when request fails', async () => {
+ fetchMock.get('*', Promise.reject('Network!'));
+
+ const fetchResponse = fetchInstance.fetch('/path');
+ expect(await getCurrentRequestCount()).toEqual(1);
+
+ await expect(fetchResponse).rejects.toThrow();
+ expect(await getCurrentRequestCount()).toEqual(0);
+ });
+
+ it('should change for multiple requests', async () => {
+ fetchMock.get(`${BASE_PATH}/success`, 200);
+ fetchMock.get(`${BASE_PATH}/fail`, 400);
+ fetchMock.get(`${BASE_PATH}/network-fail`, Promise.reject('Network!'));
+
+ const requestCounts: number[] = [];
+ const subscription = fetchInstance
+ .getRequestCount$()
+ .subscribe(count => requestCounts.push(count));
+
+ const success1 = fetchInstance.fetch('/success');
+ const success2 = fetchInstance.fetch('/success');
+ const failure1 = fetchInstance.fetch('/fail');
+ const failure2 = fetchInstance.fetch('/fail');
+ const networkFailure1 = fetchInstance.fetch('/network-fail');
+ const success3 = fetchInstance.fetch('/success');
+ const failure3 = fetchInstance.fetch('/fail');
+ const networkFailure2 = fetchInstance.fetch('/network-fail');
+
+ const swallowError = (p: Promise) => p.catch(() => {});
+ await Promise.all([
+ success1,
+ success2,
+ success3,
+ swallowError(failure1),
+ swallowError(failure2),
+ swallowError(failure3),
+ swallowError(networkFailure1),
+ swallowError(networkFailure2),
+ ]);
+
+ expect(requestCounts).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
+ subscription.unsubscribe();
+ });
+ });
+
describe('http requests', () => {
it('should fail with invalid arguments', async () => {
fetchMock.get('*', {});
diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts
index b433acdb6dbb9..d88dc2e3a9037 100644
--- a/src/core/public/http/fetch.ts
+++ b/src/core/public/http/fetch.ts
@@ -19,6 +19,7 @@
import { merge } from 'lodash';
import { format } from 'url';
+import { BehaviorSubject } from 'rxjs';
import {
IBasePath,
@@ -43,6 +44,7 @@ const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/;
export class Fetch {
private readonly interceptors = new Set();
+ private readonly requestCount$ = new BehaviorSubject(0);
constructor(private readonly params: Params) {}
@@ -57,6 +59,10 @@ export class Fetch {
this.interceptors.clear();
}
+ public getRequestCount$() {
+ return this.requestCount$.asObservable();
+ }
+
public readonly delete = this.shorthand('DELETE');
public readonly get = this.shorthand('GET');
public readonly head = this.shorthand('HEAD');
@@ -76,6 +82,7 @@ export class Fetch {
// a halt is called we do not resolve or reject, halting handling of the promise.
return new Promise>(async (resolve, reject) => {
try {
+ this.requestCount$.next(this.requestCount$.value + 1);
const interceptedOptions = await interceptRequest(
optionsWithPath,
this.interceptors,
@@ -98,6 +105,8 @@ export class Fetch {
if (!(error instanceof HttpInterceptHaltError)) {
reject(error);
}
+ } finally {
+ this.requestCount$.next(this.requestCount$.value - 1);
}
});
};
diff --git a/src/core/public/http/http_service.test.ts b/src/core/public/http/http_service.test.ts
index a40fcb06273dd..78220af9cc83b 100644
--- a/src/core/public/http/http_service.test.ts
+++ b/src/core/public/http/http_service.test.ts
@@ -24,6 +24,7 @@ import { loadingServiceMock } from './http_service.test.mocks';
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { HttpService } from './http_service';
+import { Observable } from 'rxjs';
describe('interceptors', () => {
afterEach(() => fetchMock.restore());
@@ -52,6 +53,18 @@ describe('interceptors', () => {
});
});
+describe('#setup()', () => {
+ it('registers Fetch#getLoadingCount$() with LoadingCountSetup#addLoadingCountSource()', () => {
+ const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
+ const fatalErrors = fatalErrorsServiceMock.createSetupContract();
+ const httpService = new HttpService();
+ httpService.setup({ fatalErrors, injectedMetadata });
+ const loadingServiceSetup = loadingServiceMock.setup.mock.results[0].value;
+ // We don't verify that this Observable comes from Fetch#getLoadingCount$() to avoid complex mocking
+ expect(loadingServiceSetup.addLoadingCountSource).toHaveBeenCalledWith(expect.any(Observable));
+ });
+});
+
describe('#stop()', () => {
it('calls loadingCount.stop()', () => {
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
diff --git a/src/core/public/http/http_service.ts b/src/core/public/http/http_service.ts
index 44fc9d65565d4..98de1d919c481 100644
--- a/src/core/public/http/http_service.ts
+++ b/src/core/public/http/http_service.ts
@@ -45,6 +45,7 @@ export class HttpService implements CoreService {
);
const fetchService = new Fetch({ basePath, kibanaVersion });
const loadingCount = this.loadingCount.setup({ fatalErrors });
+ loadingCount.addLoadingCountSource(fetchService.getRequestCount$());
this.service = {
basePath,
diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts
index 8ea672890ca29..c860e9de8334e 100644
--- a/src/core/public/mocks.ts
+++ b/src/core/public/mocks.ts
@@ -16,9 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { createMemoryHistory } from 'history';
+
+// Only import types from '.' to avoid triggering default Jest mocks.
+import { CoreContext, PluginInitializerContext, AppMountParameters } from '.';
+// Import values from their individual modules instead.
+import { ScopedHistory } from './application';
+
import { applicationServiceMock } from './application/application_service.mock';
import { chromeServiceMock } from './chrome/chrome_service.mock';
-import { CoreContext, PluginInitializerContext } from '.';
import { docLinksServiceMock } from './doc_links/doc_links_service.mock';
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
import { httpServiceMock } from './http/http_service.mock';
@@ -139,10 +145,27 @@ function createStorageMock() {
return storageMock;
}
+function createAppMountParametersMock(appBasePath = '') {
+ // Assemble an in-memory history mock using the provided basePath
+ const rawHistory = createMemoryHistory();
+ rawHistory.push(appBasePath);
+ const history = new ScopedHistory(rawHistory, appBasePath);
+
+ const params: jest.Mocked = {
+ appBasePath,
+ element: document.createElement('div'),
+ history,
+ onAppLeave: jest.fn(),
+ };
+
+ return params;
+}
+
export const coreMock = {
createCoreContext,
createSetup: createCoreSetupMock,
createStart: createCoreStartMock,
createPluginInitializerContext: pluginInitializerContextMock,
createStorage: createStorageMock,
+ createAppMountParamters: createAppMountParametersMock,
};
diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts
index b40dbdc1b6651..a91e128f62d2d 100644
--- a/src/core/server/config/deprecation/core_deprecations.test.ts
+++ b/src/core/server/config/deprecation/core_deprecations.test.ts
@@ -81,6 +81,19 @@ describe('core deprecations', () => {
});
});
+ describe('xsrfDeprecation', () => {
+ it('logs a warning if server.xsrf.whitelist is set', () => {
+ const { messages } = applyCoreDeprecations({
+ server: { xsrf: { whitelist: ['/path'] } },
+ });
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "It is not recommended to disable xsrf protections for API endpoints via [server.xsrf.whitelist]. It will be removed in 8.0 release. Instead, supply the \\"kbn-xsrf\\" header.",
+ ]
+ `);
+ });
+ });
+
describe('rewriteBasePath', () => {
it('logs a warning is server.basePath is set and server.rewriteBasePath is not', () => {
const { messages } = applyCoreDeprecations({
diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts
index 4fa51dcd5a082..d91e55115d0b1 100644
--- a/src/core/server/config/deprecation/core_deprecations.ts
+++ b/src/core/server/config/deprecation/core_deprecations.ts
@@ -38,6 +38,19 @@ const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
return settings;
};
+const xsrfDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
+ if (
+ has(settings, 'server.xsrf.whitelist') &&
+ get(settings, 'server.xsrf.whitelist').length > 0
+ ) {
+ log(
+ 'It is not recommended to disable xsrf protections for API endpoints via [server.xsrf.whitelist]. ' +
+ 'It will be removed in 8.0 release. Instead, supply the "kbn-xsrf" header.'
+ );
+ }
+ return settings;
+};
+
const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) {
log(
@@ -177,4 +190,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({
rewriteBasePathDeprecation,
cspRulesDeprecation,
mapManifestServiceUrlDeprecation,
+ xsrfDeprecation,
];
diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts
index 0a9541393284e..741c723ca9365 100644
--- a/src/core/server/http/http_server.mocks.ts
+++ b/src/core/server/http/http_server.mocks.ts
@@ -29,6 +29,7 @@ import {
RouteMethod,
KibanaResponseFactory,
RouteValidationSpec,
+ KibanaRouteState,
} from './router';
import { OnPreResponseToolkit } from './lifecycle/on_pre_response';
import { OnPostAuthToolkit } from './lifecycle/on_post_auth';
@@ -43,6 +44,7 @@ interface RequestFixtureOptions