diff --git a/.sass-lint.yml b/.sass-lint.yml index 624b80b1145f1..fac5e4a04c1d6 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -8,6 +8,7 @@ files: - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/lens/**/*.s+(a|c)ss' + - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' rules: quotes: - 2 diff --git a/Jenkinsfile b/Jenkinsfile index c0afcac731ff5..2cd9c372dee9c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -294,6 +294,6 @@ def buildXpack() { def runErrorReporter() { bash """ source src/dev/ci_setup/setup_env.sh - node src/dev/failed_tests/cli + node scripts/report_failed_tests """ } diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 7531cf9a06333..3c7943ba29388 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -70,7 +70,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | | [SavedObject](./kibana-plugin-public.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored in the attributes key as either an object or an array of objects. | +| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. | | [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | | | [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | | @@ -104,7 +104,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | | [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | -| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | | +| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | | [ToastInput](./kibana-plugin-public.toastinput.md) | | | [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.savedobject.attributes.md b/docs/development/core/public/kibana-plugin-public.savedobject.attributes.md index f9d39c15fcff4..0ec57d679920c 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobject.attributes.md +++ b/docs/development/core/public/kibana-plugin-public.savedobject.attributes.md @@ -4,7 +4,7 @@ ## SavedObject.attributes property -The data for a Saved Object is stored in the `attributes` key as either an object or an array of objects. +The data for a Saved Object is stored as an object in the `attributes` property. Signature: diff --git a/docs/development/core/public/kibana-plugin-public.savedobject.md b/docs/development/core/public/kibana-plugin-public.savedobject.md index 9bf0149f0854e..00260c62dd934 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobject.md +++ b/docs/development/core/public/kibana-plugin-public.savedobject.md @@ -15,7 +15,7 @@ export interface SavedObject | Property | Type | Description | | --- | --- | --- | -| [attributes](./kibana-plugin-public.savedobject.attributes.md) | T | The data for a Saved Object is stored in the attributes key as either an object or an array of objects. | +| [attributes](./kibana-plugin-public.savedobject.attributes.md) | T | The data for a Saved Object is stored as an object in the attributes property. | | [error](./kibana-plugin-public.savedobject.error.md) | {
message: string;
statusCode: number;
} | | | [id](./kibana-plugin-public.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-public.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectattribute.md b/docs/development/core/public/kibana-plugin-public.savedobjectattribute.md index f8d51390863eb..5ce6a60f76c79 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectattribute.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectattribute.md @@ -4,9 +4,10 @@ ## SavedObjectAttribute type +Type definition for a Saved Object attribute value Signature: ```typescript -export declare type SavedObjectAttribute = string | number | boolean | null | undefined | SavedObjectAttributes | SavedObjectAttributes[]; +export declare type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectattributes.md b/docs/development/core/public/kibana-plugin-public.savedobjectattributes.md index 4a9e096cc25b7..39c02216f4827 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectattributes.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectattributes.md @@ -4,7 +4,7 @@ ## SavedObjectAttributes interface -The data for a Saved Object is stored in the `attributes` key as either an object or an array of objects. +The data for a Saved Object is stored as an object in the `attributes` property. Signature: diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectattributesingle.md b/docs/development/core/public/kibana-plugin-public.savedobjectattributesingle.md new file mode 100644 index 0000000000000..3f2d70baa64e6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectattributesingle.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) + +## SavedObjectAttributeSingle type + +Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) + +Signature: + +```typescript +export declare type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 1247c52a5704b..02bbd028ac419 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -76,7 +76,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored in the attributes key as either an object or an array of objects. | +| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | | [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | | [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | | @@ -152,7 +152,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | +| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobject.attributes.md b/docs/development/core/server/kibana-plugin-server.savedobject.attributes.md index c3d521aa7bc2c..7049ca65e96d3 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobject.attributes.md +++ b/docs/development/core/server/kibana-plugin-server.savedobject.attributes.md @@ -4,7 +4,7 @@ ## SavedObject.attributes property -The data for a Saved Object is stored in the `attributes` key as either an object or an array of objects. +The data for a Saved Object is stored as an object in the `attributes` property. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.savedobject.md b/docs/development/core/server/kibana-plugin-server.savedobject.md index 1514980588746..c7099cdce7ecd 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobject.md +++ b/docs/development/core/server/kibana-plugin-server.savedobject.md @@ -15,7 +15,7 @@ export interface SavedObject | Property | Type | Description | | --- | --- | --- | -| [attributes](./kibana-plugin-server.savedobject.attributes.md) | T | The data for a Saved Object is stored in the attributes key as either an object or an array of objects. | +| [attributes](./kibana-plugin-server.savedobject.attributes.md) | T | The data for a Saved Object is stored as an object in the attributes property. | | [error](./kibana-plugin-server.savedobject.error.md) | {
message: string;
statusCode: number;
} | | | [id](./kibana-plugin-server.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-server.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectattribute.md b/docs/development/core/server/kibana-plugin-server.savedobjectattribute.md index 6a6c7c4d36bc6..6696d9c6ce96d 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectattribute.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectattribute.md @@ -4,9 +4,10 @@ ## SavedObjectAttribute type +Type definition for a Saved Object attribute value Signature: ```typescript -export declare type SavedObjectAttribute = string | number | boolean | null | undefined | SavedObjectAttributes | SavedObjectAttributes[]; +export declare type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; ``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectattributes.md b/docs/development/core/server/kibana-plugin-server.savedobjectattributes.md index 7ff1247e89f2d..6d24eb02b4eaf 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectattributes.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectattributes.md @@ -4,7 +4,7 @@ ## SavedObjectAttributes interface -The data for a Saved Object is stored in the `attributes` key as either an object or an array of objects. +The data for a Saved Object is stored as an object in the `attributes` property. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectattributesingle.md b/docs/development/core/server/kibana-plugin-server.savedobjectattributesingle.md new file mode 100644 index 0000000000000..b2ddb59ddb252 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectattributesingle.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) + +## SavedObjectAttributeSingle type + +Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) + +Signature: + +```typescript +export declare type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; +``` diff --git a/package.json b/package.json index 22a38eb8e7fec..ec89e923f5129 100644 --- a/package.json +++ b/package.json @@ -267,6 +267,7 @@ "@babel/parser": "^7.5.5", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/types": "^7.5.5", + "@elastic/elasticsearch": "^7.4.0", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", @@ -281,7 +282,6 @@ "@kbn/utility-types": "1.0.0", "@microsoft/api-documenter": "7.4.3", "@microsoft/api-extractor": "7.4.2", - "@octokit/rest": "^15.10.0", "@percy/agent": "^0.11.0", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", diff --git a/packages/kbn-dev-utils/src/kbn_client/errors.ts b/packages/kbn-dev-utils/src/axios/errors.ts similarity index 73% rename from packages/kbn-dev-utils/src/kbn_client/errors.ts rename to packages/kbn-dev-utils/src/axios/errors.ts index 068c68555b62a..e449c49a483b7 100644 --- a/packages/kbn-dev-utils/src/kbn_client/errors.ts +++ b/packages/kbn-dev-utils/src/axios/errors.ts @@ -28,15 +28,9 @@ export interface AxiosResponseError extends AxiosError { } export const isAxiosRequestError = (error: any): error is AxiosRequestError => { - return error && error.code === undefined && error.response === undefined; + return error && error.config && error.response === undefined; }; -export const isAxiosResponseError = (error: any): error is AxiosResponseError => { - return error && error.code !== undefined && error.response !== undefined; -}; - -export const isConcliftOnGetError = (error: any) => { - return ( - isAxiosResponseError(error) && error.config.method === 'GET' && error.response.status === 409 - ); +export const isAxiosResponseError = (error: any): error is AxiosResponseError => { + return error && error.response && error.response.status !== undefined; }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/shim/index.ts b/packages/kbn-dev-utils/src/axios/index.ts similarity index 94% rename from src/legacy/core_plugins/vis_type_metric/public/shim/index.ts rename to packages/kbn-dev-utils/src/axios/index.ts index cfc7b62ff4f86..8764f468c5333 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/shim/index.ts +++ b/packages/kbn-dev-utils/src/axios/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './legacy_dependencies_plugin'; +export * from './errors'; diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 5c69036a4b13a..7bd22d73df8d5 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -18,9 +18,15 @@ */ export { withProcRunner } from './proc_runner'; -export { ToolingLog, ToolingLogTextWriter, pickLevelFromFlags } from './tooling_log'; +export { + ToolingLog, + ToolingLogTextWriter, + pickLevelFromFlags, + ToolingLogCollectingWriter, +} from './tooling_log'; export { createAbsolutePathSerializer } from './serializers'; export { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from './certs'; export { run, createFailError, createFlagError, combineErrors, isFailError, Flags } from './run'; export { REPO_ROOT } from './constants'; export { KbnClient } from './kbn_client'; +export * from './axios'; diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts index 56d4d7f99e0b8..25962c91a896d 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts @@ -21,9 +21,15 @@ import Url from 'url'; import Axios from 'axios'; -import { isAxiosRequestError, isConcliftOnGetError } from './errors'; +import { isAxiosRequestError, isAxiosResponseError } from '../axios'; import { ToolingLog } from '../tooling_log'; +const isConcliftOnGetError = (error: any) => { + return ( + isAxiosResponseError(error) && error.config.method === 'GET' && error.response.status === 409 + ); +}; + export const uriencode = ( strings: TemplateStringsArray, ...values: Array diff --git a/packages/kbn-dev-utils/src/tooling_log/index.ts b/packages/kbn-dev-utils/src/tooling_log/index.ts index e00e1c8b7d1b9..1f5afac26d561 100644 --- a/packages/kbn-dev-utils/src/tooling_log/index.ts +++ b/packages/kbn-dev-utils/src/tooling_log/index.ts @@ -20,3 +20,4 @@ export { ToolingLog } from './tooling_log'; export { ToolingLogTextWriter, ToolingLogTextWriterConfig } from './tooling_log_text_writer'; export { pickLevelFromFlags, LogLevel } from './log_levels'; +export { ToolingLogCollectingWriter } from './tooling_log_collecting_writer'; diff --git a/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts b/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts new file mode 100644 index 0000000000000..46026bdc369d4 --- /dev/null +++ b/packages/kbn-dev-utils/src/tooling_log/tooling_log_collecting_writer.ts @@ -0,0 +1,36 @@ +/* + * 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 { ToolingLogTextWriter } from './tooling_log_text_writer'; + +export class ToolingLogCollectingWriter extends ToolingLogTextWriter { + messages: string[] = []; + + constructor() { + super({ + level: 'verbose', + writeTo: { + write: msg => { + // trim trailing new line + this.messages.push(msg.slice(0, -1)); + }, + }, + }); + } +} diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 80eaaa297227c..40a3bd475f1c1 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -6,5 +6,5 @@ }, "include": [ "src/**/*" - ], + ] } 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 523317ab63e85..c823bbc3be264 100644 --- a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js +++ b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js @@ -25,7 +25,7 @@ import { stat, readFileSync } from 'fs'; import { snakeCase } from 'lodash'; import del from 'del'; import { withProcRunner, ToolingLog } from '@kbn/dev-utils'; -import { createEsTestCluster } from '@kbn/test'; +import { createLegacyEsTestCluster } from '@kbn/test'; import execa from 'execa'; const statP = util.promisify(stat); @@ -83,7 +83,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug it(`'yarn start' should result in the spec plugin being initialized on kibana's stdout`, async () => { const log = new ToolingLog(); - const es = createEsTestCluster({ license: 'basic', log }); + const es = createLegacyEsTestCluster({ license: 'basic', log }); await es.start(); await withProcRunner(log, async proc => { await proc.run('kibana', { diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 5d2f5c786f2c2..f52c194cfee6a 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -38648,7 +38648,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(611); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(613); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -38828,8 +38828,8 @@ const EventEmitter = __webpack_require__(46); const path = __webpack_require__(16); const arrify = __webpack_require__(407); const globby = __webpack_require__(408); -const cpFile = __webpack_require__(601); -const CpyError = __webpack_require__(609); +const cpFile = __webpack_require__(603); +const CpyError = __webpack_require__(611); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -38958,8 +38958,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(170); const glob = __webpack_require__(37); const fastGlob = __webpack_require__(409); -const dirGlob = __webpack_require__(594); -const gitignore = __webpack_require__(597); +const dirGlob = __webpack_require__(596); +const gitignore = __webpack_require__(599); const DEFAULT_FILTER = () => false; @@ -39128,11 +39128,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(411); var taskManager = __webpack_require__(412); -var reader_async_1 = __webpack_require__(564); -var reader_stream_1 = __webpack_require__(588); -var reader_sync_1 = __webpack_require__(589); -var arrayUtils = __webpack_require__(591); -var streamUtils = __webpack_require__(592); +var reader_async_1 = __webpack_require__(566); +var reader_stream_1 = __webpack_require__(590); +var reader_sync_1 = __webpack_require__(591); +var arrayUtils = __webpack_require__(593); +var streamUtils = __webpack_require__(594); /** * Synchronous API. */ @@ -39790,17 +39790,17 @@ module.exports = function isGlob(str, options) { var util = __webpack_require__(29); var braces = __webpack_require__(420); -var toRegex = __webpack_require__(522); -var extend = __webpack_require__(530); +var toRegex = __webpack_require__(524); +var extend = __webpack_require__(532); /** * Local dependencies */ -var compilers = __webpack_require__(533); -var parsers = __webpack_require__(560); -var cache = __webpack_require__(561); -var utils = __webpack_require__(562); +var compilers = __webpack_require__(535); +var parsers = __webpack_require__(562); +var cache = __webpack_require__(563); +var utils = __webpack_require__(564); var MAX_LENGTH = 1024 * 64; /** @@ -46382,9 +46382,9 @@ module.exports = Braces; var Base = __webpack_require__(461); var define = __webpack_require__(422); -var Compiler = __webpack_require__(490); -var Parser = __webpack_require__(519); -var utils = __webpack_require__(499); +var Compiler = __webpack_require__(491); +var Parser = __webpack_require__(521); +var utils = __webpack_require__(501); var regexCache = {}; var cache = {}; @@ -46566,9 +46566,9 @@ var define = __webpack_require__(462); var CacheBase = __webpack_require__(463); var Emitter = __webpack_require__(464); var isObject = __webpack_require__(440); -var merge = __webpack_require__(481); -var pascal = __webpack_require__(484); -var cu = __webpack_require__(485); +var merge = __webpack_require__(482); +var pascal = __webpack_require__(485); +var cu = __webpack_require__(486); /** * Optionally define a custom `cache` namespace to use. @@ -47049,7 +47049,7 @@ var union = __webpack_require__(469); var del = __webpack_require__(473); var get = __webpack_require__(471); var has = __webpack_require__(478); -var set = __webpack_require__(472); +var set = __webpack_require__(481); /** * Create a `Cache` constructor that when instantiated will @@ -47775,52 +47775,60 @@ function toString(val) { -var split = __webpack_require__(436); +var toPath = __webpack_require__(468); var extend = __webpack_require__(430); var isPlainObject = __webpack_require__(439); var isObject = __webpack_require__(431); -module.exports = function(obj, prop, val) { +module.exports = function(obj, path, val) { if (!isObject(obj)) { return obj; } - if (Array.isArray(prop)) { - prop = [].concat.apply([], prop).join('.'); + if (Array.isArray(path)) { + path = toPath(path); } - if (typeof prop !== 'string') { + if (typeof path !== 'string') { return obj; } - var keys = split(prop, {sep: '.', brackets: true}).filter(isValidKey); - var len = keys.length; - var idx = -1; - var current = obj; + var segs = path.split('.'); + var len = segs.length, i = -1; + var res = obj; + var last; - while (++idx < len) { - var key = keys[idx]; - if (idx !== len - 1) { - if (!isObject(current[key])) { - current[key] = {}; - } - current = current[key]; - continue; + while (++i < len) { + var key = segs[i]; + + while (key[key.length - 1] === '\\') { + key = key.slice(0, -1) + '.' + segs[++i]; } - if (isPlainObject(current[key]) && isPlainObject(val)) { - current[key] = extend({}, current[key], val); - } else { - current[key] = val; + if (i === len - 1) { + last = key; + break; + } + + if (!isObject(obj[key])) { + obj[key] = {}; } + obj = obj[key]; } - return obj; + if (obj.hasOwnProperty(last) && isObject(obj[last])) { + if (isPlainObject(val)) { + extend(obj[last], val); + } else { + obj[last] = val; + } + + } else { + obj[last] = val; + } + return res; }; -function isValidKey(key) { - return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; -} /***/ }), @@ -48183,10 +48191,68 @@ module.exports = function kindOf(val) { /***/ (function(module, exports, __webpack_require__) { "use strict"; +/*! + * set-value + * + * Copyright (c) 2014-2015, 2017, Jon Schlinkert. + * Released under the MIT License. + */ + -var isExtendable = __webpack_require__(482); -var forIn = __webpack_require__(483); +var split = __webpack_require__(436); +var extend = __webpack_require__(430); +var isPlainObject = __webpack_require__(439); +var isObject = __webpack_require__(431); + +module.exports = function(obj, prop, val) { + if (!isObject(obj)) { + return obj; + } + + if (Array.isArray(prop)) { + prop = [].concat.apply([], prop).join('.'); + } + + if (typeof prop !== 'string') { + return obj; + } + + var keys = split(prop, {sep: '.', brackets: true}); + var len = keys.length; + var idx = -1; + var current = obj; + + while (++idx < len) { + var key = keys[idx]; + if (idx !== len - 1) { + if (!isObject(current[key])) { + current[key] = {}; + } + current = current[key]; + continue; + } + + if (isPlainObject(current[key]) && isPlainObject(val)) { + current[key] = extend({}, current[key], val); + } else { + current[key] = val; + } + } + + return obj; +}; + + +/***/ }), +/* 482 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var isExtendable = __webpack_require__(483); +var forIn = __webpack_require__(484); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -48250,7 +48316,7 @@ module.exports = mixinDeep; /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48271,7 +48337,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48294,7 +48360,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, exports) { /*! @@ -48321,14 +48387,14 @@ module.exports = pascalcase; /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(486); +var utils = __webpack_require__(487); /** * Expose class utils @@ -48693,7 +48759,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48710,7 +48776,7 @@ var utils = {}; utils.union = __webpack_require__(470); utils.define = __webpack_require__(422); utils.isObj = __webpack_require__(440); -utils.staticExtend = __webpack_require__(487); +utils.staticExtend = __webpack_require__(488); /** @@ -48721,7 +48787,7 @@ module.exports = utils; /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48734,7 +48800,7 @@ module.exports = utils; -var copy = __webpack_require__(488); +var copy = __webpack_require__(489); var define = __webpack_require__(422); var util = __webpack_require__(29); @@ -48818,14 +48884,14 @@ module.exports = extend; /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var typeOf = __webpack_require__(445); -var copyDescriptor = __webpack_require__(489); +var copyDescriptor = __webpack_require__(490); var define = __webpack_require__(422); /** @@ -48999,7 +49065,7 @@ module.exports.has = has; /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49087,16 +49153,16 @@ function isObject(val) { /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(491); +var use = __webpack_require__(492); var define = __webpack_require__(422); -var debug = __webpack_require__(492)('snapdragon:compiler'); -var utils = __webpack_require__(499); +var debug = __webpack_require__(494)('snapdragon:compiler'); +var utils = __webpack_require__(501); /** * Create a new `Compiler` with the given `options`. @@ -49250,7 +49316,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(518); + var sourcemaps = __webpack_require__(520); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -49271,28 +49337,33 @@ module.exports = Compiler; /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /*! * use * - * Copyright (c) 2015-2017, Jon Schlinkert. + * Copyright (c) 2015, 2017, Jon Schlinkert. * Released under the MIT License. */ -module.exports = function base(app, options) { - if (!isObject(app) && typeof app !== 'function') { - throw new TypeError('expected an object or function'); +var utils = __webpack_require__(493); + +module.exports = function base(app, opts) { + if (!utils.isObject(app) && typeof app !== 'function') { + throw new TypeError('use: expect `app` be an object or function'); } - var opts = isObject(options) ? options : {}; - var prop = typeof opts.prop === 'string' ? opts.prop : 'fns'; + if (!utils.isObject(opts)) { + opts = {}; + } + + var prop = utils.isString(opts.prop) ? opts.prop : 'fns'; if (!Array.isArray(app[prop])) { - define(app, prop, []); + utils.define(app, prop, []); } /** @@ -49325,7 +49396,7 @@ module.exports = function base(app, options) { * @api public */ - define(app, 'use', use); + utils.define(app, 'use', use); /** * Run all plugins on `fns`. Any plugin that returns a function @@ -49341,17 +49412,9 @@ module.exports = function base(app, options) { * @api public */ - define(app, 'run', function(val) { - if (!isObject(val)) return; - - if (!val.use || !val.run) { - define(val, prop, val[prop] || []); - define(val, 'use', use); - } - - if (!val[prop] || val[prop].indexOf(base) === -1) { - val.use(base); - } + utils.define(app, 'run', function(val) { + if (!utils.isObject(val)) return; + decorate(val); var self = this || app; var fns = self[prop]; @@ -49369,71 +49432,70 @@ module.exports = function base(app, options) { * `fns` array to be called by the `run` method. */ - function use(type, fn, options) { - var offset = 1; - - if (typeof type === 'string' || Array.isArray(type)) { - fn = wrap(type, fn); - offset++; - } else { - options = fn; - fn = type; - } - + function use(fn, options) { if (typeof fn !== 'function') { - throw new TypeError('expected a function'); + throw new TypeError('.use expects `fn` be a function'); } var self = this || app; - var fns = self[prop]; - - var args = [].slice.call(arguments, offset); - args.unshift(self); - - if (typeof opts.hook === 'function') { - opts.hook.apply(self, args); + if (typeof opts.fn === 'function') { + opts.fn.call(self, self, options); } - var val = fn.apply(self, args); - if (typeof val === 'function' && fns.indexOf(val) === -1) { - fns.push(val); + var plugin = fn.call(self, self); + if (typeof plugin === 'function') { + var fns = self[prop]; + fns.push(plugin); } return self; } /** - * Wrap a named plugin function so that it's only called on objects of the - * given `type` - * - * @param {String} `type` - * @param {Function} `fn` Plugin function - * @return {Function} + * Ensure the `.use` method exists on `val` */ - function wrap(type, fn) { - return function plugin() { - return this.type === type ? fn.apply(this, arguments) : plugin; - }; + function decorate(val) { + if (!val.use || !val.run) { + base(val); + } } return app; }; -function isObject(val) { - return val && typeof val === 'object' && !Array.isArray(val); -} -function define(obj, key, val) { - Object.defineProperty(obj, key, { - configurable: true, - writable: true, - value: val - }); -} +/***/ }), +/* 493 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = {}; + + + +/** + * Lazily required module dependencies + */ + +utils.define = __webpack_require__(422); +utils.isObject = __webpack_require__(440); + + +utils.isString = function(val) { + return val && typeof val === 'string'; +}; + +/** + * Expose `utils` modules + */ + +module.exports = utils; /***/ }), -/* 492 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49442,14 +49504,14 @@ function define(obj, key, val) { */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(493); + module.exports = __webpack_require__(495); } else { - module.exports = __webpack_require__(496); + module.exports = __webpack_require__(498); } /***/ }), -/* 493 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49458,7 +49520,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(494); +exports = module.exports = __webpack_require__(496); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -49640,7 +49702,7 @@ function localstorage() { /***/ }), -/* 494 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { @@ -49656,7 +49718,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(495); +exports.humanize = __webpack_require__(497); /** * The currently active debug mode names, and names to skip. @@ -49848,7 +49910,7 @@ function coerce(val) { /***/ }), -/* 495 */ +/* 497 */ /***/ (function(module, exports) { /** @@ -50006,14 +50068,14 @@ function plural(ms, n, name) { /***/ }), -/* 496 */ +/* 498 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(497); +var tty = __webpack_require__(499); var util = __webpack_require__(29); /** @@ -50022,7 +50084,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(494); +exports = module.exports = __webpack_require__(496); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -50201,7 +50263,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(498); + var net = __webpack_require__(500); stream = new net.Socket({ fd: fd, readable: false, @@ -50260,19 +50322,19 @@ exports.enable(load()); /***/ }), -/* 497 */ +/* 499 */ /***/ (function(module, exports) { module.exports = require("tty"); /***/ }), -/* 498 */ +/* 500 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 499 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50283,8 +50345,8 @@ module.exports = require("net"); */ exports.extend = __webpack_require__(430); -exports.SourceMap = __webpack_require__(500); -exports.sourceMapResolve = __webpack_require__(511); +exports.SourceMap = __webpack_require__(502); +exports.sourceMapResolve = __webpack_require__(513); /** * Convert backslash in the given string to forward slashes @@ -50327,7 +50389,7 @@ exports.last = function(arr, n) { /***/ }), -/* 500 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -50335,13 +50397,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(501).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(507).SourceMapConsumer; -exports.SourceNode = __webpack_require__(510).SourceNode; +exports.SourceMapGenerator = __webpack_require__(503).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(509).SourceMapConsumer; +exports.SourceNode = __webpack_require__(512).SourceNode; /***/ }), -/* 501 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50351,10 +50413,10 @@ exports.SourceNode = __webpack_require__(510).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(502); -var util = __webpack_require__(504); -var ArraySet = __webpack_require__(505).ArraySet; -var MappingList = __webpack_require__(506).MappingList; +var base64VLQ = __webpack_require__(504); +var util = __webpack_require__(506); +var ArraySet = __webpack_require__(507).ArraySet; +var MappingList = __webpack_require__(508).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -50763,7 +50825,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 502 */ +/* 504 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50803,7 +50865,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(503); +var base64 = __webpack_require__(505); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -50909,7 +50971,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 503 */ +/* 505 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50982,7 +51044,7 @@ exports.decode = function (charCode) { /***/ }), -/* 504 */ +/* 506 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51405,7 +51467,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 505 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51415,7 +51477,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(504); +var util = __webpack_require__(506); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -51532,7 +51594,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 506 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51542,7 +51604,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(504); +var util = __webpack_require__(506); /** * Determine whether mappingB is after mappingA with respect to generated @@ -51617,7 +51679,7 @@ exports.MappingList = MappingList; /***/ }), -/* 507 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51627,11 +51689,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(504); -var binarySearch = __webpack_require__(508); -var ArraySet = __webpack_require__(505).ArraySet; -var base64VLQ = __webpack_require__(502); -var quickSort = __webpack_require__(509).quickSort; +var util = __webpack_require__(506); +var binarySearch = __webpack_require__(510); +var ArraySet = __webpack_require__(507).ArraySet; +var base64VLQ = __webpack_require__(504); +var quickSort = __webpack_require__(511).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -52705,7 +52767,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 508 */ +/* 510 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52822,7 +52884,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 509 */ +/* 511 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52942,7 +53004,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 510 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52952,8 +53014,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(501).SourceMapGenerator; -var util = __webpack_require__(504); +var SourceMapGenerator = __webpack_require__(503).SourceMapGenerator; +var util = __webpack_require__(506); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -53361,17 +53423,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 511 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(512) -var resolveUrl = __webpack_require__(513) -var decodeUriComponent = __webpack_require__(514) -var urix = __webpack_require__(516) -var atob = __webpack_require__(517) +var sourceMappingURL = __webpack_require__(514) +var resolveUrl = __webpack_require__(515) +var decodeUriComponent = __webpack_require__(516) +var urix = __webpack_require__(518) +var atob = __webpack_require__(519) @@ -53669,7 +53731,7 @@ module.exports = { /***/ }), -/* 512 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -53732,7 +53794,7 @@ void (function(root, factory) { /***/ }), -/* 513 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53750,13 +53812,13 @@ module.exports = resolveUrl /***/ }), -/* 514 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(515) +var decodeUriComponent = __webpack_require__(517) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -53767,7 +53829,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 515 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53868,7 +53930,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 516 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53891,7 +53953,7 @@ module.exports = urix /***/ }), -/* 517 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53905,7 +53967,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 518 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53914,7 +53976,7 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); var define = __webpack_require__(422); -var utils = __webpack_require__(499); +var utils = __webpack_require__(501); /** * Expose `mixin()`. @@ -54057,19 +54119,19 @@ exports.comment = function(node) { /***/ }), -/* 519 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(491); +var use = __webpack_require__(492); var util = __webpack_require__(29); -var Cache = __webpack_require__(520); +var Cache = __webpack_require__(522); var define = __webpack_require__(422); -var debug = __webpack_require__(492)('snapdragon:parser'); -var Position = __webpack_require__(521); -var utils = __webpack_require__(499); +var debug = __webpack_require__(494)('snapdragon:parser'); +var Position = __webpack_require__(523); +var utils = __webpack_require__(501); /** * Create a new `Parser` with the given `input` and `options`. @@ -54597,7 +54659,7 @@ module.exports = Parser; /***/ }), -/* 520 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54704,7 +54766,7 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 521 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54725,16 +54787,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(523); -var define = __webpack_require__(529); -var extend = __webpack_require__(530); -var not = __webpack_require__(532); +var safe = __webpack_require__(525); +var define = __webpack_require__(531); +var extend = __webpack_require__(532); +var not = __webpack_require__(534); var MAX_LENGTH = 1024 * 64; /** @@ -54887,10 +54949,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 523 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(524); +var parse = __webpack_require__(526); var types = parse.types; module.exports = function (re, opts) { @@ -54936,13 +54998,13 @@ function isRegExp (x) { /***/ }), -/* 524 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(525); -var types = __webpack_require__(526); -var sets = __webpack_require__(527); -var positions = __webpack_require__(528); +var util = __webpack_require__(527); +var types = __webpack_require__(528); +var sets = __webpack_require__(529); +var positions = __webpack_require__(530); module.exports = function(regexpStr) { @@ -55224,11 +55286,11 @@ module.exports.types = types; /***/ }), -/* 525 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(526); -var sets = __webpack_require__(527); +var types = __webpack_require__(528); +var sets = __webpack_require__(529); // All of these are private and only used by randexp. @@ -55341,7 +55403,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 526 */ +/* 528 */ /***/ (function(module, exports) { module.exports = { @@ -55357,10 +55419,10 @@ module.exports = { /***/ }), -/* 527 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(526); +var types = __webpack_require__(528); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -55445,10 +55507,10 @@ exports.anyChar = function() { /***/ }), -/* 528 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(526); +var types = __webpack_require__(528); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -55468,7 +55530,7 @@ exports.end = function() { /***/ }), -/* 529 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55513,13 +55575,13 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 530 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(531); +var isExtendable = __webpack_require__(533); var assignSymbols = __webpack_require__(441); module.exports = Object.assign || function(obj/*, objects*/) { @@ -55580,7 +55642,7 @@ function isEnum(obj, key) { /***/ }), -/* 531 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55601,14 +55663,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 532 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(530); -var safe = __webpack_require__(523); +var extend = __webpack_require__(532); +var safe = __webpack_require__(525); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -55680,14 +55742,14 @@ module.exports = toRegex; /***/ }), -/* 533 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(534); -var extglob = __webpack_require__(549); +var nanomatch = __webpack_require__(536); +var extglob = __webpack_require__(551); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -55764,7 +55826,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 534 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55776,16 +55838,16 @@ function escapeExtglobs(compiler) { var util = __webpack_require__(29); var toRegex = __webpack_require__(421); -var extend = __webpack_require__(535); +var extend = __webpack_require__(537); /** * Local dependencies */ -var compilers = __webpack_require__(537); -var parsers = __webpack_require__(538); -var cache = __webpack_require__(541); -var utils = __webpack_require__(543); +var compilers = __webpack_require__(539); +var parsers = __webpack_require__(540); +var cache = __webpack_require__(543); +var utils = __webpack_require__(545); var MAX_LENGTH = 1024 * 64; /** @@ -56609,13 +56671,13 @@ module.exports = nanomatch; /***/ }), -/* 535 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(536); +var isExtendable = __webpack_require__(538); var assignSymbols = __webpack_require__(441); module.exports = Object.assign || function(obj/*, objects*/) { @@ -56676,7 +56738,7 @@ function isEnum(obj, key) { /***/ }), -/* 536 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56697,7 +56759,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 537 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57043,7 +57105,7 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 538 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57051,7 +57113,7 @@ module.exports = function(nanomatch, options) { var regexNot = __webpack_require__(432); var toRegex = __webpack_require__(421); -var isOdd = __webpack_require__(539); +var isOdd = __webpack_require__(541); /** * Characters to use in negation regex (we want to "not" match @@ -57437,7 +57499,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 539 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57450,7 +57512,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(540); +var isNumber = __webpack_require__(542); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -57464,7 +57526,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 540 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57492,14 +57554,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 541 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(542))(); +module.exports = new (__webpack_require__(544))(); /***/ }), -/* 542 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57512,7 +57574,7 @@ module.exports = new (__webpack_require__(542))(); -var MapCache = __webpack_require__(520); +var MapCache = __webpack_require__(522); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -57634,7 +57696,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 543 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57647,13 +57709,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(544)(); +var isWindows = __webpack_require__(546)(); var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(545); -utils.diff = __webpack_require__(546); -utils.extend = __webpack_require__(535); -utils.pick = __webpack_require__(547); -utils.typeOf = __webpack_require__(548); +utils.define = __webpack_require__(547); +utils.diff = __webpack_require__(548); +utils.extend = __webpack_require__(537); +utils.pick = __webpack_require__(549); +utils.typeOf = __webpack_require__(550); utils.unique = __webpack_require__(433); /** @@ -58020,7 +58082,7 @@ utils.unixify = function(options) { /***/ }), -/* 544 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -58048,7 +58110,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 545 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58093,7 +58155,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 546 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58147,7 +58209,7 @@ function diffArray(one, two) { /***/ }), -/* 547 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58189,7 +58251,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 548 */ +/* 550 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -58324,7 +58386,7 @@ function isBuffer(val) { /***/ }), -/* 549 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58342,10 +58404,10 @@ var toRegex = __webpack_require__(421); * Local dependencies */ -var compilers = __webpack_require__(550); -var parsers = __webpack_require__(556); -var Extglob = __webpack_require__(559); -var utils = __webpack_require__(558); +var compilers = __webpack_require__(552); +var parsers = __webpack_require__(558); +var Extglob = __webpack_require__(561); +var utils = __webpack_require__(560); var MAX_LENGTH = 1024 * 64; /** @@ -58662,13 +58724,13 @@ module.exports = extglob; /***/ }), -/* 550 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(551); +var brackets = __webpack_require__(553); /** * Extglob compilers @@ -58838,7 +58900,7 @@ module.exports = function(extglob) { /***/ }), -/* 551 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58848,14 +58910,14 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(552); -var parsers = __webpack_require__(554); +var compilers = __webpack_require__(554); +var parsers = __webpack_require__(556); /** * Module dependencies */ -var debug = __webpack_require__(492)('expand-brackets'); +var debug = __webpack_require__(494)('expand-brackets'); var extend = __webpack_require__(430); var Snapdragon = __webpack_require__(460); var toRegex = __webpack_require__(421); @@ -59056,13 +59118,13 @@ module.exports = brackets; /***/ }), -/* 552 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(553); +var posix = __webpack_require__(555); module.exports = function(brackets) { brackets.compiler @@ -59150,7 +59212,7 @@ module.exports = function(brackets) { /***/ }), -/* 553 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59179,13 +59241,13 @@ module.exports = { /***/ }), -/* 554 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(555); +var utils = __webpack_require__(557); var define = __webpack_require__(422); /** @@ -59405,7 +59467,7 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 555 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59446,15 +59508,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 556 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(551); -var define = __webpack_require__(557); -var utils = __webpack_require__(558); +var brackets = __webpack_require__(553); +var define = __webpack_require__(559); +var utils = __webpack_require__(560); /** * Characters to use in text regex (we want to "not" match @@ -59609,7 +59671,7 @@ module.exports = parsers; /***/ }), -/* 557 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59647,14 +59709,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 558 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var regex = __webpack_require__(432); -var Cache = __webpack_require__(542); +var Cache = __webpack_require__(544); /** * Utils @@ -59723,7 +59785,7 @@ utils.createRegex = function(str) { /***/ }), -/* 559 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59734,15 +59796,15 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(460); -var define = __webpack_require__(557); +var define = __webpack_require__(559); var extend = __webpack_require__(430); /** * Local dependencies */ -var compilers = __webpack_require__(550); -var parsers = __webpack_require__(556); +var compilers = __webpack_require__(552); +var parsers = __webpack_require__(558); /** * Customize Snapdragon parser and renderer @@ -59808,16 +59870,16 @@ module.exports = Extglob; /***/ }), -/* 560 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(549); -var nanomatch = __webpack_require__(534); +var extglob = __webpack_require__(551); +var nanomatch = __webpack_require__(536); var regexNot = __webpack_require__(432); -var toRegex = __webpack_require__(522); +var toRegex = __webpack_require__(524); var not; /** @@ -59898,14 +59960,14 @@ function textRegex(pattern) { /***/ }), -/* 561 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(542))(); +module.exports = new (__webpack_require__(544))(); /***/ }), -/* 562 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59919,11 +59981,11 @@ var path = __webpack_require__(16); */ var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(529); -utils.diff = __webpack_require__(546); -utils.extend = __webpack_require__(530); -utils.pick = __webpack_require__(547); -utils.typeOf = __webpack_require__(563); +utils.define = __webpack_require__(531); +utils.diff = __webpack_require__(548); +utils.extend = __webpack_require__(532); +utils.pick = __webpack_require__(549); +utils.typeOf = __webpack_require__(565); utils.unique = __webpack_require__(433); /** @@ -60221,7 +60283,7 @@ utils.unixify = function(options) { /***/ }), -/* 563 */ +/* 565 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -60356,7 +60418,7 @@ function isBuffer(val) { /***/ }), -/* 564 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60375,9 +60437,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(565); -var reader_1 = __webpack_require__(578); -var fs_stream_1 = __webpack_require__(582); +var readdir = __webpack_require__(567); +var reader_1 = __webpack_require__(580); +var fs_stream_1 = __webpack_require__(584); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -60438,15 +60500,15 @@ exports.default = ReaderAsync; /***/ }), -/* 565 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(566); -const readdirAsync = __webpack_require__(574); -const readdirStream = __webpack_require__(577); +const readdirSync = __webpack_require__(568); +const readdirAsync = __webpack_require__(576); +const readdirStream = __webpack_require__(579); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -60530,7 +60592,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 566 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60538,11 +60600,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(567); +const DirectoryReader = __webpack_require__(569); let syncFacade = { - fs: __webpack_require__(572), - forEach: __webpack_require__(573), + fs: __webpack_require__(574), + forEach: __webpack_require__(575), sync: true }; @@ -60571,7 +60633,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 567 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60580,9 +60642,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(28).Readable; const EventEmitter = __webpack_require__(46).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(568); -const stat = __webpack_require__(570); -const call = __webpack_require__(571); +const normalizeOptions = __webpack_require__(570); +const stat = __webpack_require__(572); +const call = __webpack_require__(573); /** * Asynchronously reads the contents of a directory and streams the results @@ -60958,14 +61020,14 @@ module.exports = DirectoryReader; /***/ }), -/* 568 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(569); +const globToRegExp = __webpack_require__(571); module.exports = normalizeOptions; @@ -61142,7 +61204,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 569 */ +/* 571 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -61279,13 +61341,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 570 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(571); +const call = __webpack_require__(573); module.exports = stat; @@ -61360,7 +61422,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 571 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61421,14 +61483,14 @@ function callOnce (fn) { /***/ }), -/* 572 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(571); +const call = __webpack_require__(573); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -61492,7 +61554,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 573 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61521,7 +61583,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 574 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61529,12 +61591,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(575); -const DirectoryReader = __webpack_require__(567); +const maybe = __webpack_require__(577); +const DirectoryReader = __webpack_require__(569); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(576), + forEach: __webpack_require__(578), async: true }; @@ -61576,7 +61638,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 575 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61603,7 +61665,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 576 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61639,7 +61701,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 577 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61647,11 +61709,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(567); +const DirectoryReader = __webpack_require__(569); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(576), + forEach: __webpack_require__(578), async: true }; @@ -61671,16 +61733,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 578 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(579); -var entry_1 = __webpack_require__(581); -var pathUtil = __webpack_require__(580); +var deep_1 = __webpack_require__(581); +var entry_1 = __webpack_require__(583); +var pathUtil = __webpack_require__(582); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -61746,13 +61808,13 @@ exports.default = Reader; /***/ }), -/* 579 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(580); +var pathUtils = __webpack_require__(582); var patternUtils = __webpack_require__(413); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -61836,7 +61898,7 @@ exports.default = DeepFilter; /***/ }), -/* 580 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61867,13 +61929,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 581 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(580); +var pathUtils = __webpack_require__(582); var patternUtils = __webpack_require__(413); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -61959,7 +62021,7 @@ exports.default = EntryFilter; /***/ }), -/* 582 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61979,8 +62041,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var fsStat = __webpack_require__(583); -var fs_1 = __webpack_require__(587); +var fsStat = __webpack_require__(585); +var fs_1 = __webpack_require__(589); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -62030,14 +62092,14 @@ exports.default = FileSystemStream; /***/ }), -/* 583 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(584); -const statProvider = __webpack_require__(586); +const optionsManager = __webpack_require__(586); +const statProvider = __webpack_require__(588); /** * Asynchronous API. */ @@ -62068,13 +62130,13 @@ exports.statSync = statSync; /***/ }), -/* 584 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(585); +const fsAdapter = __webpack_require__(587); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -62087,7 +62149,7 @@ exports.prepare = prepare; /***/ }), -/* 585 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62110,7 +62172,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 586 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62162,7 +62224,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 587 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62193,7 +62255,7 @@ exports.default = FileSystem; /***/ }), -/* 588 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62213,9 +62275,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var readdir = __webpack_require__(565); -var reader_1 = __webpack_require__(578); -var fs_stream_1 = __webpack_require__(582); +var readdir = __webpack_require__(567); +var reader_1 = __webpack_require__(580); +var fs_stream_1 = __webpack_require__(584); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -62283,7 +62345,7 @@ exports.default = ReaderStream; /***/ }), -/* 589 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62302,9 +62364,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(565); -var reader_1 = __webpack_require__(578); -var fs_sync_1 = __webpack_require__(590); +var readdir = __webpack_require__(567); +var reader_1 = __webpack_require__(580); +var fs_sync_1 = __webpack_require__(592); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -62364,7 +62426,7 @@ exports.default = ReaderSync; /***/ }), -/* 590 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62383,8 +62445,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(583); -var fs_1 = __webpack_require__(587); +var fsStat = __webpack_require__(585); +var fs_1 = __webpack_require__(589); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -62430,7 +62492,7 @@ exports.default = FileSystemSync; /***/ }), -/* 591 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62446,13 +62508,13 @@ exports.flatten = flatten; /***/ }), -/* 592 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(593); +var merge2 = __webpack_require__(595); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -62467,7 +62529,7 @@ exports.merge = merge; /***/ }), -/* 593 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62581,13 +62643,13 @@ function pauseStreams (streams, options) { /***/ }), -/* 594 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(595); +const pathType = __webpack_require__(597); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -62653,13 +62715,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 595 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(596); +const pify = __webpack_require__(598); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -62702,7 +62764,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 596 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62793,7 +62855,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62801,9 +62863,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(409); -const gitIgnore = __webpack_require__(598); -const pify = __webpack_require__(599); -const slash = __webpack_require__(600); +const gitIgnore = __webpack_require__(600); +const pify = __webpack_require__(601); +const slash = __webpack_require__(602); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -62901,7 +62963,7 @@ module.exports.sync = options => { /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -63370,7 +63432,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63445,7 +63507,7 @@ module.exports = (input, options) => { /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63463,17 +63525,17 @@ module.exports = input => { /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(602); -const CpFileError = __webpack_require__(604); -const fs = __webpack_require__(606); -const ProgressEmitter = __webpack_require__(608); +const {Buffer} = __webpack_require__(604); +const CpFileError = __webpack_require__(606); +const fs = __webpack_require__(608); +const ProgressEmitter = __webpack_require__(610); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -63627,11 +63689,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(603) +var buffer = __webpack_require__(605) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -63695,18 +63757,18 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(605); +const NestedError = __webpack_require__(607); class CpFileError extends NestedError { constructor(message, nested) { @@ -63720,7 +63782,7 @@ module.exports = CpFileError; /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(44); @@ -63774,15 +63836,15 @@ module.exports = NestedError; /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(115); -const pify = __webpack_require__(607); -const CpFileError = __webpack_require__(604); +const pify = __webpack_require__(609); +const CpFileError = __webpack_require__(606); const fsP = pify(fs); @@ -63927,7 +63989,7 @@ if (fs.copyFileSync) { /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64002,7 +64064,7 @@ module.exports = (input, options) => { /***/ }), -/* 608 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64043,12 +64105,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(610); +const NestedError = __webpack_require__(612); class CpyError extends NestedError { constructor(message, nested) { @@ -64062,7 +64124,7 @@ module.exports = CpyError; /***/ }), -/* 610 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -64118,7 +64180,7 @@ module.exports = NestedError; /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index d02b2cf41d3f8..86a81207a9fa9 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -12,7 +12,10 @@ "devDependencies": { "@babel/cli": "^7.5.5", "@kbn/babel-preset": "1.0.0", - "@kbn/dev-utils": "1.0.0" + "@kbn/dev-utils": "1.0.0", + "@types/parse-link-header": "^1.0.0", + "@types/strip-ansi": "^5.2.1", + "@types/xml2js": "^0.4.5" }, "dependencies": { "chalk": "^2.4.2", @@ -20,9 +23,12 @@ "del": "^4.1.1", "getopts": "^2.2.4", "glob": "^7.1.2", + "parse-link-header": "^1.0.1", + "strip-ansi": "^5.2.0", "rxjs": "^6.2.1", "tar-fs": "^1.16.3", "tmp": "^0.1.0", + "xml2js": "^0.4.22", "zlib": "^1.0.5" } } diff --git a/packages/kbn-test/src/failed_tests_reporter/README.md b/packages/kbn-test/src/failed_tests_reporter/README.md new file mode 100644 index 0000000000000..42533a45c714a --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/README.md @@ -0,0 +1,21 @@ +# failed tests reporter + +A little CLI that runs in CI to find the failed tests in the JUnit reports, then create/update github issues for each failure. + +## Test this script locally + +To fetch some JUnit reports from a recent build on CI, visit its `Google Cloud Storage Upload Report` and execute the following in the JS Console: + +```js +copy(`wget "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Download').map(a => a.href.replace('https://storage.cloud.google.com/', 'https://storage.googleapis.com/')).join('" "')}"`) +``` + +This copies a script to download the reporets, which can be executed in the `test/junit` directory. + +Next, run the CLI in `--dry-run` mode so that it doesn't actually communicate with Github. + +```sh +node scripts/report_failed_tests.js --verbose --dry-run --build-url foo +``` + +If you specify the `GITHUB_TOKEN` environment variable then `--dry-run` will execute read operations but still won't execute write operations. \ No newline at end of file diff --git a/src/dev/failed_tests/__fixtures__/ftr_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml similarity index 100% rename from src/dev/failed_tests/__fixtures__/ftr_report.xml rename to packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml diff --git a/src/dev/failed_tests/__fixtures__/jest_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/jest_report.xml similarity index 100% rename from src/dev/failed_tests/__fixtures__/jest_report.xml rename to packages/kbn-test/src/failed_tests_reporter/__fixtures__/jest_report.xml diff --git a/src/dev/failed_tests/__fixtures__/karma_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml similarity index 100% rename from src/dev/failed_tests/__fixtures__/karma_report.xml rename to packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml diff --git a/src/dev/failed_tests/__fixtures__/mocha_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/mocha_report.xml similarity index 100% rename from src/dev/failed_tests/__fixtures__/mocha_report.xml rename to packages/kbn-test/src/failed_tests_reporter/__fixtures__/mocha_report.xml diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts new file mode 100644 index 0000000000000..1e0514a9b1cb0 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; + +import { getFailures } from './get_failures'; + +const log = new ToolingLog(); + +it('discovers failures in ftr report', async () => { + const failures = await getFailures(log, require.resolve('./__fixtures__/ftr_report.xml')); + expect(failures).toMatchInlineSnapshot(` + Array [ + Object { + "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js", + "failure": " + Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~=\\"layerTocActionsPanelToggleButtonRoad_Map_-_Bright\\"]) + Wait timed out after 10055ms + at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17 + at process._tickCallback (internal/process/next_tick.js:68:7) + at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9) + at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13) + ", + "name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook", + "time": "154.378", + }, + ] + `); +}); + +it('discovers failures in jest report', async () => { + const failures = await getFailures(log, require.resolve('./__fixtures__/jest_report.xml')); + expect(failures).toMatchInlineSnapshot(` + Array [ + Object { + "classname": "X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp", + "failure": " + TypeError: Cannot read property '0' of undefined + at Object..test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10) + ", + "name": "launcher can reconnect if process died", + "time": "7.060", + }, + ] + `); +}); + +it('discovers failures in karma report', async () => { + const failures = await getFailures(log, require.resolve('./__fixtures__/karma_report.xml')); + expect(failures).toMatchInlineSnapshot(` + Array [ + Object { + "classname": "Browser Unit Tests.CoordinateMapsVisualizationTest", + "failure": "Error: expected 7069 to be below 64 + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) + at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) + at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) + at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) + at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) + at Generator.prototype. [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) + at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) + at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) + ", + "name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK", + "time": "0.265", + }, + ] + `); +}); + +it('discovers failures in mocha report', async () => { + const failures = await getFailures(log, require.resolve('./__fixtures__/mocha_report.xml')); + expect(failures).toMatchInlineSnapshot(`Array []`); +}); diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts new file mode 100644 index 0000000000000..d38d118a255e7 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts @@ -0,0 +1,163 @@ +/* + * 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 { promisify } from 'util'; +import Fs from 'fs'; + +import xml2js from 'xml2js'; +import stripAnsi from 'strip-ansi'; +import { ToolingLog } from '@kbn/dev-utils'; + +type TestReport = + | { + testsuites: { + testsuite: TestSuite[]; + }; + } + | { + testsuite: TestSuite; + }; + +interface TestSuite { + $: { + /* ISO8601 timetamp when test suite ran */ + timestamp: string; + /* number of second this tests suite took */ + time: string; + /* number of tests as a string */ + tests: string; + /* number of failed tests as a string */ + failures: string; + /* number of skipped tests as a string */ + skipped: string; + }; + testcase: TestCase[]; +} + +interface TestCase { + $: { + /* unique test name */ + name: string; + /* somewhat human readable combination of test name and file */ + classname: string; + /* number of seconds this test took */ + time: string; + }; + /* contents of system-out elements */ + 'system-out'?: string[]; + /* contents of failure elements */ + failure?: Array; + /* contents of skipped elements */ + skipped?: string[]; +} + +export type TestFailure = TestCase['$'] & { + failure: string; +}; + +const readAsync = promisify(Fs.readFile); + +const indent = (text: string) => + ` ${text + .split('\n') + .map(l => ` ${l}`) + .join('\n')}`; + +const getFailureText = (failure: NonNullable) => { + const [failureNode] = failure; + + if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') { + return stripAnsi(failureNode._); + } + + return stripAnsi(String(failureNode)); +}; + +const isLikelyIrrelevant = ({ name, failure }: TestFailure) => { + if ( + failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID') + ) { + return true; + } + + if (failure.includes('Error: No Living connections')) { + return true; + } + + if ( + name.includes('"after all" hook') && + failure.includes(`Cannot read property 'shutdown' of undefined`) + ) { + return true; + } + + if ( + failure.includes('Unable to read artifact info') && + failure.includes('Service Temporarily Unavailable') + ) { + return true; + } + + if (failure.includes('Unable to fetch Kibana status API response from Kibana')) { + return true; + } +}; + +export async function getFailures(log: ToolingLog, testReportPath: string) { + const xml = await readAsync(testReportPath, 'utf8'); + + // Parses junit XML files + const report: TestReport = await xml2js.parseStringPromise(xml); + + // Grab the failures. Reporters may report multiple testsuites in a single file. + const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite]; + + const failures: TestFailure[] = []; + for (const testSuite of testSuites) { + for (const testCase of testSuite.testcase) { + const { failure } = testCase; + + if (!failure) { + continue; + } + + // unwrap xml weirdness + const failureCase: TestFailure = { + ...testCase.$, + // Strip ANSI color characters + failure: getFailureText(failure), + }; + + if (isLikelyIrrelevant(failureCase)) { + log.warning( + `Ignoring likely irrelevant failure: ${failureCase.classname} - ${ + failureCase.name + }\n${indent(failureCase.failure)}` + ); + continue; + } + + failures.push(failureCase); + } + } + + log.info(`Found ${failures.length} test failures`); + + return failures; +} diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts new file mode 100644 index 0000000000000..4f8b339072ef4 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts @@ -0,0 +1,169 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Url from 'url'; + +import Axios, { AxiosRequestConfig } from 'axios'; +import parseLinkHeader from 'parse-link-header'; +import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils'; + +const BASE_URL = 'https://api.github.com/repos/elastic/kibana/'; + +export interface GithubIssue { + html_url: string; + number: number; + title: string; + labels: unknown[]; + body: string; +} + +type RequestOptions = AxiosRequestConfig & { safeForDryRun?: boolean }; + +export class GithubApi { + private readonly x = Axios.create({ + headers: { + Authorization: `token ${this.token}`, + 'User-Agent': 'elastic/kibana#failed_test_reporter', + }, + }); + + /** + * Create a GithubApi helper object, if token is undefined requests won't be + * sent, but will instead be logged. + */ + constructor( + private readonly log: ToolingLog, + private readonly token: string | undefined, + private readonly dryRun: boolean + ) { + if (!token && !dryRun) { + throw new TypeError('token parameter is required'); + } + } + + async getAllFailedTestIssues() { + this.log.info('Fetching failed-test issues'); + const issues: GithubIssue[] = []; + let nextRequest: RequestOptions = { + safeForDryRun: true, + method: 'GET', + url: Url.resolve(BASE_URL, 'issues'), + params: { + state: 'all', + per_page: '100', + labels: 'failed-test', + }, + }; + + while (true) { + const resp = await this.request(nextRequest, []); + + for (const issue of resp.data) { + issues.push(issue); + } + + const parsed = parseLinkHeader(resp.headers.link); + if (parsed && parsed.next && parsed.next.url) { + nextRequest = { + safeForDryRun: true, + method: 'GET', + url: parsed.next.url, + }; + } else { + break; + } + } + + return issues; + } + + async editIssueBodyAndEnsureOpen(issueNumber: number, newBody: string) { + await this.request( + { + method: 'PATCH', + url: Url.resolve(BASE_URL, `issues/${encodeURIComponent(issueNumber)}`), + data: { + state: 'open', // Reopen issue if it was closed. + body: newBody, + }, + }, + undefined + ); + } + + async addIssueComment(issueNumber: number, commentBody: string) { + await this.request( + { + method: 'POST', + url: Url.resolve(BASE_URL, `issues/${encodeURIComponent(issueNumber)}/comments`), + data: { + body: commentBody, + }, + }, + undefined + ); + } + + async createIssue(title: string, body: string, labels?: string[]) { + const resp = await this.request( + { + method: 'POST', + url: Url.resolve(BASE_URL, 'issues'), + data: { + title, + body, + labels, + }, + }, + { + html_url: 'https://dryrun', + } + ); + + return resp.data.html_url; + } + + private async request(options: RequestOptions, dryRunResponse: T) { + const executeRequest = !this.dryRun || options.safeForDryRun; + this.log.verbose('Github API', executeRequest ? 'Request' : 'Dry Run', options); + + if (executeRequest) { + try { + return await this.x.request(options); + } catch (error) { + if (isAxiosResponseError(error)) { + throw new Error( + `[${error.config.method} ${error.config.url}] ${error.response.status} ${ + error.response.statusText + } Error: ${JSON.stringify(error.response.data)}` + ); + } + + throw error; + } + } + + return { + status: 200, + statusText: 'OK', + headers: {}, + data: dryRunResponse, + }; + } +} diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/index.js b/packages/kbn-test/src/failed_tests_reporter/index.ts similarity index 91% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/index.js rename to packages/kbn-test/src/failed_tests_reporter/index.ts index 3db93663b625d..1332f08eb4e16 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/index.js +++ b/packages/kbn-test/src/failed_tests_reporter/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { MetricVisParams } from './metric_vis_params'; +export { runFailedTestsReporterCli } from './run_failed_tests_reporter_cli'; diff --git a/packages/kbn-test/src/failed_tests_reporter/issue_metadata.test.ts b/packages/kbn-test/src/failed_tests_reporter/issue_metadata.test.ts new file mode 100644 index 0000000000000..f7585cb941a23 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/issue_metadata.test.ts @@ -0,0 +1,134 @@ +/* + * 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 dedent from 'dedent'; + +import { getIssueMetadata, updateIssueMetadata } from './issue_metadata'; + +const HAS_METADATA = dedent` + # my issue + + some text + + +`; + +const HAS_SOME_OTHER_METADATA = dedent` + # my issue + + some text + + +`; + +const INVALID_METADATA = dedent` + # my issue + + some text + + +`; + +const MISSING_METADATA = dedent` + # my issue + + some text +`; + +describe('getIssueMetadata', () => { + it('reads properly formatted metadata', () => { + expect(getIssueMetadata(HAS_METADATA, 'foo')).toBe('bar'); + }); + + it('returns undefined if JSON is malformed', () => { + expect(getIssueMetadata(INVALID_METADATA, 'foo')).toBe(undefined); + }); + + it('returns undefined if metadata is missing', () => { + expect(getIssueMetadata(MISSING_METADATA, 'foo')).toBe(undefined); + }); + + it('returns undefined if JSON is missing `failed-test` property', () => { + expect(getIssueMetadata(HAS_SOME_OTHER_METADATA, 'foo')).toBe(undefined); + }); + + it('returns defaultValue if specified', () => { + expect(getIssueMetadata(HAS_METADATA, 'foo2', 'bar2')).toBe('bar2'); + }); + + it('returns undefined if defaultValue is not specified', () => { + expect(getIssueMetadata(HAS_METADATA, 'foo2')).toBe(undefined); + }); +}); + +describe('updateIssueMetadata', () => { + it('merges new values with previous values', () => { + expect( + updateIssueMetadata(HAS_METADATA, { + box: 'baz', + }) + ).toMatchInlineSnapshot(` + "# my issue + + some text + + " + `); + }); + + it('adds metadata if not found', () => { + expect( + updateIssueMetadata(MISSING_METADATA, { + box: 'baz', + }) + ).toMatchInlineSnapshot(` + "# my issue + + some text + + " + `); + + expect( + updateIssueMetadata(HAS_SOME_OTHER_METADATA, { + box: 'baz', + }) + ).toMatchInlineSnapshot(` + "# my issue + + some text + + " + `); + }); + + it('overwrites metdata if JSON is malformed', () => { + expect( + updateIssueMetadata(INVALID_METADATA, { + box: 'baz', + }) + ).toMatchInlineSnapshot(` + "# my issue + + some text + + " + `); + }); +}); diff --git a/src/dev/github_utils/metadata.js b/packages/kbn-test/src/failed_tests_reporter/issue_metadata.ts similarity index 69% rename from src/dev/github_utils/metadata.js rename to packages/kbn-test/src/failed_tests_reporter/issue_metadata.ts index ab50ee3cf959e..5c87d7c322990 100644 --- a/src/dev/github_utils/metadata.js +++ b/packages/kbn-test/src/failed_tests_reporter/issue_metadata.ts @@ -17,8 +17,6 @@ * under the License. */ -const REGEX = /\n\n/; - /** * Allows retrieving and setting key/value pairs on a Github Issue. Keys and values must be JSON-serializable. * Derived from https://github.com/probot/metadata/blob/6ae1523d5035ba727d09c0e7f77a6a154d9a4777/index.js @@ -47,42 +45,43 @@ const REGEX = /\n\n/; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -export const markdownMetadata = { - get(body, key = null, prefix = 'failed-test') { - const match = body.match(REGEX); - - if (match) { - const data = JSON.parse(match[1])[prefix]; - return key ? data && data[key] : data; - } else { - return null; - } - }, - - /** - * Set data on the body. Can either be set individually with `key` and `value` OR - */ - set(body, key, value, prefix = 'failed-test') { - let newData = {}; - // If second arg is an object, use all supplied values. - if (typeof key === 'object') { - newData = key; - prefix = value || prefix; // need to move third arg to prefix. - } else { - newData[key] = value; - } - let data = {}; +const PREFIX = 'failed-test'; +const REGEX = /\n\n/; - body = body.replace(REGEX, (_, json) => { - data = JSON.parse(json); - return ''; - }); +function safeJsonParse(json: string, onError: any) { + try { + return JSON.parse(json); + } catch { + return onError; + } +} - if (!data[prefix]) data[prefix] = {}; +/** + * Parse metadata from issue body + */ +export function getIssueMetadata(body: string, key: string, defaultValue: any = undefined) { + const match = body.match(REGEX); - Object.assign(data[prefix], newData); + if (match) { + const data = safeJsonParse(match[1], {})[PREFIX]; + return data && data[key] !== undefined ? data[key] : defaultValue; + } else { + return defaultValue; + } +} - return `${body}\n\n`; +/** + * Set data on the body. + */ +export function updateIssueMetadata(body: string, values: Record) { + if (REGEX.test(body)) { + return body.replace(REGEX, (match, json) => { + const data = safeJsonParse(json, {}); + data[PREFIX] = Object.assign(data[PREFIX] || {}, values); + return match.replace(json, JSON.stringify(data)); + }); } -}; + + return `${body}\n\n`; +} 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 new file mode 100644 index 0000000000000..7a127b07b16b0 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts @@ -0,0 +1,146 @@ +/* + * 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 dedent from 'dedent'; +import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/dev-utils'; + +import { createFailureIssue, updatedFailureIssue } from './report_failure'; + +jest.mock('./github_api'); +const { GithubApi } = jest.requireMock('./github_api'); + +describe('createFailureIssue()', () => { + it('creates new github issue with failure text, link to issue, and valid metadata', async () => { + const log = new ToolingLog(); + const writer = new ToolingLogCollectingWriter(); + log.setWriters([writer]); + + const api = new GithubApi(); + + await createFailureIssue( + 'https://build-url', + { + classname: 'some.classname', + failure: 'this is the failure text', + name: 'test name', + time: '2018-01-01T01:00:00Z', + }, + log, + api + ); + + expect(api.createIssue).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + "Failing test: some.classname - test name", + "A test failed on a tracked branch + \`\`\` + this is the failure text + \`\`\` + First failure: [Jenkins Build](https://build-url) + + ", + Array [ + "failed-test", + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + } + `); + expect(writer.messages).toMatchInlineSnapshot(` + Array [ + " info Created issue undefined", + ] + `); + }); +}); + +describe('updatedFailureIssue()', () => { + it('increments failure count and adds new comment to issue', async () => { + const log = new ToolingLog(); + const writer = new ToolingLogCollectingWriter(); + log.setWriters([writer]); + + const api = new GithubApi(); + + await updatedFailureIssue( + 'https://build-url', + { + html_url: 'https://github.com/issues/1234', + labels: ['some-label'], + number: 1234, + title: 'issue title', + body: dedent` + # existing issue body + + " + `, + }, + log, + api + ); + + expect(api.editIssueBodyAndEnsureOpen).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + 1234, + "# existing issue body + + \\"", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + } + `); + expect(api.addIssueComment).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + 1234, + "New failure: [Jenkins Build](https://build-url)", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + } + `); + expect(writer.messages).toMatchInlineSnapshot(` + Array [ + " info Updated issue https://github.com/issues/1234, failCount: 11", + ] + `); + }); +}); diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts new file mode 100644 index 0000000000000..55b4dc6e12036 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts @@ -0,0 +1,70 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; +import dedent from 'dedent'; + +import { TestFailure } from './get_failures'; +import { GithubIssue, GithubApi } from './github_api'; +import { getIssueMetadata, updateIssueMetadata } from './issue_metadata'; + +export async function createFailureIssue( + buildUrl: string, + failure: TestFailure, + log: ToolingLog, + api: GithubApi +) { + const title = `Failing test: ${failure.classname} - ${failure.name}`; + + const body = updateIssueMetadata( + dedent` + A test failed on a tracked branch + \`\`\` + ${failure.failure} + \`\`\` + First failure: [Jenkins Build](${buildUrl}) + `, + { + 'test.class': failure.classname, + 'test.name': failure.name, + 'test.failCount': 1, + } + ); + + const newIssueUrl = await api.createIssue(title, body, ['failed-test']); + log.info(`Created issue ${newIssueUrl}`); +} + +export async function updatedFailureIssue( + buildUrl: string, + issue: GithubIssue, + log: ToolingLog, + api: GithubApi +) { + // Increment failCount + const newCount = getIssueMetadata(issue.body, 'test.failCount', 0) + 1; + const newBody = updateIssueMetadata(issue.body, { + 'test.failCount': newCount, + }); + + await api.editIssueBodyAndEnsureOpen(issue.number, newBody); + await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`); + + log.info(`Updated issue ${issue.html_url}, failCount: ${newCount}`); +} 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 new file mode 100644 index 0000000000000..40f2cec149ada --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -0,0 +1,101 @@ +/* + * 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 { REPO_ROOT, run, createFailError, createFlagError } from '@kbn/dev-utils'; +import globby from 'globby'; + +import { getFailures } from './get_failures'; +import { GithubApi } from './github_api'; +import { updatedFailureIssue, createFailureIssue } from './report_failure'; +import { getIssueMetadata } from './issue_metadata'; + +export function runFailedTestsReporterCli() { + run( + async ({ log, flags }) => { + const buildUrl = flags['build-url']; + if (typeof buildUrl !== 'string' || !buildUrl) { + throw createFlagError('Missing --build-url or process.env.BUILD_URL'); + } + + const dryRun = !!flags['dry-run']; + if (!dryRun) { + // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others + const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); + const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; + if (!branch) { + throw createFailError( + 'Unable to determine originating branch from job name or other environment variables' + ); + } + + const isPr = !!process.env.ghprbPullId; + const isMasterOrVersion = + branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/); + if (!isMasterOrVersion || isPr) { + throw createFailError('Failure issues only created on master/version branch jobs', { + exitCode: 0, + }); + } + + if (!process.env.GITHUB_TOKEN) { + throw createFailError( + 'GITHUB_TOKEN environment variable must be set, otherwise use --dry-run flag' + ); + } + } + + const githubApi = new GithubApi(log, process.env.GITHUB_TOKEN, dryRun); + const issues = await githubApi.getAllFailedTestIssues(); + const reportPaths = await globby(['target/junit/**/*.xml'], { + cwd: REPO_ROOT, + absolute: true, + }); + + for (const reportPath of reportPaths) { + for (const failure of await getFailures(log, reportPath)) { + const existingIssue = issues.find( + i => + getIssueMetadata(i.body, 'test.class') === failure.classname && + getIssueMetadata(i.body, 'test.name') === failure.name + ); + + if (existingIssue) { + await updatedFailureIssue(buildUrl, existingIssue, log, githubApi); + } else { + await createFailureIssue(buildUrl, failure, log, githubApi); + } + } + } + }, + { + description: `a cli that opens issues or updates existing issues based on junit reports`, + flags: { + boolean: ['dry-run'], + string: ['build-url'], + default: { + 'build-url': process.env.BUILD_URL, + }, + help: ` + --dry-run Execute the CLI without contacting Github + --build-url URL of the failed build, defaults to process.env.BUILD_URL + `, + }, + } + ); +} 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 2423665bacb84..136704a639bff 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js +++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js @@ -19,7 +19,7 @@ import { resolve } from 'path'; import { KIBANA_ROOT } from './paths'; -import { createEsTestCluster } from '../../es'; +import { createLegacyEsTestCluster } from '../../legacy_es'; import { setupUsers, DEFAULT_SUPERUSER_PASS } from './auth'; @@ -31,7 +31,7 @@ export async function runElasticsearch({ config, options }) { const esEnvVars = config.get('esTestCluster.serverEnvVars'); const isSecurityEnabled = esArgs.includes('xpack.security.enabled=true'); - const cluster = createEsTestCluster({ + const cluster = createLegacyEsTestCluster({ port: config.get('servers.elasticsearch.port'), password: isSecurityEnabled ? DEFAULT_SUPERUSER_PASS diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 9167fd190dc86..62739fd37030f 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -27,7 +27,7 @@ export { runTests, startServers } from './functional_tests/tasks'; export { OPTIMIZE_BUNDLE_DIR, KIBANA_ROOT } from './functional_tests/lib/paths'; // @ts-ignore not typed yet -export { esTestConfig, createEsTestCluster } from './es'; +export { esTestConfig, createLegacyEsTestCluster } from './legacy_es'; // @ts-ignore not typed yet export { kbnTestConfig, kibanaServerTestUser, kibanaTestUser, adminTestUser } from './kbn'; @@ -47,3 +47,5 @@ export { setupJUnitReportGeneration, escapeCdata, } from './mocha'; + +export { runFailedTestsReporterCli } from './failed_tests_reporter'; diff --git a/packages/kbn-test/src/es/es_test_config.js b/packages/kbn-test/src/legacy_es/es_test_config.js similarity index 100% rename from packages/kbn-test/src/es/es_test_config.js rename to packages/kbn-test/src/legacy_es/es_test_config.js diff --git a/packages/kbn-test/src/es/index.js b/packages/kbn-test/src/legacy_es/index.js similarity index 92% rename from packages/kbn-test/src/es/index.js rename to packages/kbn-test/src/legacy_es/index.js index 029f71e5cc283..8691b7b81152d 100644 --- a/packages/kbn-test/src/es/index.js +++ b/packages/kbn-test/src/legacy_es/index.js @@ -17,5 +17,5 @@ * under the License. */ -export { createEsTestCluster } from './es_test_cluster.js'; +export { createLegacyEsTestCluster } from './legacy_es_test_cluster.js'; export { esTestConfig } from './es_test_config'; diff --git a/packages/kbn-test/src/es/es_test_cluster.js b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js similarity index 96% rename from packages/kbn-test/src/es/es_test_cluster.js rename to packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js index eb8373eb2206a..355304c86a3c3 100644 --- a/packages/kbn-test/src/es/es_test_cluster.js +++ b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js @@ -23,12 +23,13 @@ import { get } from 'lodash'; import toPath from 'lodash/internal/toPath'; import { Cluster } from '@kbn/es'; import { esTestConfig } from './es_test_config'; + import { KIBANA_ROOT } from '../'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; const path = require('path'); const del = require('del'); -export function createEsTestCluster(options = {}) { +export function createLegacyEsTestCluster(options = {}) { const { port = esTestConfig.getPort(), password = 'changeme', @@ -111,7 +112,7 @@ export function createEsTestCluster(options = {}) { * Returns an ES Client to the configured cluster */ getClient() { - return new elasticsearch.Client({ + return new legacyElasticsearch.Client({ host: this.getUrl(), }); } diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 54df5c5f33494..51601fab12e53 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -142,13 +142,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { `TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml` ); - const reportXML = builder.end({ - pretty: true, - indent: ' ', - newline: '\n', - spacebeforeslash: '', - }); - + const reportXML = builder.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); }); diff --git a/renovate.json5 b/renovate.json5 index e8fd7b6ceda50..221d5b232fd21 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -647,6 +647,14 @@ '@types/color', ], }, + { + groupSlug: 'cytoscape', + groupName: 'cytoscape related packages', + packageNames: [ + 'cytoscape', + '@types/cytoscape', + ], + }, { groupSlug: 'fancy-log', groupName: 'fancy-log related packages', @@ -903,6 +911,22 @@ '@types/write-pkg', ], }, + { + groupSlug: 'parse-link-header', + groupName: 'parse-link-header related packages', + packageNames: [ + 'parse-link-header', + '@types/parse-link-header', + ], + }, + { + groupSlug: 'xml2js', + groupName: 'xml2js related packages', + packageNames: [ + 'xml2js', + '@types/xml2js', + ], + }, { packagePatterns: [ '^@kbn/.*', diff --git a/src/legacy/core_plugins/vis_type_metric/public/shim/legacy_dependencies_plugin.ts b/scripts/report_failed_tests.js similarity index 68% rename from src/legacy/core_plugins/vis_type_metric/public/shim/legacy_dependencies_plugin.ts rename to scripts/report_failed_tests.js index 9d5f49882ab8c..6d23f0695b01d 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/shim/legacy_dependencies_plugin.ts +++ b/scripts/report_failed_tests.js @@ -16,16 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreStart, Plugin } from 'kibana/public'; -import { initMetricVisLegacyModule } from './metric_vis_legacy_module'; -export class LegacyDependenciesPlugin implements Plugin { - public setup() { - // Init kibana/metric_vis AngularJS module. - initMetricVisLegacyModule(); - } - - public start(core: CoreStart) { - // nothing to do here yet - } -} +require('../src/setup_node_env'); +require('@kbn/test').runFailedTestsReporterCli(); diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 9640f6f510c45..91f65a14986e9 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -91,6 +91,7 @@ export { SavedObject, SavedObjectAttribute, SavedObjectAttributes, + SavedObjectAttributeSingle, SavedObjectReference, SavedObjectsBaseOptions, SavedObjectsFindOptions, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 0156cbaebb949..51a993ee5a5bb 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -716,15 +716,18 @@ export interface SavedObject { version?: string; } -// @public (undocumented) -export type SavedObjectAttribute = string | number | boolean | null | undefined | SavedObjectAttributes | SavedObjectAttributes[]; +// @public +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; // @public export interface SavedObjectAttributes { // (undocumented) - [key: string]: SavedObjectAttribute | SavedObjectAttribute[]; + [key: string]: SavedObjectAttribute; } +// @public +export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; + // @public export interface SavedObjectReference { // (undocumented) diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index f82112c7a65bd..9452ece0ce823 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -33,6 +33,7 @@ export { SavedObject, SavedObjectAttribute, SavedObjectAttributes, + SavedObjectAttributeSingle, SavedObjectReference, SavedObjectsBaseOptions, SavedObjectsFindOptions, diff --git a/src/core/server/elasticsearch/retry_call_cluster.test.ts b/src/core/server/elasticsearch/retry_call_cluster.test.ts index e2c6415a08c56..0de10e8fb4f77 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.test.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.test.ts @@ -16,7 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; + import { retryCallCluster } from './retry_call_cluster'; describe('retryCallCluster', () => { @@ -26,7 +27,7 @@ describe('retryCallCluster', () => { let i = 0; callEsApi.mockImplementation(() => { return i++ <= 2 - ? Promise.reject(new elasticsearch.errors.NoConnections()) + ? Promise.reject(new legacyElasticsearch.errors.NoConnections()) : Promise.resolve('success'); }); const retried = retryCallCluster(callEsApi); @@ -45,7 +46,7 @@ describe('retryCallCluster', () => { : i === 2 ? Promise.resolve('success') : i === 3 || i === 4 - ? Promise.reject(new elasticsearch.errors.NoConnections()) + ? Promise.reject(new legacyElasticsearch.errors.NoConnections()) : i === 5 ? Promise.reject(new Error('unknown error')) : null; diff --git a/src/core/server/elasticsearch/retry_call_cluster.ts b/src/core/server/elasticsearch/retry_call_cluster.ts index 4b74dffebbef9..2e4afa682ea75 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.ts @@ -19,7 +19,8 @@ import { retryWhen, concatMap } from 'rxjs/operators'; import { defer, throwError, iif, timer } from 'rxjs'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; + import { CallAPIOptions } from '.'; /** @@ -45,7 +46,7 @@ export function retryCallCluster( errors.pipe( concatMap((error, i) => iif( - () => error instanceof elasticsearch.errors.NoConnections, + () => error instanceof legacyElasticsearch.errors.NoConnections, timer(1000), throwError(error) ) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 3cc420b766439..027c82e419b5c 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -169,6 +169,7 @@ export { SavedObject, SavedObjectAttribute, SavedObjectAttributes, + SavedObjectAttributeSingle, SavedObjectReference, SavedObjectsBaseOptions, SavedObjectsClientContract, diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index c13be579c04bb..ffcfa247f756f 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -23,7 +23,7 @@ import { SavedObjectsService, SavedObjectsSetupDeps } from './saved_objects_serv import { mockCoreContext } from '../core_context.mock'; import { KibanaMigrator } from './migrations/kibana/kibana_migrator'; import { of } from 'rxjs'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; @@ -41,7 +41,7 @@ describe('SavedObjectsService', () => { .fn() .mockImplementation(() => i++ <= 2 - ? Promise.reject(new elasticsearch.errors.NoConnections()) + ? Promise.reject(new legacyElasticsearch.errors.NoConnections()) : Promise.resolve('success') ), }; diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.ts index af66348a98eb3..eb9bc89636435 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.ts @@ -17,7 +17,7 @@ * under the License. */ -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; import { get } from 'lodash'; const { @@ -34,7 +34,7 @@ const { 413: RequestEntityTooLarge, NotFound, BadRequest, -} = elasticsearch.errors; +} = legacyElasticsearch.errors; import { SavedObjectsErrorHelpers } from './errors'; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index bbca181e05488..dcabb90c0a13d 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -22,7 +22,7 @@ import { delay } from 'bluebird'; import { SavedObjectsRepository } from './repository'; import * as getSearchDslNS from './search_dsl/search_dsl'; import { SavedObjectsErrorHelpers } from './errors'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; import { SavedObjectsSchema } from '../../schema'; import { SavedObjectsSerializer } from '../../serialization'; import { getRootPropertiesObjects } from '../../mappings/lib/get_root_properties_objects'; @@ -2148,7 +2148,7 @@ describe('SavedObjectsRepository', () => { it('can throw es errors and have them decorated as SavedObjectsClient errors', async () => { expect.assertions(4); - const es401 = new elasticsearch.errors[401](); + const es401 = new legacyElasticsearch.errors[401](); expect(SavedObjectsErrorHelpers.isNotAuthorizedError(es401)).toBe(false); onBeforeWrite.mockImplementation(() => { throw es401; diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index e7e7a4c64392a..281ffe1ecc875 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -43,26 +43,33 @@ export interface SavedObjectsMigrationVersion { } /** + * Don't use this type, it's simply a helper type for {@link SavedObjectAttribute} * * @public */ -export type SavedObjectAttribute = +export type SavedObjectAttributeSingle = | string | number | boolean | null | undefined - | SavedObjectAttributes - | SavedObjectAttributes[]; + | SavedObjectAttributes; /** - * The data for a Saved Object is stored in the `attributes` key as either an - * object or an array of objects. + * Type definition for a Saved Object attribute value + * + * @public + */ +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; + +/** + * The data for a Saved Object is stored as an object in the `attributes` + * property. * * @public */ export interface SavedObjectAttributes { - [key: string]: SavedObjectAttribute | SavedObjectAttribute[]; + [key: string]: SavedObjectAttribute; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 2de72cf1a35f6..01e6b63ee433a 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1104,15 +1104,18 @@ export interface SavedObject { version?: string; } -// @public (undocumented) -export type SavedObjectAttribute = string | number | boolean | null | undefined | SavedObjectAttributes | SavedObjectAttributes[]; +// @public +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; // @public export interface SavedObjectAttributes { // (undocumented) - [key: string]: SavedObjectAttribute | SavedObjectAttribute[]; + [key: string]: SavedObjectAttribute; } +// @public +export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; + // @public export interface SavedObjectReference { // (undocumented) diff --git a/src/dev/failed_tests/cli.js b/src/dev/failed_tests/cli.js deleted file mode 100644 index a046f70080c28..0000000000000 --- a/src/dev/failed_tests/cli.js +++ /dev/null @@ -1,41 +0,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 { resolve } = require('path'); - -// force cwd -process.chdir(resolve(__dirname, '../../..')); - -// JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others -const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); -const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; -if (!branch) { - console.log('Unable to determine originating branch from job name or other environment variables'); - process.exit(1); -} - -const isPr = !!process.env.ghprbPullId; -const isMasterOrVersion = branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/); -if (!isMasterOrVersion || isPr) { - console.log('Failure issues only created on master/version branch jobs'); - process.exit(0); -} - -require('../../setup_node_env'); -require('./report').reportFailedTests(); diff --git a/src/dev/failed_tests/report.js b/src/dev/failed_tests/report.js deleted file mode 100644 index 68ded3ad26bcc..0000000000000 --- a/src/dev/failed_tests/report.js +++ /dev/null @@ -1,196 +0,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. - */ - -import xml2js from 'xml2js'; -import vfs from 'vinyl-fs'; -import { createMapStream } from '../../legacy/utils/streams'; -import { getGithubClient, markdownMetadata, paginate } from '../github_utils'; -import { find } from 'lodash'; -import stripAnsi from 'strip-ansi'; - -const GITHUB_FLAKY_TEST_LABEL = 'failed-test'; -const GITHUB_OWNER = 'elastic'; -const GITHUB_REPO = 'kibana'; -const BUILD_URL = process.env.BUILD_URL; - -const indent = text => ( - ` ${text.split('\n').map(l => ` ${l}`).join('\n')}` -); - -const getFailureText = (testCase) => { - const [failureNode] = testCase.failure; - - if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') { - return stripAnsi(failureNode._); - } - - return stripAnsi(String(failureNode)); -}; - -const isLikelyIrrelevant = ({ name, failure }) => { - if (failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID')) { - return true; - } - - if (failure.includes('Error: No Living connections')) { - return true; - } - - if (name.includes('"after all" hook') && failure.includes(`Cannot read property 'shutdown' of undefined`)) { - return true; - } - - if (failure.includes('Unable to read artifact info') && failure.includes('Service Temporarily Unavailable')) { - return true; - } - - if (failure.includes('Unable to fetch Kibana status API response from Kibana')) { - return true; - } -}; - -/** - * Parses junit XML files into JSON - */ -export const mapXml = () => createMapStream((file) => new Promise((resolve, reject) => { - xml2js.parseString(file.contents.toString(), (err, result) => { - if (err) { - return reject(err); - } - resolve(result); - }); -})); - -/** - * Filters all testsuites to find failed testcases - */ -export const filterFailures = () => createMapStream((testSuite) => { - // Grab the failures. Reporters may report multiple testsuites in a single file. - const testFiles = testSuite.testsuites - ? testSuite.testsuites.testsuite - : [testSuite.testsuite]; - - const failures = []; - for (const testFile of testFiles) { - for (const testCase of testFile.testcase) { - if (!testCase.failure) { - continue; - } - - // unwrap xml weirdness - const failureCase = { - ...testCase.$, - // Strip ANSI color characters - failure: getFailureText(testCase) - }; - - if (isLikelyIrrelevant(failureCase)) { - console.log(`Ignoring likely irrelevant failure: ${failureCase.classname} - ${failureCase.name}\n${indent(failureCase.failure)}`); - continue; - } - - failures.push(failureCase); - } - } - - console.log(`Found ${failures.length} test failures`); - - return failures; -}); - -/** - * Creates and updates github issues for the given testcase failures. - */ -const updateGithubIssues = (githubClient, issues) => { - return createMapStream(async (failureCases) => { - - await Promise.all(failureCases.map(async (failureCase) => { - const existingIssue = find(issues, (issue) => { - return markdownMetadata.get(issue.body, 'test.class') === failureCase.classname && - markdownMetadata.get(issue.body, 'test.name') === failureCase.name; - }); - - if (existingIssue) { - // Increment failCount - const newCount = (markdownMetadata.get(existingIssue.body, 'test.failCount') || 0) + 1; - const newBody = markdownMetadata.set(existingIssue.body, 'test.failCount', newCount); - - await githubClient.issues.edit({ - owner: GITHUB_OWNER, - repo: GITHUB_REPO, - number: existingIssue.number, - state: 'open', // Reopen issue if it was closed. - body: newBody - }); - - // Append a new comment - await githubClient.issues.createComment({ - owner: GITHUB_OWNER, - repo: GITHUB_REPO, - number: existingIssue.number, - body: `New failure: [Jenkins Build](${BUILD_URL})` - }); - - console.log(`Updated issue ${existingIssue.html_url}, failCount: ${newCount}`); - } else { - let body = 'A test failed on a tracked branch\n' + - '```\n' + failureCase.failure + '\n```\n' + - `First failure: [Jenkins Build](${BUILD_URL})`; - body = markdownMetadata.set(body, { - 'test.class': failureCase.classname, - 'test.name': failureCase.name, - 'test.failCount': 1 - }); - - const newIssue = await githubClient.issues.create({ - owner: GITHUB_OWNER, - repo: GITHUB_REPO, - title: `Failing test: ${failureCase.classname} - ${failureCase.name}`, - body: body, - labels: [GITHUB_FLAKY_TEST_LABEL] - }); - - console.log(`Created issue ${newIssue.data.html_url}`); - } - })); - - return failureCases; - }); -}; - -/** - * Scans all junit XML files in ./target/junit/ and reports any found test failures to Github Issues. - */ -export async function reportFailedTests() { - const githubClient = getGithubClient(); - const issues = await paginate(githubClient, githubClient.issues.getForRepo({ - owner: GITHUB_OWNER, - repo: GITHUB_REPO, - labels: GITHUB_FLAKY_TEST_LABEL, - state: 'all', - per_page: 100 - })); - - vfs - .src(['./target/junit/**/*.xml']) - .pipe(mapXml()) - .pipe(filterFailures()) - .pipe(updateGithubIssues(githubClient, issues)) - .on('done', () => console.log(`Finished reporting test failures.`)); -} diff --git a/src/dev/failed_tests/report.test.js b/src/dev/failed_tests/report.test.js deleted file mode 100644 index f4c0e2e33edf0..0000000000000 --- a/src/dev/failed_tests/report.test.js +++ /dev/null @@ -1,193 +0,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. - */ - -/* eslint-disable max-len */ - -import { resolve } from 'path'; - -import vfs from 'vinyl-fs'; - -import { mapXml, filterFailures } from './report'; -import { createPromiseFromStreams } from '../../legacy/utils/streams/promise_from_streams'; -import { createConcatStream } from '../../legacy/utils/streams/concat_stream'; - -console.log = jest.fn(); -afterEach(() => jest.resetAllMocks()); - -describe('irrelevant failure filtering', () => { - describe('jest report', () => { - it('allows relevant tests', async () => { - const failures = await createPromiseFromStreams([ - vfs.src([resolve(__dirname, '__fixtures__/jest_report.xml')]), - mapXml(), - filterFailures(), - createConcatStream(), - ]); - - expect(console.log.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "Found 1 test failures", - ], -] -`); - expect(failures).toMatchInlineSnapshot(` -Array [ - Object { - "classname": "X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp", - "failure": " - TypeError: Cannot read property '0' of undefined - at Object..test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10) - ", - "name": "launcher can reconnect if process died", - "time": "7.060", - }, -] -`); - }); - }); - - describe('ftr report', () => { - it('allows relevant tests', async () => { - const failures = await createPromiseFromStreams([ - vfs.src([resolve(__dirname, '__fixtures__/ftr_report.xml')]), - mapXml(), - filterFailures(), - createConcatStream(), - ]); - - expect(console.log.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "Ignoring likely irrelevant failure: Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps - maps app \\"after all\\" hook - - { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used. - at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38) - at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12) - at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' } - ", - ], - Array [ - "Found 1 test failures", - ], -] -`); - expect(failures).toMatchInlineSnapshot(` -Array [ - Object { - "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js", - "failure": " - Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~=\\"layerTocActionsPanelToggleButtonRoad_Map_-_Bright\\"]) -Wait timed out after 10055ms - at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17 - at process._tickCallback (internal/process/next_tick.js:68:7) - at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9) - at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13) - ", - "name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook", - "time": "154.378", - }, -] -`); - }); - }); - - describe('mocha report', () => { - it('allows relevant tests', async () => { - const failures = await createPromiseFromStreams([ - vfs.src([resolve(__dirname, '__fixtures__/mocha_report.xml')]), - mapXml(), - filterFailures(), - createConcatStream(), - ]); - - expect(console.log.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "Ignoring likely irrelevant failure: X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts - code in multiple nodes \\"before all\\" hook - - Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.13.7
- - - - at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13) - at process._tickCallback (internal/process/next_tick.js:68:7) - ", - ], - Array [ - "Ignoring likely irrelevant failure: X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts - code in multiple nodes \\"after all\\" hook - - TypeError: Cannot read property 'shutdown' of undefined - at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23) - at process.topLevelDomainCallback (domain.js:120:23) - ", - ], - Array [ - "Found 0 test failures", - ], -] -`); - expect(failures).toMatchInlineSnapshot(`Array []`); - }); - }); - - describe('karma report', () => { - it('allows relevant tests', async () => { - const failures = await createPromiseFromStreams([ - vfs.src([resolve(__dirname, '__fixtures__/karma_report.xml')]), - mapXml(), - filterFailures(), - createConcatStream(), - ]); - - expect(console.log.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "Found 1 test failures", - ], -] -`); - expect(failures).toMatchInlineSnapshot(` -Array [ - Object { - "classname": "Browser Unit Tests.CoordinateMapsVisualizationTest", - "failure": "Error: expected 7069 to be below 64 - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) - at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) - at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) - at Generator.prototype. [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) -", - "name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK", - "time": "0.265", - }, -] -`); - }); - }); -}); diff --git a/src/dev/github_utils/index.js b/src/dev/github_utils/index.js deleted file mode 100644 index 0a3d35cf6a5ab..0000000000000 --- a/src/dev/github_utils/index.js +++ /dev/null @@ -1,43 +0,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. - */ - -import Octokit from '@octokit/rest'; -import { markdownMetadata } from './metadata'; - -export { markdownMetadata }; - -export function getGithubClient() { - const client = new Octokit(); - client.authenticate({ - type: 'token', - token: process.env.GITHUB_TOKEN - }); - - return client; -} - -export async function paginate(client, promise) { - let response = await promise; - let { data } = response; - while (client.hasNextPage(response)) { - response = await client.getNextPage(response); - data = data.concat(response.data); - } - return data; -} diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js index 8846beaed8b35..30501965bf1e7 100644 --- a/src/dev/jest/junit_reporter.js +++ b/src/dev/jest/junit_reporter.js @@ -109,13 +109,7 @@ export default class JestJUnitReporter { `TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml` ); - const reportXML = root.end({ - pretty: true, - indent: ' ', - newline: '\n', - spacebeforeslash: '', - }); - + const reportXML = root.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); } diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 4fe50baaf83be..727c51f704431 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -63,6 +63,7 @@ export const LICENSE_WHITELIST = [ 'MIT/X11', 'new BSD, and MIT', '(OFL-1.1 AND MIT)', + 'PSF', 'Public Domain', 'Unlicense', 'WTFPL OR ISC', diff --git a/src/es_archiver/cli.js b/src/es_archiver/cli.js index f37b6afdc4ba1..5481e05fcacd5 100644 --- a/src/es_archiver/cli.js +++ b/src/es_archiver/cli.js @@ -29,7 +29,7 @@ import { format as formatUrl } from 'url'; import readline from 'readline'; import { Command } from 'commander'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; import { EsArchiver } from './es_archiver'; import { ToolingLog } from '@kbn/dev-utils'; @@ -139,7 +139,7 @@ async function execute(fn) { } // run! - const client = new elasticsearch.Client({ + const client = new legacyElasticsearch.Client({ host: cmd.esUrl, log: cmd.verbose ? 'trace' : [] }); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx index 58b98ed8e4058..44837c514d4cf 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx @@ -38,6 +38,7 @@ interface ColorSchemaOptionsProps extends ColorSchemaVislibParams { colorSchemas: ColorSchema[]; uiState: VisOptionsProps['uiState']; setValue: SetColorSchemaOptionsValue; + showHelpText?: boolean; } function ColorSchemaOptions({ @@ -47,6 +48,7 @@ function ColorSchemaOptions({ invertColors, uiState, setValue, + showHelpText = true, }: ColorSchemaOptionsProps) { const [isCustomColors, setIsCustomColors] = useState(() => !!uiState.get('vis.colors')); @@ -76,12 +78,12 @@ function ColorSchemaOptions({ <> { min: number; paramName: ParamName; showInput?: boolean; + showLabels?: boolean; + showValue?: boolean; step?: number; value: '' | number; setValue: (paramName: ParamName, value: number) => void; @@ -37,6 +39,8 @@ function RangeOption({ max, min, showInput, + showLabels, + showValue = true, step, paramName, value, @@ -65,10 +69,11 @@ function RangeOption({ void; } -function TruncateLabelsOption({ disabled, value, setValue }: TruncateLabelsOptionProps) { +function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabelsOptionProps) { const onChange = (ev: ChangeEvent) => setValue('truncate', ev.target.value === '' ? null : parseFloat(ev.target.value)); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx index df547265e4fdc..45774b867d601 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx @@ -167,16 +167,13 @@ function HeatmapOptions(props: VisOptionsProps) { /> {stateParams.setColorRange && ( - <> - - - + )} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts index 422ad3c88fe8a..7955bf79c24eb 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Axis, ValueAxis, SeriesParam } from '../../../types'; +import { Axis, ValueAxis, SeriesParam, Style } from '../../../types'; import { ChartTypes, ChartModes, @@ -31,7 +31,7 @@ const defaultValueAxisId = 'ValueAxis-1'; const axis = { show: true, - style: {}, + style: {} as Style, title: { text: '', }, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts index 4206f7376decb..bb042abfdcca7 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts @@ -19,7 +19,7 @@ import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; import { Alignments, GaugeTypes } from './utils/collections'; -import { ColorSchemaVislibParams } from './types'; +import { ColorSchemaVislibParams, Labels, Style } from './types'; interface Gauge extends ColorSchemaVislibParams { backStyle: 'Full'; @@ -30,18 +30,14 @@ interface Gauge extends ColorSchemaVislibParams { colorsRange: RangeValues[]; extendRange: boolean; gaugeType: GaugeTypes; - labels: { - show: boolean; - }; + labels: Labels; percentageMode: boolean; scale: { show: boolean; labels: false; color: 'rgba(105,112,125,0.2)'; }; - style: { - subText: string; - }; + style: Style; } export interface GaugeVisParams { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js index da960d27ea7e1..75907618eb859 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -23,7 +23,7 @@ import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { GaugeOptions } from './components/options'; -import { getGaugeCollections, Alignments, GaugeColorModes, GaugeTypes } from './utils/collections'; +import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections'; import { vislibVisController } from './controller'; export default function GaugeVisType() { @@ -49,7 +49,7 @@ export default function GaugeVisType() { backStyle: 'Full', orientation: 'vertical', colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: GaugeColorModes.LABELS, + gaugeColorMode: ColorModes.LABELS, colorsRange: [ { from: 0, to: 50 }, { from: 50, to: 75 }, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js index 0ee5e2304701d..3a6b9f873aa87 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js @@ -22,7 +22,7 @@ import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { GaugeOptions } from './components/options'; -import { getGaugeCollections, GaugeTypes, GaugeColorModes } from './utils/collections'; +import { getGaugeCollections, GaugeTypes, ColorModes } from './utils/collections'; import { vislibVisController } from './controller'; import { visFactory } from '../../../ui/public/vis/vis_factory'; @@ -52,7 +52,7 @@ export default function GoalVisType() { orientation: 'vertical', useRanges: false, colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: GaugeColorModes.NONE, + gaugeColorMode: ColorModes.NONE, colorsRange: [ { from: 0, to: 10000 } ], diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts index 2b872fbd6c86c..008f2a0e0ce62 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts @@ -41,13 +41,21 @@ export interface ColorSchemaVislibParams { invertColors: boolean; } -interface Labels { - color: string; - filter: boolean; +export interface Labels { + color?: string; + filter?: boolean; overwriteColor?: boolean; rotate?: Rotates; show: boolean; - truncate: number | null; + truncate?: number | null; +} + +export interface Style { + bgFill: string; + bgColor: boolean; + labelColor: boolean; + subText: string; + fontSize: number; } export interface Scale { @@ -74,7 +82,7 @@ export interface Axis { position: Positions; scale: Scale; show: boolean; - style: object; + style: Style; title: { text: string }; type: AxisTypes; } diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts index 84b5cb5285948..6fe30483a32e8 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts @@ -253,7 +253,8 @@ export enum GaugeTypes { CIRCLE = 'Circle', } -export enum GaugeColorModes { +export enum ColorModes { + BACKGROUND = 'Background', LABELS = 'Labels', NONE = 'None', } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 506f4f56d703b..31ddafb4ec719 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -25,10 +25,10 @@ import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { VisType } from 'ui/vis'; import { VisualizeConstants } from '../visualize_constants'; - +import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; interface TypeSelectionProps { isOpen: boolean; @@ -50,6 +50,7 @@ class NewVisModal extends React.Component; constructor(props: TypeSelectionProps) { super(props); @@ -58,6 +59,8 @@ class NewVisModal extends React.Component { - if (visType.requiresSearch && visType.options.showIndexSelection) { + private onVisTypeSelected = (visType: VisType | VisTypeAlias) => { + if (!('aliasUrl' in visType) && visType.requiresSearch && visType.options.showIndexSelection) { this.setState({ showSearchVisModal: true, visType, }); } else { - const params = [`type=${encodeURIComponent(visType.name)}`, ...this.props.editorParams!]; - this.props.onClose(); - location.assign(`${baseUrl}${params.join('&')}`); + this.redirectToVis(visType); } }; private onSearchSelected = (searchId: string, searchType: string) => { - this.props.onClose(); + this.redirectToVis(this.state.visType!, searchType, searchId); + }; + + private redirectToVis(visType: VisType | VisTypeAlias, searchType?: string, searchId?: string) { + this.trackUiMetric(METRIC_TYPE.CLICK, visType.name); + + if ('aliasUrl' in visType) { + window.location = chrome.addBasePath(visType.aliasUrl); + + return; + } + + let params = [`type=${encodeURIComponent(visType.name)}`]; + + if (searchType) { + params.push(`${searchType === 'search' ? 'savedSearchId' : 'indexPattern'}=${searchId}`); + } + params = params.concat(this.props.editorParams!); - const params = [ - `type=${encodeURIComponent(this.state.visType!.name)}`, - `${searchType === 'search' ? 'savedSearchId' : 'indexPattern'}=${searchId}`, - ...this.props.editorParams!, - ]; + this.props.onClose(); location.assign(`${baseUrl}${params.join('&')}`); - }; + } } export { NewVisModal }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx index 04d5456dbf80d..3d5e0b4f6afcf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -21,7 +21,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { sortByOrder } from 'lodash'; import React, { ChangeEvent } from 'react'; -import chrome from 'ui/chrome'; import { EuiFieldSearch, @@ -52,7 +51,7 @@ interface VisTypeAliasListEntry extends VisTypeAlias { } interface TypeSelectionProps { - onVisTypeSelected: (visType: VisType) => void; + onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; visTypesRegistry: TypesStart; showExperimental: boolean; } @@ -234,12 +233,7 @@ class TypeSelection extends React.Component { - window.location = chrome.addBasePath(visType.aliasUrl); - } - : () => this.props.onVisTypeSelected(visType); + const onClick = () => this.props.onVisTypeSelected(visType); const highlightedType: HighlightedType = { title: visType.title, diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx new file mode 100644 index 0000000000000..9649588976c0d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -0,0 +1,197 @@ +/* + * 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, { useCallback } from 'react'; +import { + EuiButtonGroup, + EuiButtonGroupProps, + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { VisOptionsProps } from 'ui/vis/editors/default'; +import { + ColorRanges, + ColorSchemaOptions, + SwitchOption, + RangeOption, + SetColorSchemaOptionsValue, +} from '../../../kbn_vislib_vis_types/public/components'; +import { ColorModes } from '../../../kbn_vislib_vis_types/public/utils/collections'; +import { MetricVisParam, VisParams } from '../types'; + +function MetricVisOptions({ + stateParams, + setValue, + setValidity, + setTouched, + vis, + uiState, +}: VisOptionsProps) { + const setMetricValue: ( + paramName: T, + value: MetricVisParam[T] + ) => void = useCallback( + (paramName, value) => + setValue('metric', { + ...stateParams.metric, + [paramName]: value, + }), + [setValue, stateParams.metric] + ); + + const setMetricLabels: ( + paramName: T, + value: MetricVisParam['labels'][T] + ) => void = useCallback( + (paramName, value) => + setMetricValue('labels', { + ...stateParams.metric.labels, + [paramName]: value, + }), + [setMetricValue, stateParams.metric.labels] + ); + + const setMetricStyle: ( + paramName: T, + value: MetricVisParam['style'][T] + ) => void = useCallback( + (paramName, value) => + setMetricValue('style', { + ...stateParams.metric.style, + [paramName]: value, + }), + [setMetricValue, stateParams.metric.style] + ); + + const setColorMode: EuiButtonGroupProps['onChange'] = useCallback( + id => setMetricValue('metricColorMode', id as ColorModes), + [setMetricValue] + ); + + const metricColorModeLabel = i18n.translate('visTypeMetric.params.color.useForLabel', { + defaultMessage: 'Use color for', + }); + + return ( + <> + + +

+ +

+
+ + + + + +
+ + + + + +

+ +

+
+ + + + + + + + + +
+ + + + + +

+ +

+
+ + + +
+ + ); +} + +export { MetricVisOptions }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.html b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.html deleted file mode 100644 index 6680ec884728f..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.html +++ /dev/null @@ -1,249 +0,0 @@ -
- -
- -
- -
-
- -
- -
- -
-
- -
-
-
- - - - - -
-
- -
- - - - - - - - - - - - -
- - - -
- - - - - -
- -
-

- - - -

-
- -
-
- -
-
- -
-
-
- - - - - -
-
-
- -
- -
- -
-
-
- -
- -
-
-
-
- -
- -
- -
-
- -
-
- -
-
-
- - - - -
-
- -
- -
- -
- -
-
- -
-
-
diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.js deleted file mode 100644 index af81490224cf9..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_params/metric_vis_params.js +++ /dev/null @@ -1,86 +0,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. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import metricVisParamsTemplate from './metric_vis_params.html'; - -export function MetricVisParams() { - return { - restrict: 'E', - template: metricVisParamsTemplate, - replace: true, - link: function ($scope) { - $scope.collections = $scope.vis.type.editorConfig.collections; - $scope.showColorRange = true; - - $scope.$watch('editorState.params.metric.metricColorMode', newValue => { - switch (newValue) { - case 'Labels': - $scope.editorState.params.metric.style.labelColor = true; - $scope.editorState.params.metric.style.bgColor = false; - break; - case 'Background': - $scope.editorState.params.metric.style.labelColor = false; - $scope.editorState.params.metric.style.bgColor = true; - break; - case 'None': - $scope.editorState.params.metric.style.labelColor = false; - $scope.editorState.params.metric.style.bgColor = false; - break; - } - }); - - $scope.resetColors = function () { - $scope.uiState.set('vis.colors', null); - $scope.customColors = false; - }; - - $scope.getGreaterThan = function (index) { - if (index === 0) return -Infinity; - return $scope.editorState.params.metric.colorsRange[index - 1].to; - }; - - $scope.addRange = function () { - const previousRange = _.last($scope.editorState.params.metric.colorsRange); - const from = previousRange ? previousRange.to : 0; - const to = previousRange ? from + (previousRange.to - previousRange.from) : 100; - $scope.editorState.params.metric.colorsRange.push({ from: from, to: to }); - }; - - $scope.removeRange = function (index) { - $scope.editorState.params.metric.colorsRange.splice(index, 1); - }; - - $scope.getColor = function (index) { - const defaultColors = this.uiState.get('vis.defaultColors'); - const overwriteColors = this.uiState.get('vis.colors'); - const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors; - return colors ? Object.values(colors)[index] : 'transparent'; - }; - - $scope.uiState.on('colorChanged', () => { - $scope.customColors = true; - }); - - $scope.editorState.requiredDescription = i18n.translate( - 'visTypeMetric.params.ranges.warning.requiredDescription', { defaultMessage: 'Required:' }); - }, - }; -} diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts index f214bcbc82f7d..65179ae1315c6 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts @@ -22,16 +22,11 @@ import { npSetup, npStart } from 'ui/new_platform'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { MetricVisPluginSetupDependencies } from './plugin'; -import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: visualizationsSetup, - - // Temporary solution - // It will be removed when all dependent services are migrated to the new platform. - __LEGACY: new LegacyDependenciesPlugin(), }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index e4dbd9134a4f5..89f74ef4b53e9 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -19,9 +19,10 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; -import { ExpressionFunction, KibanaDatatable, Render, Range, Style } from '../../interpreter/types'; +import { vislibColorMaps, ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { ExpressionFunction, KibanaDatatable, Range, Render, Style } from '../../interpreter/types'; +import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { visType, DimensionsVisParam, VisParams } from './types'; type Context = KibanaDatatable; @@ -29,8 +30,8 @@ const name = 'metricVis'; interface Arguments { percentage: boolean; - colorScheme: string; - colorMode: string; + colorScheme: ColorSchemas; + colorMode: ColorModes; useRanges: boolean; invertColors: boolean; showLabels: boolean; @@ -42,39 +43,10 @@ interface Arguments { bucket: any; // these aren't typed yet } -interface VisParams { - dimensions: DimensionsVisParam; - metric: MetricVisParam; -} - -interface DimensionsVisParam { - metrics: any; - bucket?: any; -} - -interface MetricVisParam { - percentageMode: Arguments['percentage']; - useRanges: Arguments['useRanges']; - colorSchema: Arguments['colorScheme']; - metricColorMode: Arguments['colorMode']; - colorsRange: Arguments['colorRange']; - labels: { - show: Arguments['showLabels']; - }; - invertColors: Arguments['invertColors']; - style: { - bgFill: Arguments['bgFill']; - bgColor: boolean; - labelColor: boolean; - subText: Arguments['subText']; - fontSize: number; - }; -} - interface RenderValue { - visType: 'metric'; + visType: typeof visType; visData: Context; - visConfig: VisParams; + visConfig: Pick; params: any; } @@ -113,7 +85,7 @@ export const createMetricVisFn = (): ExpressionFunction< colorMode: { types: ['string'], default: '"None"', - options: ['None', 'Label', 'Background'], + options: [ColorModes.NONE, ColorModes.LABELS, ColorModes.BACKGROUND], help: i18n.translate('visTypeMetric.function.colorMode.help', { defaultMessage: 'Which part of metric to color', }), @@ -206,7 +178,7 @@ export const createMetricVisFn = (): ExpressionFunction< as: 'visualization', value: { visData: context, - visType: 'metric', + visType, visConfig: { metric: { percentageMode: args.percentage, @@ -220,8 +192,8 @@ export const createMetricVisFn = (): ExpressionFunction< invertColors: args.invertColors, style: { bgFill: args.bgFill, - bgColor: args.colorMode === 'Background', - labelColor: args.colorMode === 'Labels', + bgColor: args.colorMode === ColorModes.BACKGROUND, + labelColor: args.colorMode === ColorModes.LABELS, subText: args.subText, fontSize, }, diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index 76e88888ef7f2..a05df6f4d1564 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -21,12 +21,16 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { Schemas } from 'ui/vis/editors/default/schemas'; -// @ts-ignore -import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; + +import { AggGroupNames } from 'ui/vis/editors/default'; +import { colorSchemas, ColorSchemas } from 'ui/vislib/components/color/colormaps'; + // @ts-ignore import { MetricVisComponent } from './components/metric_vis_controller'; import { visFactory } from '../../visualizations/public'; +import { MetricVisOptions } from './components/metric_vis_options'; +import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; export const createMetricVisTypeDefinition = () => { return visFactory.createReactVisualization({ @@ -45,8 +49,8 @@ export const createMetricVisTypeDefinition = () => { metric: { percentageMode: false, useRanges: false, - colorSchema: 'Green to Red', - metricColorMode: 'None', + colorSchema: ColorSchemas.GreenToRed, + metricColorMode: ColorModes.NONE, colorsRange: [{ from: 0, to: 10000 }], labels: { show: true, @@ -66,33 +70,30 @@ export const createMetricVisTypeDefinition = () => { collections: { metricColorMode: [ { - id: 'None', + id: ColorModes.NONE, label: i18n.translate('visTypeMetric.colorModes.noneOptionLabel', { defaultMessage: 'None', }), }, { - id: 'Labels', + id: ColorModes.LABELS, label: i18n.translate('visTypeMetric.colorModes.labelsOptionLabel', { defaultMessage: 'Labels', }), }, { - id: 'Background', + id: ColorModes.BACKGROUND, label: i18n.translate('visTypeMetric.colorModes.backgroundOptionLabel', { defaultMessage: 'Background', }), }, ], - colorSchemas: Object.values(vislibColorMaps).map((value: any) => ({ - id: value.id, - label: value.label, - })), + colorSchemas, }, - optionsTemplate: '', + optionsTemplate: MetricVisOptions, schemas: new Schemas([ { - group: 'metrics', + group: AggGroupNames.Metrics, name: 'metric', title: i18n.translate('visTypeMetric.schemas.metricTitle', { defaultMessage: 'Metric' }), min: 1, @@ -118,7 +119,7 @@ export const createMetricVisTypeDefinition = () => { ], }, { - group: 'buckets', + group: AggGroupNames.Buckets, name: 'group', title: i18n.translate('visTypeMetric.schemas.splitGroupTitle', { defaultMessage: 'Split group', diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index 1c5dbbd03a18d..e3e0ffaab2116 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -18,19 +18,16 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { LegacyDependenciesPlugin } from './shim'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; -// @ts-ignore import { createMetricVisTypeDefinition } from './metric_vis_type'; /** @internal */ export interface MetricVisPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; - __LEGACY: LegacyDependenciesPlugin; } /** @internal */ @@ -41,12 +38,7 @@ export class MetricVisPlugin implements Plugin { this.initializerContext = initializerContext; } - public setup( - core: CoreSetup, - { expressions, visualizations, __LEGACY }: MetricVisPluginSetupDependencies - ) { - __LEGACY.setup(); - + public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) { expressions.registerFunction(createMetricVisFn); visualizations.types.registerVisualization(createMetricVisTypeDefinition); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/shim/metric_vis_legacy_module.ts b/src/legacy/core_plugins/vis_type_metric/public/shim/metric_vis_legacy_module.ts deleted file mode 100644 index 25942b13a4103..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/shim/metric_vis_legacy_module.ts +++ /dev/null @@ -1,31 +0,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. - */ -import { once } from 'lodash'; - -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import 'ui/directives/inequality'; -// @ts-ignore -import { MetricVisParams } from '../components/metric_vis_params'; - -/** @internal */ -export const initMetricVisLegacyModule = once((): void => { - uiModules.get('kibana/metric_vis', ['kibana']).directive('metricVisParams', MetricVisParams); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts new file mode 100644 index 0000000000000..54f1f36e19c99 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -0,0 +1,50 @@ +/* + * 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 { ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; +import { SchemaConfig } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { Labels, Style } from '../../kbn_vislib_vis_types/public/types'; + +export const visType = 'metric'; + +export interface DimensionsVisParam { + metrics: SchemaConfig[]; + bucket?: SchemaConfig[]; +} + +export interface MetricVisParam { + percentageMode: boolean; + useRanges: boolean; + colorSchema: ColorSchemas; + metricColorMode: ColorModes; + colorsRange: RangeValues[]; + labels: Labels; + invertColors: boolean; + style: Style; +} + +export interface VisParams { + addTooltip: boolean; + addLegend: boolean; + dimensions: DimensionsVisParam; + metric: MetricVisParam; + type: typeof visType; +} diff --git a/src/legacy/ui/public/directives/__tests__/inequality.js b/src/legacy/ui/public/directives/__tests__/inequality.js deleted file mode 100644 index 0e6a537d69f7c..0000000000000 --- a/src/legacy/ui/public/directives/__tests__/inequality.js +++ /dev/null @@ -1,124 +0,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. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../inequality'; - -describe('greater_than model validator directive', function () { - let $compile; - let $rootScope; - let html; - - beforeEach(ngMock.module('kibana')); - - beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_; - $rootScope = _$rootScope_; - })); - - describe('without value', function () { - let element; - beforeEach(function () { - html = ''; - element = $compile(html)($rootScope); - }); - - it('should be valid when larger than 0', function () { - $rootScope.value = '1'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should be valid for 0', function () { - $rootScope.value = '0'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should be valid for negatives', function () { - $rootScope.value = '-10'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - }); - - describe('with string values', function () { - let element; - beforeEach(function () { - html = ``; - element = $compile(html)($rootScope); - }); - - it('should be valid for greater than 10', function () { - $rootScope.value = '15'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should be invalid for 10', function () { - $rootScope.value = '10'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); - - // Edge case because '5' > '10' as strings - it('should be invalid less than 10', function () { - $rootScope.value = '5'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); - }); - - [0, 1, 10, 42, -12].forEach(function (num) { - describe('with value ' + num, function () { - let element; - beforeEach(function () { - html = ''; - element = $compile(html)($rootScope); - }); - - it('should be valid when larger than ' + num, function () { - $rootScope.value = num + 1; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should be invalid for ' + num, function () { - $rootScope.value = num; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); - - it('should be invalid for less than ' + num, function () { - $rootScope.value = num - 1; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); - - it('should be valid for empty model values', () => { - [undefined, null, ''].forEach(val => { - $rootScope.value = val; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/directives/inequality.js b/src/legacy/ui/public/directives/inequality.js deleted file mode 100644 index a08b617ceb929..0000000000000 --- a/src/legacy/ui/public/directives/inequality.js +++ /dev/null @@ -1,65 +0,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. - */ - -import { uiModules } from '../modules'; - -function makeDirectiveDef(id, compare) { - return function ($parse) { - return { - require: 'ngModel', - link: function ($scope, $el, $attr, ngModel) { - const getBound = function () { return $parse($attr[id])($scope); }; - - ngModel.$parsers.push(validate); - ngModel.$formatters.push(validate); - - $scope.$watch(getBound, function () { - validate(ngModel.$viewValue); - }); - - // We only set it to invalid when both the model value and the value to compare against are - // provided, and the values do not meet the condition. - function validate(val) { - const bound = getBound(); - const left = parseFloat(val); - const right = parseFloat(bound); - const isValid = isEmpty(val) || isEmpty(bound) || compare(left, right); - ngModel.$setValidity(id, isValid); - return val; - } - - function isEmpty(val) { - return typeof val === 'undefined' || val === null || val === ''; - } - } - }; - }; -} - -uiModules - .get('kibana') - .directive('greaterThan', makeDirectiveDef('greaterThan', function (a, b) { - return a > b; - })) - .directive('lessThan', makeDirectiveDef('lessThan', function (a, b) { - return a < b; - })) - .directive('greaterOrEqualThan', makeDirectiveDef('greaterOrEqualThan', function (a, b) { - return a >= b; - })); diff --git a/src/legacy/ui/public/vis/editors/default/_agg.scss b/src/legacy/ui/public/vis/editors/default/_agg.scss index e66f3185d25dc..da8fbaa36dd07 100644 --- a/src/legacy/ui/public/vis/editors/default/_agg.scss +++ b/src/legacy/ui/public/vis/editors/default/_agg.scss @@ -1,17 +1,3 @@ -// -// Misc elements -- found in agg_types/controls/*.html -// - -.visEditorAgg__rangesTable { - td { - padding: 0 $euiSizeS $euiSizeS 0; - - &:last-child { - padding-right: 0; - } - } -} - .visEditorAgg__subAgg { border: $euiBorderThick; border-radius: $euiBorderRadius; diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss index 03a9a9aaa4f45..5a8a0aabad565 100644 --- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss +++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss @@ -122,49 +122,10 @@ background-color: lightOrDarkTheme($euiPageBackgroundColor, $euiColorLightestShade); } -.visEditorSidebar__collapsible--margin { - margin-top: $euiSizeM; -} - .visEditorSidebar__collapsible--marginBottom { margin-bottom: $euiSizeM; } -.visEditorSidebar__collapsibleTitle { - @include euiFontSizeS; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - white-space: nowrap; - - + * { - margin-top: $euiSize; - display: block; - } - - + .form-group { - margin-top: $euiSizeXS; - } - - label { - margin-bottom: 0 !important; // For filters - } -} - -.visEditorSidebar__collapsibleTitleLabel { - color: $euiColorPrimary; - display: flex; - align-items: center; - margin-right: $euiSizeXS; - line-height: $euiLineHeight; -} - -.visEditorSidebar__collapsibleTitleText { - @include euiTextTruncate; - margin-left: $euiSizeS; -} - // // FORMS // diff --git a/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx b/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx index b238d1124528d..071e15f8b97f8 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx @@ -41,6 +41,7 @@ const generateId = htmlIdGenerator(); const isEmpty = (value: any) => value === undefined || value === null; export interface RangeValues { + type?: 'range'; from?: number; to?: number; } diff --git a/src/plugins/expressions/common/expressions/expression_types/shape.ts b/src/plugins/expressions/common/expressions/expression_types/shape.ts index e8f6fbdc8062e..fd176e188a47b 100644 --- a/src/plugins/expressions/common/expressions/expression_types/shape.ts +++ b/src/plugins/expressions/common/expressions/expression_types/shape.ts @@ -20,7 +20,9 @@ import { ExpressionType } from '../types'; import { Render } from './render'; -export const shape = (): ExpressionType<'shape', Render> => ({ +const name = 'shape'; + +export const shape = (): ExpressionType> => ({ name: 'shape', to: { render: input => { diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 11b65d6dcc813..52e2b869d5639 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -19,7 +19,7 @@ import { ToolingLog } from '@kbn/dev-utils'; import { - createEsTestCluster, + createLegacyEsTestCluster, DEFAULT_SUPERUSER_PASS, esTestConfig, kbnTestConfig, @@ -205,7 +205,7 @@ export function createTestServers({ log.info('starting elasticsearch'); log.indent(4); - const es = createEsTestCluster( + const es = createLegacyEsTestCluster( defaultsDeep({}, get(settings, 'es', {}), { log, license, diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 0dbd2d0262cf4..47d93d811e3f6 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -17,13 +17,13 @@ * under the License. */ -import { EsProvider } from './es'; +import { LegacyEsProvider } from './legacy_es'; import { EsArchiverProvider } from './es_archiver'; import { KibanaServerProvider } from './kibana_server'; import { RetryProvider } from './retry'; export const services = { - es: EsProvider, + es: LegacyEsProvider, esArchiver: EsArchiverProvider, kibanaServer: KibanaServerProvider, retry: RetryProvider, diff --git a/test/common/services/es.ts b/test/common/services/legacy_es.ts similarity index 86% rename from test/common/services/es.ts rename to test/common/services/legacy_es.ts index 9a19c533651d0..b53061d185ccc 100644 --- a/test/common/services/es.ts +++ b/test/common/services/legacy_es.ts @@ -19,15 +19,15 @@ import { format as formatUrl } from 'url'; -import elasticsearch from 'elasticsearch'; +import * as legacyElasticsearch from 'elasticsearch'; import { DEFAULT_API_VERSION } from '../../../src/core/server/elasticsearch/elasticsearch_config'; import { FtrProviderContext } from '../ftr_provider_context'; -export function EsProvider({ getService }: FtrProviderContext): elasticsearch.Client { +export function LegacyEsProvider({ getService }: FtrProviderContext): legacyElasticsearch.Client { const config = getService('config'); - return new elasticsearch.Client({ + return new legacyElasticsearch.Client({ apiVersion: DEFAULT_API_VERSION, host: formatUrl(config.get('servers.elasticsearch')), requestTimeout: config.get('timeouts.esRequestTimeout'), diff --git a/test/functional/page_objects/share_page.js b/test/functional/page_objects/share_page.ts similarity index 75% rename from test/functional/page_objects/share_page.js rename to test/functional/page_objects/share_page.ts index 0bd2def010274..906effcb54a26 100644 --- a/test/functional/page_objects/share_page.js +++ b/test/functional/page_objects/share_page.ts @@ -17,7 +17,9 @@ * under the License. */ -export function SharePageProvider({ getService, getPageObjects }) { +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SharePageProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); const PageObjects = getPageObjects(['visualize', 'common']); @@ -32,7 +34,7 @@ export function SharePageProvider({ getService, getPageObjects }) { return testSubjects.click('shareTopNavButton'); } - async openShareMenuItem(itemTitle) { + async openShareMenuItem(itemTitle: string) { log.debug(`openShareMenuItem title:${itemTitle}`); const isShareMenuOpen = await this.isShareMenuOpen(); if (!isShareMenuOpen) { @@ -45,11 +47,24 @@ export function SharePageProvider({ getService, getPageObjects }) { await this.clickShareTopNavButton(); } const menuPanel = await find.byCssSelector('div.euiContextMenuPanel'); - testSubjects.click(`sharePanel-${itemTitle.replace(' ', '')}`); + await testSubjects.click(`sharePanel-${itemTitle.replace(' ', '')}`); await testSubjects.waitForDeleted(menuPanel); } + /** + * if there are more entries in the share menu, the permalinks entry has to be clicked first + * else the selection isn't displayed. this happens if you're testing against an instance + * with xpack features enabled, where there's also a csv sharing option + * in a pure OSS environment, the permalinks sharing panel is displayed initially + */ + async openPermaLinks() { + if (await testSubjects.exists('sharePanel-Permalinks')) { + await testSubjects.click(`sharePanel-Permalinks`); + } + } + async getSharedUrl() { + await this.openPermaLinks(); return await testSubjects.getAttribute('copyShareUrlButton', 'data-share-url'); } @@ -68,9 +83,9 @@ export function SharePageProvider({ getService, getPageObjects }) { } async exportAsSavedObject() { + await this.openPermaLinks(); return await testSubjects.click('exportAsSavedObject'); } - } return new SharePage(); diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index a1fb9eee4b0b2..d588d977e4538 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/test/scripts/jenkins_firefox_smoke.sh b/test/scripts/jenkins_firefox_smoke.sh index 2cae804b94413..dc91eb3a2c3dd 100755 --- a/test/scripts/jenkins_firefox_smoke.sh +++ b/test/scripts/jenkins_firefox_smoke.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index fc379ec1c5ecc..b1ed3ed4551ee 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT fi export TEST_BROWSER_HEADLESS=1 diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh index abfb0cca0e99d..da077541ea1d5 100755 --- a/test/scripts/jenkins_visual_regression.sh +++ b/test/scripts/jenkins_visual_regression.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 34928f2564153..4faa1ff793141 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT fi export TEST_BROWSER_HEADLESS=1 diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 180e1d225365e..69df6644bf1ee 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/test/scripts/jenkins_xpack_firefox_smoke.sh b/test/scripts/jenkins_xpack_firefox_smoke.sh index 216c1c39370eb..a30a98472ad2b 100755 --- a/test/scripts/jenkins_xpack_firefox_smoke.sh +++ b/test/scripts/jenkins_xpack_firefox_smoke.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index cc9b6adcecd40..7312a9938c105 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -3,7 +3,7 @@ set -e if [[ -z "$IS_PIPELINE_JOB" ]] ; then - trap 'node "$KIBANA_DIR/src/dev/failed_tests/cli"' EXIT + trap 'node "$KIBANA_DIR/scripts/report_failed_tests"' EXIT else source src/dev/ci_setup/setup_env.sh fi diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 95c872c20aeb1..4d3d2b210bbae 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -43,6 +43,7 @@ export const apm: LegacyPluginInitializer = kibana => { apmUiEnabled: config.get('xpack.apm.ui.enabled'), // TODO: rename to apm_oss.indexPatternTitle in 7.0 (breaking change) apmIndexPatternTitle: config.get('apm_oss.indexPattern'), + apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled'), apmTransactionIndices: config.get('apm_oss.transactionIndices') }; }, @@ -70,7 +71,10 @@ export const apm: LegacyPluginInitializer = kibana => { // buckets minimumBucketSize: Joi.number().default(15), - bucketTargetCount: Joi.number().default(15) + bucketTargetCount: Joi.number().default(15), + + // service map + serviceMapEnabled: Joi.boolean().default(false) }).default(); }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap index 2c011c136f005..be9d67f31cb11 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -36,7 +36,7 @@ exports[`DetailView should render Discover button 1`] = ` - View 10 occurrences in Discover + View 10 occurrences in Discover. `; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 41ccb6a6c736e..9b598ff469b9d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -39,6 +39,7 @@ import { Summary } from '../../../shared/Summary'; import { TimestampSummaryItem } from '../../../shared/Summary/TimestampSummaryItem'; import { HttpInfoSummaryItem } from '../../../shared/Summary/HttpInfoSummaryItem'; import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; +import { UserAgentSummaryItem } from '../../../shared/Summary/UserAgentSummaryItem'; const HeaderContainer = styled.div` display: flex; @@ -103,7 +104,7 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { 'xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel', { defaultMessage: - 'View {occurrencesCount} occurrences in Discover', + 'View {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} in Discover.', values: { occurrencesCount } } )} @@ -121,6 +122,9 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { status={status} /> ) : null, + transaction && transaction.user_agent ? ( + + ) : null, transaction && ( + {i18n.translate('xpack.apm.home.serviceMapTabLabel', { + defaultMessage: 'Service Map' + })} + + ), + render: () => , + name: 'service-map' + }); +} + const SETTINGS_LINK_LABEL = i18n.translate('xpack.apm.settingsLinkLabel', { defaultMessage: 'Settings' }); interface Props { - tab: 'traces' | 'services'; + tab: 'traces' | 'services' | 'service-map'; } export function Home({ tab }: Props) { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx index 528870da12cdc..c66c73b9f98fb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; +import { npStart } from 'ui/new_platform'; import { ErrorGroupDetails } from '../../ErrorGroupDetails'; import { ServiceDetails } from '../../ServiceDetails'; import { TransactionDetails } from '../../TransactionDetails'; @@ -149,3 +150,26 @@ export const routes: BreadcrumbRoute[] = [ name: RouteName.TRANSACTION_NAME } ]; + +if (npStart.core.injectedMetadata.getInjectedVar('apmServiceMapEnabled')) { + routes.push( + { + exact: true, + path: '/service-map', + component: () => , + breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { + defaultMessage: 'Service Map' + }), + name: RouteName.SERVICE_MAP + }, + { + exact: true, + path: '/services/:serviceName/service-map', + component: () => , + breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { + defaultMessage: 'Service Map' + }), + name: RouteName.SINGLE_SERVICE_MAP + } + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx index 26c68b835ff03..3af5b217ca738 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx @@ -7,6 +7,8 @@ export enum RouteName { HOME = 'home', SERVICES = 'services', + SERVICE_MAP = 'service-map', + SINGLE_SERVICE_MAP = 'single-service-map', TRACES = 'traces', SERVICE = 'service', TRANSACTIONS = 'transactions', diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx index fd9002e0edd94..1fc83217f2a1c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiTabs, EuiSpacer } from '@elastic/eui'; +import { npStart } from 'ui/new_platform'; import { ErrorGroupOverview } from '../ErrorGroupOverview'; import { TransactionOverview } from '../TransactionOverview'; import { ServiceMetrics } from '../ServiceMetrics'; @@ -19,9 +20,11 @@ import { MetricOverviewLink } from '../../shared/Links/apm/MetricOverviewLink'; import { ServiceNodeOverviewLink } from '../../shared/Links/apm/ServiceNodeOverviewLink'; import { ServiceNodeOverview } from '../ServiceNodeOverview'; import { useAgentName } from '../../../hooks/useAgentName'; +import { ServiceMap } from '../ServiceMap'; +import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; interface Props { - tab: 'transactions' | 'errors' | 'metrics' | 'nodes'; + tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map'; } export function ServiceDetailTabs({ tab }: Props) { @@ -90,6 +93,22 @@ export function ServiceDetailTabs({ tab }: Props) { tabs.push(metricsTab); } + const serviceMapTab = { + link: ( + + {i18n.translate('xpack.apm.home.serviceMapTabLabel', { + defaultMessage: 'Service Map' + })} + + ), + render: () => , + name: 'service-map' + }; + + if (npStart.core.injectedMetadata.getInjectedVar('apmServiceMapEnabled')) { + tabs.push(serviceMapTab); + } + const selectedTab = tabs.find(serviceTab => serviceTab.name === tab); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx new file mode 100644 index 0000000000000..f7166bd0cec5c --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext, useState, useEffect } from 'react'; +import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; +import { CytoscapeContext } from './Cytoscape'; +import { FullscreenPanel } from './FullscreenPanel'; + +const Container = styled('div')` + left: ${theme.gutterTypes.gutterMedium}; + position: absolute; + top: ${theme.gutterTypes.gutterSmall}; +`; + +const Button = styled(EuiButtonIcon)` + display: block; + margin: ${theme.paddingSizes.xs}; +`; + +const ZoomInButton = styled(Button)` + margin-bottom: ${theme.paddingSizes.s}; +`; + +const ZoomPanel = styled(EuiPanel)` + margin-bottom: ${theme.paddingSizes.s}; +`; + +const duration = parseInt(theme.euiAnimSpeedFast, 10); +const steps = 5; + +function doZoom(cy: cytoscape.Core | undefined, increment: number) { + if (cy) { + const level = cy.zoom() + increment; + cy.animate({ + duration, + zoom: { level, position: cy.$('.primary').position() } + }); + } +} + +export function Controls() { + const cy = useContext(CytoscapeContext); + + const [zoom, setZoom] = useState((cy && cy.zoom()) || 1); + + useEffect(() => { + if (cy) { + cy.on('zoom', event => { + setZoom(event.cy.zoom()); + }); + } + }, [cy]); + + function zoomIn() { + doZoom(cy, increment); + } + + function zoomOut() { + doZoom(cy, -increment); + } + + if (!cy) { + return null; + } + + const maxZoom = cy.maxZoom(); + const isMaxZoom = zoom === maxZoom; + const minZoom = cy.minZoom(); + const isMinZoom = zoom === minZoom; + const increment = (maxZoom - minZoom) / steps; + const mapDomElement = cy.container(); + const zoomInLabel = i18n.translate('xpack.apm.serviceMap.zoomIn', { + defaultMessage: 'Zoom in' + }); + const zoomOutLabel = i18n.translate('xpack.apm.serviceMap.zoomOut', { + defaultMessage: 'Zoom out' + }); + + return ( + + + +