diff --git a/.eslintrc.js b/.eslintrc.js index ab218db7a6dd37..7dd0860aac04e1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -257,7 +257,8 @@ module.exports = { 'src/legacy/**/*', 'x-pack/**/*', '!x-pack/**/*.test.*', - 'src/plugins/**/(public|server)/**/*', + '!x-pack/test/**/*', + '(src|x-pack)/plugins/**/(public|server)/**/*', 'src/core/(public|server)/**/*', ], from: [ @@ -277,16 +278,35 @@ module.exports = { '!src/core/server/types', '!src/core/server/*.test.mocks.ts', - 'src/plugins/**/public/**/*', - '!src/plugins/**/public/index.{js,ts,tsx}', - - 'src/plugins/**/server/**/*', - '!src/plugins/**/server/index.{js,ts,tsx}', + '(src|x-pack)/plugins/**/(public|server)/**/*', + '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,ts,tsx}', ], allowSameFolder: true, + errorMessage: 'Plugins may only import from top-level public and server modules.', }, { - target: ['src/core/**/*'], + target: [ + '(src|x-pack)/plugins/**/*', + '!(src|x-pack)/plugins/*/server/**/*', + + 'src/legacy/core_plugins/**/*', + '!src/legacy/core_plugins/*/server/**/*', + '!src/legacy/core_plugins/*/index.{js,ts,tsx}', + + 'x-pack/legacy/plugins/**/*', + '!x-pack/legacy/plugins/*/server/**/*', + '!x-pack/legacy/plugins/*/index.{js,ts,tsx}', + ], + from: [ + 'src/core/server', + 'src/core/server/**/*', + '(src|x-pack)/plugins/*/server/**/*', + ], + errorMessage: + 'Server modules cannot be imported into client modules or shared modules.', + }, + { + target: ['src/**/*'], from: ['x-pack/**/*'], errorMessage: 'OSS cannot import x-pack files.', }, @@ -300,6 +320,11 @@ module.exports = { ], errorMessage: 'The core cannot depend on any plugins.', }, + { + target: ['(src|x-pack)/plugins/*/public/**/*'], + from: ['ui/**/*', 'uiExports/**/*'], + errorMessage: 'Plugins cannot import legacy UI code.', + }, { from: ['src/legacy/ui/**/*', 'ui/**/*'], target: [ @@ -660,8 +685,6 @@ module.exports = { 'accessor-pairs': 'error', 'array-callback-return': 'error', 'no-array-constructor': 'error', - // This will be turned on after bug fixes are mostly completed - // 'arrow-body-style': ['warn', 'as-needed'], complexity: 'warn', // This will be turned on after bug fixes are mostly completed // 'consistent-return': 'warn', @@ -691,7 +714,6 @@ module.exports = { 'no-extra-bind': 'error', 'no-extra-boolean-cast': 'error', 'no-extra-label': 'error', - 'no-floating-decimal': 'error', 'no-func-assign': 'error', 'no-implicit-globals': 'error', 'no-implied-eval': 'error', @@ -732,8 +754,6 @@ module.exports = { 'prefer-spread': 'error', // This style will be turned on after most bugs are fixed // 'prefer-template': 'warn', - // This style will be turned on after most bugs are fixed - // quotes: ['warn', 'single', { avoidEscape: true }], 'react/boolean-prop-naming': 'error', 'react/button-has-type': 'error', 'react/forbid-dom-props': 'error', @@ -769,13 +789,10 @@ module.exports = { 'react/jsx-sort-default-props': 'error', // might be introduced after the other warns are fixed // 'react/jsx-sort-props': 'error', - 'react/jsx-tag-spacing': 'error', // might be introduced after the other warns are fixed 'react-hooks/exhaustive-deps': 'off', 'require-atomic-updates': 'error', - 'rest-spread-spacing': ['error', 'never'], 'symbol-description': 'error', - 'template-curly-spacing': 'error', 'vars-on-top': 'error', }, }, @@ -810,8 +827,6 @@ module.exports = { { files: ['x-pack/legacy/plugins/monitoring/**/*.js'], rules: { - 'block-spacing': ['error', 'always'], - curly: ['error', 'all'], 'no-unused-vars': ['error', { args: 'all', argsIgnorePattern: '^_' }], 'no-else-return': 'error', }, @@ -828,7 +843,6 @@ module.exports = { files: ['x-pack/legacy/plugins/canvas/**/*.js'], rules: { radix: 'error', - curly: ['error', 'all'], // module importing 'import/order': [ @@ -846,7 +860,6 @@ module.exports = { 'react/self-closing-comp': 'error', 'react/sort-comp': 'error', 'react/jsx-boolean-value': 'error', - 'react/jsx-wrap-multilines': 'error', 'react/no-unescaped-entities': ['error', { forbid: ['>', '}'] }], 'react/forbid-elements': [ 'error', @@ -923,5 +936,17 @@ module.exports = { 'import/no-default-export': 'error', }, }, + + /** + * Prettier disables all conflicting rules, listing as last override so it takes precedence + */ + { + files: ['**/*'], + rules: { + ...require('eslint-config-prettier').rules, + ...require('eslint-config-prettier/react').rules, + ...require('eslint-config-prettier/@typescript-eslint').rules, + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36a2cda841fa8b..3142e0ff977495 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -80,7 +80,6 @@ /x-pack/plugins/licensing/ @elastic/kibana-platform /packages/kbn-config-schema/ @elastic/kibana-platform /src/legacy/server/config/ @elastic/kibana-platform -/src/legacy/server/csp/ @elastic/kibana-platform /src/legacy/server/http/ @elastic/kibana-platform /src/legacy/server/i18n/ @elastic/kibana-platform /src/legacy/server/logging/ @elastic/kibana-platform @@ -88,12 +87,12 @@ /src/legacy/server/status/ @elastic/kibana-platform # Security +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform /x-pack/legacy/plugins/security/ @elastic/kibana-security /x-pack/legacy/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security -/src/legacy/server/csp/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security diff --git a/.i18nrc.json b/.i18nrc.json index a1c49ae03f3593..9a37ce1310a32e 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -4,7 +4,10 @@ "console": "src/legacy/core_plugins/console", "core": "src/core", "dashboardEmbeddableContainer": "src/plugins/dashboard_embeddable_container", - "data": ["src/legacy/core_plugins/data", "src/plugins/data"], + "data": [ + "src/legacy/core_plugins/data", + "src/plugins/data" + ], "embeddableApi": "src/plugins/embeddable", "share": "src/plugins/share", "esUi": "src/plugins/es_ui_shared", @@ -21,7 +24,7 @@ "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", - "navigation": "src/legacy/core_plugins/navigation", + "navigation": "src/plugins/navigation", "newsfeed": "src/plugins/newsfeed", "regionMap": "src/legacy/core_plugins/region_map", "server": "src/legacy/server", @@ -36,8 +39,13 @@ "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": "src/legacy/core_plugins/vis_type_timeseries", "visTypeVega": "src/legacy/core_plugins/vis_type_vega", - "visualizations": ["src/plugins/visualizations", "src/legacy/core_plugins/visualizations"] + "visualizations": [ + "src/plugins/visualizations", + "src/legacy/core_plugins/visualizations" + ] }, - "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], + "exclude": [ + "src/legacy/ui/ui_render/ui_render_mixin.js" + ], "translations": [] -} +} \ No newline at end of file diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md index 31513bda2e8791..a1544373ee6980 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md @@ -22,9 +22,9 @@ export class MyPlugin implements Plugin { setup({ application }) { application.register({ id: 'my-app', - async mount(context, params) { + async mount(params) { const { renderApp } = await import('./application'); - return renderApp(context, params); + return renderApp(params); }, }); } @@ -38,7 +38,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; -export renderApp = (context, { appBasePath, element }) => { +import { CoreStart, AppMountParams } from 'src/core/public'; +import { MyPluginDepsStart } from './plugin'; + +export renderApp = ({ appBasePath, element }: AppMountParams) => { ReactDOM.render( // pass `appBasePath` to `basename` diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index c599e1eaa14fe9..2c43f36ede09e6 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -89,6 +89,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | | | [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | | | [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | +| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. | +| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | +| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. | +| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | +| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | | [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | 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. | | [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | | | [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | | diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.md new file mode 100644 index 00000000000000..6becc3d5074617 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) + +## SavedObjectsImportConflictError interface + +Represents a failure to import due to a conflict. + +Signature: + +```typescript +export interface SavedObjectsImportConflictError +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [type](./kibana-plugin-public.savedobjectsimportconflicterror.type.md) | 'conflict' | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.type.md new file mode 100644 index 00000000000000..af20cc8fa8df27 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportconflicterror.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) > [type](./kibana-plugin-public.savedobjectsimportconflicterror.type.md) + +## SavedObjectsImportConflictError.type property + +Signature: + +```typescript +type: 'conflict'; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.error.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.error.md new file mode 100644 index 00000000000000..ece6016e8bf542 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.error.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) > [error](./kibana-plugin-public.savedobjectsimporterror.error.md) + +## SavedObjectsImportError.error property + +Signature: + +```typescript +error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.id.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.id.md new file mode 100644 index 00000000000000..995fe61745a006 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) > [id](./kibana-plugin-public.savedobjectsimporterror.id.md) + +## SavedObjectsImportError.id property + +Signature: + +```typescript +id: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.md new file mode 100644 index 00000000000000..dee8bb1c79a57d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) + +## SavedObjectsImportError interface + +Represents a failure to import. + +Signature: + +```typescript +export interface SavedObjectsImportError +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [error](./kibana-plugin-public.savedobjectsimporterror.error.md) | SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError | | +| [id](./kibana-plugin-public.savedobjectsimporterror.id.md) | string | | +| [title](./kibana-plugin-public.savedobjectsimporterror.title.md) | string | | +| [type](./kibana-plugin-public.savedobjectsimporterror.type.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.title.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.title.md new file mode 100644 index 00000000000000..71fa13ad4a5d09 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) > [title](./kibana-plugin-public.savedobjectsimporterror.title.md) + +## SavedObjectsImportError.title property + +Signature: + +```typescript +title?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.type.md new file mode 100644 index 00000000000000..fe98dc928e5f0b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimporterror.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) > [type](./kibana-plugin-public.savedobjectsimporterror.type.md) + +## SavedObjectsImportError.type property + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.blocking.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.blocking.md new file mode 100644 index 00000000000000..76bd6e0939a96a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.blocking.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) > [blocking](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.blocking.md) + +## SavedObjectsImportMissingReferencesError.blocking property + +Signature: + +```typescript +blocking: Array<{ + type: string; + id: string; + }>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.md new file mode 100644 index 00000000000000..58af9e9be0cc5e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) + +## SavedObjectsImportMissingReferencesError interface + +Represents a failure to import due to missing references. + +Signature: + +```typescript +export interface SavedObjectsImportMissingReferencesError +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [blocking](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.blocking.md) | Array<{
type: string;
id: string;
}> | | +| [references](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.references.md) | Array<{
type: string;
id: string;
}> | | +| [type](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.type.md) | 'missing_references' | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.references.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.references.md new file mode 100644 index 00000000000000..f1dc3b454f7ed9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.references.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) > [references](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.references.md) + +## SavedObjectsImportMissingReferencesError.references property + +Signature: + +```typescript +references: Array<{ + type: string; + id: string; + }>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.type.md new file mode 100644 index 00000000000000..340b36248d83e1 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportmissingreferenceserror.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) > [type](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.type.md) + +## SavedObjectsImportMissingReferencesError.type property + +Signature: + +```typescript +type: 'missing_references'; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.errors.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.errors.md new file mode 100644 index 00000000000000..c085fd0f8c3b46 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.errors.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) > [errors](./kibana-plugin-public.savedobjectsimportresponse.errors.md) + +## SavedObjectsImportResponse.errors property + +Signature: + +```typescript +errors?: SavedObjectsImportError[]; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.md new file mode 100644 index 00000000000000..9733f11fd6b8f1 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) + +## SavedObjectsImportResponse interface + +The response describing the result of an import. + +Signature: + +```typescript +export interface SavedObjectsImportResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [errors](./kibana-plugin-public.savedobjectsimportresponse.errors.md) | SavedObjectsImportError[] | | +| [success](./kibana-plugin-public.savedobjectsimportresponse.success.md) | boolean | | +| [successCount](./kibana-plugin-public.savedobjectsimportresponse.successcount.md) | number | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.success.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.success.md new file mode 100644 index 00000000000000..062b8ce3f7c72d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.success.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) > [success](./kibana-plugin-public.savedobjectsimportresponse.success.md) + +## SavedObjectsImportResponse.success property + +Signature: + +```typescript +success: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.successcount.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.successcount.md new file mode 100644 index 00000000000000..c2c93859261758 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportresponse.successcount.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) > [successCount](./kibana-plugin-public.savedobjectsimportresponse.successcount.md) + +## SavedObjectsImportResponse.successCount property + +Signature: + +```typescript +successCount: number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.id.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.id.md new file mode 100644 index 00000000000000..2569152f17b156 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) > [id](./kibana-plugin-public.savedobjectsimportretry.id.md) + +## SavedObjectsImportRetry.id property + +Signature: + +```typescript +id: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.md new file mode 100644 index 00000000000000..e2cad52f92f2da --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) + +## SavedObjectsImportRetry interface + +Describes a retry operation for importing a saved object. + +Signature: + +```typescript +export interface SavedObjectsImportRetry +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [id](./kibana-plugin-public.savedobjectsimportretry.id.md) | string | | +| [overwrite](./kibana-plugin-public.savedobjectsimportretry.overwrite.md) | boolean | | +| [replaceReferences](./kibana-plugin-public.savedobjectsimportretry.replacereferences.md) | Array<{
type: string;
from: string;
to: string;
}> | | +| [type](./kibana-plugin-public.savedobjectsimportretry.type.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.overwrite.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.overwrite.md new file mode 100644 index 00000000000000..9d1f96b2fcfcfc --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.overwrite.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) > [overwrite](./kibana-plugin-public.savedobjectsimportretry.overwrite.md) + +## SavedObjectsImportRetry.overwrite property + +Signature: + +```typescript +overwrite: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.replacereferences.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.replacereferences.md new file mode 100644 index 00000000000000..fe587ef8134cc5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.replacereferences.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) > [replaceReferences](./kibana-plugin-public.savedobjectsimportretry.replacereferences.md) + +## SavedObjectsImportRetry.replaceReferences property + +Signature: + +```typescript +replaceReferences: Array<{ + type: string; + from: string; + to: string; + }>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.type.md new file mode 100644 index 00000000000000..b84dac102483ae --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportretry.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) > [type](./kibana-plugin-public.savedobjectsimportretry.type.md) + +## SavedObjectsImportRetry.type property + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.md new file mode 100644 index 00000000000000..e6837571717879 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) + +## SavedObjectsImportUnknownError interface + +Represents a failure to import due to an unknown reason. + +Signature: + +```typescript +export interface SavedObjectsImportUnknownError +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-public.savedobjectsimportunknownerror.message.md) | string | | +| [statusCode](./kibana-plugin-public.savedobjectsimportunknownerror.statuscode.md) | number | | +| [type](./kibana-plugin-public.savedobjectsimportunknownerror.type.md) | 'unknown' | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.message.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.message.md new file mode 100644 index 00000000000000..976c2817bda0a2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) > [message](./kibana-plugin-public.savedobjectsimportunknownerror.message.md) + +## SavedObjectsImportUnknownError.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.statuscode.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.statuscode.md new file mode 100644 index 00000000000000..6c7255dd4b6313 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) > [statusCode](./kibana-plugin-public.savedobjectsimportunknownerror.statuscode.md) + +## SavedObjectsImportUnknownError.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.type.md new file mode 100644 index 00000000000000..2ef764d68322e6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunknownerror.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) > [type](./kibana-plugin-public.savedobjectsimportunknownerror.type.md) + +## SavedObjectsImportUnknownError.type property + +Signature: + +```typescript +type: 'unknown'; +``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md new file mode 100644 index 00000000000000..09ae53c0313528 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) + +## SavedObjectsImportUnsupportedTypeError interface + +Represents a failure to import due to having an unsupported saved object type. + +Signature: + +```typescript +export interface SavedObjectsImportUnsupportedTypeError +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [type](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.type.md) | 'unsupported_type' | | + diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.type.md b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.type.md new file mode 100644 index 00000000000000..55ddf15058faba --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsimportunsupportedtypeerror.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) > [type](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.type.md) + +## SavedObjectsImportUnsupportedTypeError.type property + +Signature: + +```typescript +type: 'unsupported_type'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md b/docs/development/core/server/kibana-plugin-server.cspconfig.default.md new file mode 100644 index 00000000000000..56e6cf35cdd136 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.default.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [DEFAULT](./kibana-plugin-server.cspconfig.default.md) + +## CspConfig.DEFAULT property + +Signature: + +```typescript +static readonly DEFAULT: CspConfig; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md new file mode 100644 index 00000000000000..e3a3d5d712a420 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [header](./kibana-plugin-server.cspconfig.header.md) + +## CspConfig.header property + +Signature: + +```typescript +readonly header: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md new file mode 100644 index 00000000000000..e5276991be404f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) + +## CspConfig class + +CSP configuration for use in Kibana. + +Signature: + +```typescript +export declare class CspConfig implements ICspConfig +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [DEFAULT](./kibana-plugin-server.cspconfig.default.md) | static | CspConfig | | +| [header](./kibana-plugin-server.cspconfig.header.md) | | string | | +| [rules](./kibana-plugin-server.cspconfig.rules.md) | | string[] | | +| [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | +| [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | + +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md new file mode 100644 index 00000000000000..c5270c2375dc17 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rules](./kibana-plugin-server.cspconfig.rules.md) + +## CspConfig.rules property + +Signature: + +```typescript +readonly rules: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md new file mode 100644 index 00000000000000..3ac48edd374c95 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [strict](./kibana-plugin-server.cspconfig.strict.md) + +## CspConfig.strict property + +Signature: + +```typescript +readonly strict: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md new file mode 100644 index 00000000000000..59d661593d9402 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) + +## CspConfig.warnLegacyBrowsers property + +Signature: + +```typescript +readonly warnLegacyBrowsers: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md new file mode 100644 index 00000000000000..7bf83305613eaf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [csp](./kibana-plugin-server.httpservicesetup.csp.md) + +## HttpServiceSetup.csp property + +The CSP config used for Kibana. + +Signature: + +```typescript +csp: ICspConfig; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 25eebf1c06d010..99d4caf40c0d34 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -19,6 +19,7 @@ export interface HttpServiceSetup | [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | | [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | | [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | +| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | | [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.header.md b/docs/development/core/server/kibana-plugin-server.icspconfig.header.md new file mode 100644 index 00000000000000..d757863fdc12da --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.header.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [header](./kibana-plugin-server.icspconfig.header.md) + +## ICspConfig.header property + +The CSP rules in a formatted directives string for use in a `Content-Security-Policy` header. + +Signature: + +```typescript +readonly header: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.md b/docs/development/core/server/kibana-plugin-server.icspconfig.md new file mode 100644 index 00000000000000..fb8188386a3766 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) + +## ICspConfig interface + +CSP configuration for use in Kibana. + +Signature: + +```typescript +export interface ICspConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [header](./kibana-plugin-server.icspconfig.header.md) | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | +| [rules](./kibana-plugin-server.icspconfig.rules.md) | string[] | The CSP rules used for Kibana. | +| [strict](./kibana-plugin-server.icspconfig.strict.md) | boolean | Specify whether browsers that do not support CSP should be able to use Kibana. Use true to block and false to allow. | +| [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) | boolean | Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. | + diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.rules.md b/docs/development/core/server/kibana-plugin-server.icspconfig.rules.md new file mode 100644 index 00000000000000..6216e6d8171360 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.rules.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [rules](./kibana-plugin-server.icspconfig.rules.md) + +## ICspConfig.rules property + +The CSP rules used for Kibana. + +Signature: + +```typescript +readonly rules: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md new file mode 100644 index 00000000000000..4ab97ad9f665a6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [strict](./kibana-plugin-server.icspconfig.strict.md) + +## ICspConfig.strict property + +Specify whether browsers that do not support CSP should be able to use Kibana. Use `true` to block and `false` to allow. + +Signature: + +```typescript +readonly strict: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md new file mode 100644 index 00000000000000..aea35f05694483 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) + +## ICspConfig.warnLegacyBrowsers property + +Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. + +Signature: + +```typescript +readonly warnLegacyBrowsers: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.logger.md b/docs/development/core/server/kibana-plugin-server.logger.md index 96f327a7984850..068f51f409f093 100644 --- a/docs/development/core/server/kibana-plugin-server.logger.md +++ b/docs/development/core/server/kibana-plugin-server.logger.md @@ -19,6 +19,7 @@ export interface Logger | [debug(message, meta)](./kibana-plugin-server.logger.debug.md) | Log messages useful for debugging and interactive investigation | | [error(errorOrMessage, meta)](./kibana-plugin-server.logger.error.md) | Logs abnormal or unexpected errors or messages that caused a failure in the application flow | | [fatal(errorOrMessage, meta)](./kibana-plugin-server.logger.fatal.md) | Logs abnormal or unexpected errors or messages that caused an unrecoverable failure | +| [get(childContextPaths)](./kibana-plugin-server.logger.get.md) | Returns a new [Logger](./kibana-plugin-server.logger.md) instance extending the current logger context. | | [info(message, meta)](./kibana-plugin-server.logger.info.md) | Logs messages related to general application flow | | [trace(message, meta)](./kibana-plugin-server.logger.trace.md) | Log messages at the most detailed log level | | [warn(errorOrMessage, meta)](./kibana-plugin-server.logger.warn.md) | Logs abnormal or unexpected errors or messages | diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 06dcede0f2dfe7..e97ecbcfaf7396 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -18,6 +18,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | | [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | @@ -64,6 +65,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | | [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index a2c05e4d873250..d6dd4378da1b7c 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -50,15 +50,17 @@ this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). `xpack.security.session.idleTimeout`:: -Sets the session duration (in milliseconds). By default, sessions stay active -until the browser is closed. When this is set to an explicit idle timeout, closing -the browser still requires the user to log back in to {kib}. +Sets the session duration. The format is a string of `[ms|s|m|h|d|w|M|Y]` +(e.g. '70ms', '5s', '3d', '1Y'). By default, sessions stay active until the +browser is closed. When this is set to an explicit idle timeout, closing the +browser still requires the user to log back in to {kib}. `xpack.security.session.lifespan`:: -Sets the maximum duration (in milliseconds), also known as "absolute timeout". By -default, a session can be renewed indefinitely. When this value is set, a session -will end once its lifespan is exceeded, even if the user is not idle. NOTE: if -`idleTimeout` is not set, this setting will still cause sessions to expire. +Sets the maximum duration, also known as "absolute timeout". The format is a +string of `[ms|s|m|h|d|w|M|Y]` (e.g. '70ms', '5s', '3d', '1Y'). By default, +a session can be renewed indefinitely. When this value is set, a session will end +once its lifespan is exceeded, even if the user is not idle. NOTE: if `idleTimeout` +is not set, this setting will still cause sessions to expire. `xpack.security.loginAssistanceMessage`:: Adds a message to the login screen. Useful for displaying information about maintenance windows, links to corporate sign up pages etc. diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index 60f5473f43b9df..a68a2ee285ee38 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -59,13 +59,14 @@ For more information, see <>. . Optional: Set a timeout to expire idle sessions. By default, a session stays active until the browser is closed. To define a sliding session expiration, set the `xpack.security.session.idleTimeout` property in the `kibana.yml` -configuration file. The idle timeout is specified in milliseconds. For example, -set the idle timeout to 600000 to expire idle sessions after 10 minutes: +configuration file. The idle timeout is formatted as a duration of +`[ms|s|m|h|d|w|M|Y]` (e.g. '70ms', '5s', '3d', '1Y'). For example, set +the idle timeout to expire idle sessions after 10 minutes: + -- [source,yaml] -------------------------------------------------------------------------------- -xpack.security.session.idleTimeout: 600000 +xpack.security.session.idleTimeout: "10m" -------------------------------------------------------------------------------- -- @@ -74,13 +75,14 @@ the "absolute timeout". By default, a session stays active until the browser is closed. If an idle timeout is defined, a session can still be extended indefinitely. To define a maximum session lifespan, set the `xpack.security.session.lifespan` property in the `kibana.yml` configuration -file. The lifespan is specified in milliseconds. For example, set the lifespan -to 28800000 to expire sessions after 8 hours: +file. The lifespan is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` +(e.g. '70ms', '5s', '3d', '1Y'). For example, set the lifespan to expire +sessions after 8 hours: + -- [source,yaml] -------------------------------------------------------------------------------- -xpack.security.session.lifespan: 28800000 +xpack.security.session.lifespan: "8h" -------------------------------------------------------------------------------- -- diff --git a/package.json b/package.json index 393b70e6c2087d..636580b905880d 100644 --- a/package.json +++ b/package.json @@ -110,8 +110,8 @@ ] }, "dependencies": { - "@babel/core": "^7.5.5", - "@babel/register": "^7.7.0", + "@babel/core": "^7.7.5", + "@babel/register": "^7.7.4", "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", @@ -156,7 +156,7 @@ "color": "1.0.3", "commander": "3.0.2", "compare-versions": "3.5.1", - "core-js": "^3.2.1", + "core-js": "^3.5.0", "css-loader": "2.1.1", "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", @@ -172,7 +172,7 @@ "fast-deep-equal": "^3.1.1", "file-loader": "4.2.0", "font-awesome": "4.7.0", - "getos": "^3.1.0", + "getos": "^3.1.1", "glob": "^7.1.2", "glob-all": "^3.1.0", "globby": "^8.0.1", @@ -219,7 +219,7 @@ "postcss-loader": "3.0.0", "prop-types": "15.6.0", "proxy-from-env": "1.0.0", - "pug": "^2.0.3", + "pug": "^2.0.4", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", "react": "^16.12.0", @@ -275,9 +275,9 @@ "yauzl": "2.10.0" }, "devDependencies": { - "@babel/parser": "^7.5.5", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/types": "^7.5.5", + "@babel/parser": "^7.7.5", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/types": "^7.7.4", "@elastic/elasticsearch": "^7.4.0", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", @@ -298,7 +298,7 @@ "@testing-library/react-hooks": "^3.2.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", - "@types/babel__core": "^7.1.2", + "@types/babel__core": "^7.1.3", "@types/bluebird": "^3.1.1", "@types/boom": "^7.2.0", "@types/chance": "^1.0.0", @@ -310,7 +310,7 @@ "@types/delete-empty": "^2.0.0", "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", - "@types/eslint": "^6.1.2", + "@types/eslint": "^6.1.3", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -385,25 +385,25 @@ "enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-utils": "^1.12.1", "enzyme-to-json": "^3.4.3", - "eslint": "^6.5.1", - "eslint-config-prettier": "^6.4.0", + "eslint": "^6.7.2", + "eslint-config-prettier": "^6.7.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.3.0", - "eslint-plugin-cypress": "^2.7.0", - "eslint-plugin-import": "^2.18.2", + "eslint-plugin-cypress": "^2.8.0", + "eslint-plugin-import": "^2.19.1", "eslint-plugin-jest": "^22.19.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-mocha": "^6.2.0", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-node": "^10.0.0", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-prettier": "^3.1.1", + "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.16.0", "eslint-plugin-react-hooks": "^2.1.2", "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", - "geckodriver": "^1.19.0", + "geckodriver": "^1.19.1", "getopts": "^2.2.4", "grunt": "1.0.4", "grunt-available-tasks": "^0.6.3", @@ -422,7 +422,7 @@ "jest": "^24.9.0", "jest-cli": "^24.9.0", "jest-raw-loader": "^1.0.1", - "jimp": "0.8.4", + "jimp": "0.9.3", "json5": "^1.0.1", "karma": "3.1.4", "karma-chrome-launcher": "2.2.0", @@ -453,7 +453,7 @@ "regenerate": "^1.4.0", "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.5", - "simple-git": "1.116.0", + "simple-git": "1.129.0", "sinon": "^7.4.2", "strip-ansi": "^3.0.1", "supertest": "^3.1.0", diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index 57873d28d372de..e41744311e3be6 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -11,8 +11,8 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/preset-env": "^7.5.5", + "@babel/cli": "^7.7.5", + "@babel/preset-env": "^7.7.6", "babel-plugin-add-module-exports": "^1.0.2", "moment": "^2.24.0" }, diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 2700770f1b8d4d..624ee4679a3b91 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -19,44 +19,40 @@ module.exports = { es6: true, }, - rules: Object.assign( - { - 'prettier/prettier': [ - 'error', + rules: { + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + + '@kbn/eslint/module_migration': [ + 'error', + [ { - endOfLine: 'auto', + from: 'expect.js', + to: '@kbn/expect', + }, + { + from: 'mkdirp', + to: false, + disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` + }, + { + from: '@kbn/elastic-idx', + to: false, + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + }, + { + from: 'x-pack', + toRelative: 'x-pack', + }, + { + from: 'react-router', + to: 'react-router-dom', }, ], - - '@kbn/eslint/module_migration': [ - 'error', - [ - { - from: 'expect.js', - to: '@kbn/expect', - }, - { - from: 'mkdirp', - to: false, - disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` - }, - { - from: '@kbn/elastic-idx', - to: false, - disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` - }, - { - from: 'x-pack', - toRelative: 'x-pack', - }, - { - from: 'react-router', - to: 'react-router-dom', - }, - ], - ], - }, - require('eslint-config-prettier').rules, - require('eslint-config-prettier/react').rules - ) + ], + }, }; diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js index 5ecc07ab5671b4..cd69b1ccb8226a 100644 --- a/packages/eslint-config-kibana/javascript.js +++ b/packages/eslint-config-kibana/javascript.js @@ -42,8 +42,6 @@ module.exports = { 'block-scoped-var': 'error', camelcase: [ 'error', { properties: 'never', allow: ['^UNSAFE_'] } ], 'consistent-return': 'off', - curly: [ 'error', 'multi-line' ], - 'dot-location': [ 'error', 'property' ], 'dot-notation': [ 'error', { allowKeywords: true } ], eqeqeq: [ 'error', 'allow-null' ], 'guard-for-in': 'error', @@ -86,7 +84,6 @@ module.exports = { 'prefer-const': 'error', strict: [ 'error', 'never' ], 'valid-typeof': 'error', - 'eol-last': ['error', 'always'], yoda: 'off', 'mocha/handle-done-callback': 'error', diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 7917297883b033..2e72066fcbc68d 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -18,11 +18,11 @@ "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", "babel-eslint": "^10.0.3", - "eslint": "^6.5.1", + "eslint": "^6.7.2", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.3.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-import": "^2.18.2", + "eslint-plugin-import": "^2.19.1", "eslint-plugin-jest": "^22.19.0", "eslint-plugin-mocha": "^6.2.0", "eslint-plugin-no-unsanitized": "^3.0.2", diff --git a/packages/eslint-config-kibana/react.js b/packages/eslint-config-kibana/react.js index 163bd6ca73a07b..f0e05db4d3c606 100644 --- a/packages/eslint-config-kibana/react.js +++ b/packages/eslint-config-kibana/react.js @@ -21,27 +21,14 @@ module.exports = { }, rules: { - 'jsx-quotes': ['error', 'prefer-double'], 'react/jsx-uses-react': 'error', 'react/react-in-jsx-scope': 'error', 'react/jsx-uses-vars': 'error', 'react/jsx-no-undef': 'error', 'react/jsx-pascal-case': 'error', - 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], - 'react/jsx-closing-tag-location': 'error', - 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }], - 'react/jsx-indent-props': ['error', 2], - 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], 'react/no-danger': 'error', 'react/self-closing-comp': 'error', - 'react/jsx-wrap-multilines': ['error', { - declaration: true, - assignment: true, - return: true, - arrow: true, - }], - 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 'react-hooks/exhaustive-deps': 'error', // Checks effect dependencies 'jsx-a11y/accessible-emoji': 'error', @@ -71,8 +58,6 @@ module.exports = { 'jsx-a11y/role-supports-aria-props': 'error', 'jsx-a11y/scope': 'error', 'jsx-a11y/tabindex-no-positive': 'error', - 'react/jsx-equals-spacing': ['error', 'never'], - 'react/jsx-indent': ['error', 2], 'react/no-will-update-set-state': 'error', 'react/no-is-mounted': 'error', 'react/no-multi-comp': ['error', { ignoreStateless: true }], diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index ca3b9bf99c5352..a55ca9391011d3 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -107,8 +107,6 @@ module.exports = { } } ], - 'indent': 'off', - '@typescript-eslint/indent': [ 'error', 2, { SwitchCase: 1 } ], '@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/member-ordering': ['error', { @@ -125,15 +123,9 @@ module.exports = { lib: 'never' }], '@typescript-eslint/no-var-requires': 'error', - '@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/unified-signatures': 'error', - 'arrow-body-style': 'error', - 'arrow-parens': 'error', - 'comma-dangle': ['error', 'always-multiline'], 'constructor-super': 'error', - 'curly': 'error', 'dot-notation': 'error', - 'eol-last': 'error', 'eqeqeq': ['error', 'always', {'null': 'ignore'}], 'guard-for-in': 'error', 'import/order': ['error', { @@ -144,8 +136,6 @@ module.exports = { ], }], 'max-classes-per-file': ['error', 1], - 'max-len': [ 'error', { code: 120, ignoreComments: true, ignoreUrls: true } ], - 'new-parens': 'error', 'no-bitwise': 'error', 'no-caller': 'error', 'no-cond-assign': 'error', @@ -154,30 +144,19 @@ module.exports = { 'no-empty': 'error', 'no-extend-native': 'error', 'no-eval': 'error', - 'no-multiple-empty-lines': 'error', 'no-new-wrappers': 'error', 'no-shadow': 'error', 'no-throw-literal': 'error', - 'no-trailing-spaces': 'error', 'no-undef-init': 'error', 'no-unsafe-finally': 'error', 'no-unused-expressions': 'error', 'no-unused-labels': 'error', 'no-var': 'error', - 'object-curly-spacing': 'error', 'object-shorthand': 'error', 'one-var': [ 'error', 'never' ], 'prefer-const': 'error', 'prefer-rest-params': 'error', - 'quotes': ['error', 'double', { 'avoidEscape': true }], - 'quote-props': ['error', 'consistent-as-needed'], 'radix': 'error', - 'semi': 'error', - 'space-before-function-paren': ['error', { - 'anonymous': 'never', - 'named': 'never', - 'asyncArrow': 'always' - }], 'spaced-comment': ["error", "always", { "exceptions": ["/"] }], diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index 9eefa16aaca017..73f19690d4c7b4 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -14,7 +14,7 @@ "kbn:watch": "node scripts/build --source-maps --watch" }, "devDependencies": { - "@babel/cli": "^7.5.5", + "@babel/cli": "^7.7.5", "@kbn/dev-utils": "1.0.0", "@kbn/babel-preset": "1.0.0", "typescript": "3.7.2" diff --git a/packages/kbn-analytics/src/index.ts b/packages/kbn-analytics/src/index.ts index 6514347b0b1272..c7a1350841168e 100644 --- a/packages/kbn-analytics/src/index.ts +++ b/packages/kbn-analytics/src/index.ts @@ -20,3 +20,4 @@ export { ReportHTTP, Reporter, ReporterConfig } from './reporter'; export { UiStatsMetricType, METRIC_TYPE } from './metrics'; export { Report, ReportManager } from './report'; +export { Storage } from './storage'; diff --git a/packages/kbn-analytics/src/report.ts b/packages/kbn-analytics/src/report.ts index 333bc05d28f9be..1c0b37966355fb 100644 --- a/packages/kbn-analytics/src/report.ts +++ b/packages/kbn-analytics/src/report.ts @@ -23,23 +23,25 @@ const REPORT_VERSION = 1; export interface Report { reportVersion: typeof REPORT_VERSION; - uiStatsMetrics: { - [key: string]: { + uiStatsMetrics?: Record< + string, + { key: string; appName: string; eventName: string; type: UiStatsMetricType; stats: Stats; - }; - }; - userAgent?: { - [key: string]: { + } + >; + userAgent?: Record< + string, + { userAgent: string; key: string; type: METRIC_TYPE.USER_AGENT; appName: string; - }; - }; + } + >; } export class ReportManager { @@ -49,14 +51,15 @@ export class ReportManager { this.report = report || ReportManager.createReport(); } static createReport(): Report { - return { reportVersion: REPORT_VERSION, uiStatsMetrics: {} }; + return { reportVersion: REPORT_VERSION }; } public clearReport() { this.report = ReportManager.createReport(); } public isReportEmpty(): boolean { - const noUiStats = Object.keys(this.report.uiStatsMetrics).length === 0; - const noUserAgent = !this.report.userAgent || Object.keys(this.report.userAgent).length === 0; + const { uiStatsMetrics, userAgent } = this.report; + const noUiStats = !uiStatsMetrics || Object.keys(uiStatsMetrics).length === 0; + const noUserAgent = !userAgent || Object.keys(userAgent).length === 0; return noUiStats && noUserAgent; } private incrementStats(count: number, stats?: Stats): Stats { @@ -113,14 +116,17 @@ export class ReportManager { case METRIC_TYPE.LOADED: case METRIC_TYPE.COUNT: { const { appName, type, eventName, count } = metric; - const existingStats = (report.uiStatsMetrics[key] || {}).stats; - this.report.uiStatsMetrics[key] = { - key, - appName, - eventName, - type, - stats: this.incrementStats(count, existingStats), - }; + if (report.uiStatsMetrics) { + const existingStats = (report.uiStatsMetrics[key] || {}).stats; + this.report.uiStatsMetrics = this.report.uiStatsMetrics || {}; + this.report.uiStatsMetrics[key] = { + key, + appName, + eventName, + type, + stats: this.incrementStats(count, existingStats), + }; + } return; } default: diff --git a/packages/kbn-analytics/src/storage.ts b/packages/kbn-analytics/src/storage.ts index 9abf9fa7dac2ce..5c18d9280ffc75 100644 --- a/packages/kbn-analytics/src/storage.ts +++ b/packages/kbn-analytics/src/storage.ts @@ -19,7 +19,13 @@ import { Report } from './report'; -export type Storage = Map; +export interface Storage { + get: (key: string) => T | null; + set: (key: string, value: T) => S; + remove: (key: string) => T | null; + clear: () => void; +} + export class ReportStorageManager { storageKey: string; private storage?: Storage; diff --git a/packages/kbn-babel-code-parser/package.json b/packages/kbn-babel-code-parser/package.json index a9d373d33ab38e..8c7d51da791a5f 100755 --- a/packages/kbn-babel-code-parser/package.json +++ b/packages/kbn-babel-code-parser/package.json @@ -15,12 +15,12 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.5.5" + "@babel/cli": "^7.7.5" }, "dependencies": { "@kbn/babel-preset": "1.0.0", - "@babel/parser": "^7.5.5", - "@babel/traverse": "^7.5.5", + "@babel/parser": "^7.7.5", + "@babel/traverse": "^7.7.4", "lodash": "^4.17.15" } } diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index c8bb4a568c5003..7c461fa146e8a2 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -4,18 +4,18 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.5.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", - "@babel/plugin-proposal-optional-chaining": "^7.6.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-transform-modules-commonjs": "^7.5.0", - "@babel/preset-env": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.7.4", + "@babel/plugin-proposal-optional-chaining": "^7.7.5", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/plugin-transform-modules-commonjs": "^7.7.5", + "@babel/preset-env": "^7.7.6", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", "babel-plugin-add-module-exports": "^1.0.2", - "babel-plugin-filter-imports": "^3.0.0", + "babel-plugin-filter-imports": "^4.0.0", "babel-plugin-styled-components": "^1.10.6", - "babel-plugin-transform-define": "^1.3.1", + "babel-plugin-transform-define": "^2.0.0", "babel-plugin-typescript-strip-namespaces": "^1.1.1" } } diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index cb501dab3ddb75..ccc396b5522e29 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -15,7 +15,7 @@ "getopts": "^2.2.4", "glob": "^7.1.2", "node-fetch": "^2.6.0", - "simple-git": "^1.91.0", + "simple-git": "^1.129.0", "tar-fs": "^1.16.3", "tree-kill": "^1.2.1", "yauzl": "^2.10.0" diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index badcf13187cafb..70d6b0f0174703 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -4,12 +4,12 @@ "private": true, "license": "Apache-2.0", "peerDependencies": { - "eslint": "6.5.1", + "eslint": "6.7.2", "babel-eslint": "^10.0.3" }, "dependencies": { "micromatch": "3.1.10", "dedent": "^0.7.0", - "eslint-module-utils": "2.4.1" + "eslint-module-utils": "2.5.0" } } diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index bbc5126da1dce0..a3f5009f30dba6 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -12,8 +12,8 @@ "kbn:watch": "node scripts/build --watch --source-maps" }, "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/core": "^7.5.5", + "@babel/cli": "^7.7.5", + "@babel/core": "^7.7.5", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index 27ef70d8718569..2e80276f59f91e 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -9,16 +9,16 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@babel/runtime": "^7.5.5", + "@babel/runtime": "^7.7.6", "@kbn/i18n": "1.0.0", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "lodash.clone": "^4.5.0", "uuid": "3.3.2" }, "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/core": "^7.5.5", - "@babel/plugin-transform-runtime": "^7.5.5", + "@babel/cli": "^7.7.5", + "@babel/core": "^7.7.5", + "@babel/plugin-transform-runtime": "^7.7.6", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "babel-loader": "^8.0.6", diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 68af0aa791c8e0..00f784bc2e18b1 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -13,7 +13,7 @@ "@kbn/babel-preset": "1.0.0" }, "dependencies": { - "@babel/core": "^7.5.5", + "@babel/core": "^7.7.5", "argv-split": "^2.0.1", "commander": "^3.0.0", "del": "^5.1.0", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index aea85c13d7f325..91a0159d736604 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8023,7 +8023,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(120); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -19088,7 +19088,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -23296,11 +23296,10 @@ const CleanCommand = { const originalCwd = process.cwd(); try { - for (const _ref of toDelete) { - const { - pattern, - cwd - } = _ref; + for (const { + pattern, + cwd + } of toDelete) { process.chdir(cwd); const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); @@ -45553,7 +45552,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /* 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__(692); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(697); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -45733,8 +45732,8 @@ const EventEmitter = __webpack_require__(46); const path = __webpack_require__(16); const arrify = __webpack_require__(487); const globby = __webpack_require__(488); -const cpFile = __webpack_require__(682); -const CpyError = __webpack_require__(690); +const cpFile = __webpack_require__(687); +const CpyError = __webpack_require__(695); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -45863,8 +45862,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(489); const glob = __webpack_require__(37); const fastGlob = __webpack_require__(491); -const dirGlob = __webpack_require__(675); -const gitignore = __webpack_require__(678); +const dirGlob = __webpack_require__(680); +const gitignore = __webpack_require__(683); const DEFAULT_FILTER = () => false; @@ -46115,11 +46114,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(493); var taskManager = __webpack_require__(494); -var reader_async_1 = __webpack_require__(646); -var reader_stream_1 = __webpack_require__(670); -var reader_sync_1 = __webpack_require__(671); -var arrayUtils = __webpack_require__(673); -var streamUtils = __webpack_require__(674); +var reader_async_1 = __webpack_require__(651); +var reader_stream_1 = __webpack_require__(675); +var reader_sync_1 = __webpack_require__(676); +var arrayUtils = __webpack_require__(678); +var streamUtils = __webpack_require__(679); /** * Synchronous API. */ @@ -46759,9 +46758,9 @@ var extend = __webpack_require__(612); */ var compilers = __webpack_require__(615); -var parsers = __webpack_require__(642); -var cache = __webpack_require__(643); -var utils = __webpack_require__(644); +var parsers = __webpack_require__(647); +var cache = __webpack_require__(648); +var utils = __webpack_require__(649); var MAX_LENGTH = 1024 * 64; /** @@ -65300,9 +65299,9 @@ var toRegex = __webpack_require__(502); */ var compilers = __webpack_require__(632); -var parsers = __webpack_require__(638); -var Extglob = __webpack_require__(641); -var utils = __webpack_require__(640); +var parsers = __webpack_require__(643); +var Extglob = __webpack_require__(646); +var utils = __webpack_require__(645); var MAX_LENGTH = 1024 * 64; /** @@ -65812,7 +65811,7 @@ var parsers = __webpack_require__(636); * Module dependencies */ -var debug = __webpack_require__(574)('expand-brackets'); +var debug = __webpack_require__(638)('expand-brackets'); var extend = __webpack_require__(511); var Snapdragon = __webpack_require__(541); var toRegex = __webpack_require__(502); @@ -66406,12 +66405,839 @@ exports.createRegex = function(pattern, include) { /* 638 */ /***/ (function(module, exports, __webpack_require__) { +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(639); +} else { + module.exports = __webpack_require__(642); +} + + +/***/ }), +/* 639 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(640); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 640 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(641); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 641 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 642 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(579); +var util = __webpack_require__(29); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(640); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(580); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 643 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; var brackets = __webpack_require__(633); -var define = __webpack_require__(639); -var utils = __webpack_require__(640); +var define = __webpack_require__(644); +var utils = __webpack_require__(645); /** * Characters to use in text regex (we want to "not" match @@ -66566,7 +67392,7 @@ module.exports = parsers; /***/ }), -/* 639 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66604,7 +67430,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 640 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66680,7 +67506,7 @@ utils.createRegex = function(str) { /***/ }), -/* 641 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66691,7 +67517,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(541); -var define = __webpack_require__(639); +var define = __webpack_require__(644); var extend = __webpack_require__(511); /** @@ -66699,7 +67525,7 @@ var extend = __webpack_require__(511); */ var compilers = __webpack_require__(632); -var parsers = __webpack_require__(638); +var parsers = __webpack_require__(643); /** * Customize Snapdragon parser and renderer @@ -66765,7 +67591,7 @@ module.exports = Extglob; /***/ }), -/* 642 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66855,14 +67681,14 @@ function textRegex(pattern) { /***/ }), -/* 643 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(624))(); /***/ }), -/* 644 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66880,7 +67706,7 @@ utils.define = __webpack_require__(611); utils.diff = __webpack_require__(628); utils.extend = __webpack_require__(612); utils.pick = __webpack_require__(629); -utils.typeOf = __webpack_require__(645); +utils.typeOf = __webpack_require__(650); utils.unique = __webpack_require__(514); /** @@ -67178,7 +68004,7 @@ utils.unixify = function(options) { /***/ }), -/* 645 */ +/* 650 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -67313,7 +68139,7 @@ function isBuffer(val) { /***/ }), -/* 646 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67332,9 +68158,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_stream_1 = __webpack_require__(664); +var readdir = __webpack_require__(652); +var reader_1 = __webpack_require__(665); +var fs_stream_1 = __webpack_require__(669); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -67395,15 +68221,15 @@ exports.default = ReaderAsync; /***/ }), -/* 647 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(648); -const readdirAsync = __webpack_require__(656); -const readdirStream = __webpack_require__(659); +const readdirSync = __webpack_require__(653); +const readdirAsync = __webpack_require__(661); +const readdirStream = __webpack_require__(664); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -67487,7 +68313,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 648 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67495,11 +68321,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(649); +const DirectoryReader = __webpack_require__(654); let syncFacade = { - fs: __webpack_require__(654), - forEach: __webpack_require__(655), + fs: __webpack_require__(659), + forEach: __webpack_require__(660), sync: true }; @@ -67528,7 +68354,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 649 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67537,9 +68363,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__(650); -const stat = __webpack_require__(652); -const call = __webpack_require__(653); +const normalizeOptions = __webpack_require__(655); +const stat = __webpack_require__(657); +const call = __webpack_require__(658); /** * Asynchronously reads the contents of a directory and streams the results @@ -67915,14 +68741,14 @@ module.exports = DirectoryReader; /***/ }), -/* 650 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(651); +const globToRegExp = __webpack_require__(656); module.exports = normalizeOptions; @@ -68099,7 +68925,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 651 */ +/* 656 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -68236,13 +69062,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 652 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(653); +const call = __webpack_require__(658); module.exports = stat; @@ -68317,7 +69143,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 653 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68378,14 +69204,14 @@ function callOnce (fn) { /***/ }), -/* 654 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(653); +const call = __webpack_require__(658); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -68449,7 +69275,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 655 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68478,7 +69304,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 656 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68486,12 +69312,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(657); -const DirectoryReader = __webpack_require__(649); +const maybe = __webpack_require__(662); +const DirectoryReader = __webpack_require__(654); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(658), + forEach: __webpack_require__(663), async: true }; @@ -68533,7 +69359,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 657 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68560,7 +69386,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 658 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68596,7 +69422,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 659 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68604,11 +69430,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(649); +const DirectoryReader = __webpack_require__(654); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(658), + forEach: __webpack_require__(663), async: true }; @@ -68628,16 +69454,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 660 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(661); -var entry_1 = __webpack_require__(663); -var pathUtil = __webpack_require__(662); +var deep_1 = __webpack_require__(666); +var entry_1 = __webpack_require__(668); +var pathUtil = __webpack_require__(667); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -68703,13 +69529,13 @@ exports.default = Reader; /***/ }), -/* 661 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(662); +var pathUtils = __webpack_require__(667); var patternUtils = __webpack_require__(495); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -68793,7 +69619,7 @@ exports.default = DeepFilter; /***/ }), -/* 662 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68824,13 +69650,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 663 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(662); +var pathUtils = __webpack_require__(667); var patternUtils = __webpack_require__(495); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -68916,7 +69742,7 @@ exports.default = EntryFilter; /***/ }), -/* 664 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68936,8 +69762,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var fsStat = __webpack_require__(665); -var fs_1 = __webpack_require__(669); +var fsStat = __webpack_require__(670); +var fs_1 = __webpack_require__(674); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -68987,14 +69813,14 @@ exports.default = FileSystemStream; /***/ }), -/* 665 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(666); -const statProvider = __webpack_require__(668); +const optionsManager = __webpack_require__(671); +const statProvider = __webpack_require__(673); /** * Asynchronous API. */ @@ -69025,13 +69851,13 @@ exports.statSync = statSync; /***/ }), -/* 666 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(667); +const fsAdapter = __webpack_require__(672); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -69044,7 +69870,7 @@ exports.prepare = prepare; /***/ }), -/* 667 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69067,7 +69893,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 668 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69119,7 +69945,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 669 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69150,7 +69976,7 @@ exports.default = FileSystem; /***/ }), -/* 670 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69170,9 +69996,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_stream_1 = __webpack_require__(664); +var readdir = __webpack_require__(652); +var reader_1 = __webpack_require__(665); +var fs_stream_1 = __webpack_require__(669); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -69240,7 +70066,7 @@ exports.default = ReaderStream; /***/ }), -/* 671 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69259,9 +70085,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_sync_1 = __webpack_require__(672); +var readdir = __webpack_require__(652); +var reader_1 = __webpack_require__(665); +var fs_sync_1 = __webpack_require__(677); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -69321,7 +70147,7 @@ exports.default = ReaderSync; /***/ }), -/* 672 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69340,8 +70166,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(665); -var fs_1 = __webpack_require__(669); +var fsStat = __webpack_require__(670); +var fs_1 = __webpack_require__(674); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -69387,7 +70213,7 @@ exports.default = FileSystemSync; /***/ }), -/* 673 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69403,7 +70229,7 @@ exports.flatten = flatten; /***/ }), -/* 674 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69424,13 +70250,13 @@ exports.merge = merge; /***/ }), -/* 675 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(676); +const pathType = __webpack_require__(681); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -69496,13 +70322,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 676 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(677); +const pify = __webpack_require__(682); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -69545,7 +70371,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 677 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69636,7 +70462,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 678 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69644,9 +70470,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(491); -const gitIgnore = __webpack_require__(679); -const pify = __webpack_require__(680); -const slash = __webpack_require__(681); +const gitIgnore = __webpack_require__(684); +const pify = __webpack_require__(685); +const slash = __webpack_require__(686); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -69744,7 +70570,7 @@ module.exports.sync = options => { /***/ }), -/* 679 */ +/* 684 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -70213,7 +71039,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 680 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70288,7 +71114,7 @@ module.exports = (input, options) => { /***/ }), -/* 681 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70306,17 +71132,17 @@ module.exports = input => { /***/ }), -/* 682 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(683); -const CpFileError = __webpack_require__(685); -const fs = __webpack_require__(687); -const ProgressEmitter = __webpack_require__(689); +const {Buffer} = __webpack_require__(688); +const CpFileError = __webpack_require__(690); +const fs = __webpack_require__(692); +const ProgressEmitter = __webpack_require__(694); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -70470,11 +71296,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 683 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(684) +var buffer = __webpack_require__(689) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -70538,18 +71364,18 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 684 */ +/* 689 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 685 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(686); +const NestedError = __webpack_require__(691); class CpFileError extends NestedError { constructor(message, nested) { @@ -70563,7 +71389,7 @@ module.exports = CpFileError; /***/ }), -/* 686 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(44); @@ -70617,15 +71443,15 @@ module.exports = NestedError; /***/ }), -/* 687 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(115); -const pify = __webpack_require__(688); -const CpFileError = __webpack_require__(685); +const pify = __webpack_require__(693); +const CpFileError = __webpack_require__(690); const fsP = pify(fs); @@ -70770,7 +71596,7 @@ if (fs.copyFileSync) { /***/ }), -/* 688 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70845,7 +71671,7 @@ module.exports = (input, options) => { /***/ }), -/* 689 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70886,12 +71712,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 690 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(691); +const NestedError = __webpack_require__(696); class CpyError extends NestedError { constructor(message, nested) { @@ -70905,7 +71731,7 @@ module.exports = CpyError; /***/ }), -/* 691 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -70961,7 +71787,7 @@ module.exports = NestedError; /***/ }), -/* 692 */ +/* 697 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 2f9b177be65325..b53414518d853d 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -10,10 +10,10 @@ "prettier": "prettier --write './src/**/*.ts'" }, "devDependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-proposal-class-properties": "^7.5.5", - "@babel/plugin-proposal-object-rest-spread": "^7.5.5", - "@babel/preset-env": "^7.5.5", + "@babel/core": "^7.7.5", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-proposal-object-rest-spread": "^7.7.4", + "@babel/preset-env": "^7.7.6", "@babel/preset-typescript": "^7.3.3", "@types/cmd-shim": "^2.0.0", "@types/cpy": "^5.1.0", diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 0cc54fa2a64c41..ac35c583ec7470 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -10,7 +10,7 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.5.5", + "@babel/cli": "^7.7.5", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/parse-link-header": "^1.0.0", diff --git a/packages/kbn-ui-framework/doc_site/src/views/table/table.js b/packages/kbn-ui-framework/doc_site/src/views/table/table.js index 6dddeb824b65aa..45f6e389e7234a 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/table/table.js +++ b/packages/kbn-ui-framework/doc_site/src/views/table/table.js @@ -73,7 +73,7 @@ export class Table extends Component { }, { title: - 'You can also specify that really long content wraps instead of becoming truncated with an ellipsis (which is the default behavior)', // eslint-disable-line max-len + 'You can also specify that really long content wraps instead of becoming truncated with an ellipsis (which is the default behavior)', isLink: true, isWrapped: true, status: 'danger', diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index b4d9d3dfee03f6..a4f23dd19da769 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -30,7 +30,7 @@ "enzyme-adapter-react-16": "^1.9.1" }, "devDependencies": { - "@babel/core": "^7.5.5", + "@babel/core": "^7.7.5", "@elastic/eui": "0.0.55", "@kbn/babel-preset": "1.0.0", "autoprefixer": "9.6.1", @@ -38,7 +38,7 @@ "brace": "0.11.1", "chalk": "^2.4.2", "chokidar": "3.2.1", - "core-js": "^3.2.1", + "core-js": "^3.5.0", "css-loader": "^2.1.1", "expose-loader": "^0.7.5", "file-loader": "^4.2.0", diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 786409614e6d94..18f82766bdbc16 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -130,16 +130,16 @@ leverage this pattern. import React from 'react'; import ReactDOM from 'react-dom'; -import { ApplicationMountContext } from '../../src/core/public'; +import { CoreStart, AppMountParams } from '../../src/core/public'; import { MyAppRoot } from './components/app.ts'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ -export const renderApp = (context: ApplicationMountContext, domElement: HTMLDivElement) => { - ReactDOM.render(, domElement); - return () => ReactDOM.unmountComponentAtNode(domElement); +export const renderApp = (core: CoreStart, deps: MyPluginDepsStart, { element, appBasePath }: AppMountParams) => { + ReactDOM.render(, element); + return () => ReactDOM.unmountComponentAtNode(element); } ``` @@ -152,10 +152,12 @@ export class MyPlugin implements Plugin { public setup(core) { core.application.register({ id: 'my-app', - async mount(context, domElement) { + async mount(params) { // Load application bundle const { renderApp } = await import('./application/my_app'); - return renderApp(context, domElement); + // Get start services + const [coreStart, depsStart] = core.getStartServices(); + return renderApp(coreStart, depsStart, params); } }); } diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 7c3fa4afad2ae8..5bb22579d123e0 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -64,7 +64,7 @@ We'll start with an overview of how plugins work in the new platform, and we'll Plugins in the new platform are not especially novel or complicated to describe. Our intention wasn't to build some clever system that magically solved problems through abstractions and layers of obscurity, and we wanted to make sure plugins could continue to use most of the same technologies they use today, at least from a technical perspective. -New platform plugins exist in the `src/plugins` and `x-pack/plugins` directories. +New platform plugins exist in the `src/plugins` and `x-pack/plugins` directories. _See all [conventions for first-party Elastic plugins](./CONVENTIONS.md)_. ### Architecture @@ -109,7 +109,7 @@ export function plugin(initializerContext: PluginInitializerContext) { } ``` -**[3] `public/plugin.ts`** is the client-side plugin definition itself. Technically speaking it does not need to be a class or even a separate file from the entry point, but _all plugins at Elastic_ should be consistent in this way. +**[3] `public/plugin.ts`** is the client-side plugin definition itself. Technically speaking it does not need to be a class or even a separate file from the entry point, but _all plugins at Elastic_ should be consistent in this way. _See all [conventions for first-party Elastic plugins](./CONVENTIONS.md)_. ```ts import { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; @@ -215,7 +215,7 @@ These are the contracts exposed by the core services for each lifecycle event: Plugins can expose public interfaces for other plugins to consume. Like `core`, those interfaces are bound to the lifecycle functions `setup` and/or `start`. -Anything returned from `setup` or `start` will act as the interface, and while not a technical requirement, all Elastic plugins should expose types for that interface as well. 3rd party plugins wishing to allow other plugins to integrate with it are also highly encouraged to expose types for their plugin interfaces. +Anything returned from `setup` or `start` will act as the interface, and while not a technical requirement, all first-party Elastic plugins should expose types for that interface as well. 3rd party plugins wishing to allow other plugins to integrate with it are also highly encouraged to expose types for their plugin interfaces. **foobar plugin.ts:** @@ -1095,6 +1095,8 @@ The benefit of this approach is that the details of where code lives and whether A plugin author that decides some set of code should diverge from having a single "common" definition can now safely change the implementation details without impacting downstream consumers. +_See all [conventions for first-party Elastic plugins](./CONVENTIONS.md)_. + ### When does code go into a plugin, core, or packages? This is an impossible question to answer definitively for all circumstances. For each time this question is raised, we must carefully consider to what extent we think that code is relevant to almost everyone developing in Kibana, what license the code is shipping under, which teams are most appropriate to "own" that code, is the code stateless etc. diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 72460b07900da2..fd009066fc6640 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -189,9 +189,9 @@ export interface AppMountParameters { * setup({ application }) { * application.register({ * id: 'my-app', - * async mount(context, params) { + * async mount(params) { * const { renderApp } = await import('./application'); - * return renderApp(context, params); + * return renderApp(params); * }, * }); * } @@ -204,7 +204,10 @@ export interface AppMountParameters { * import ReactDOM from 'react-dom'; * import { BrowserRouter, Route } from 'react-router-dom'; * - * export renderApp = (context, { appBasePath, element }) => { + * import { CoreStart, AppMountParams } from 'src/core/public'; + * import { MyPluginDepsStart } from './plugin'; + * + * export renderApp = ({ appBasePath, element }: AppMountParams) => { * ReactDOM.render( * // pass `appBasePath` to `basename` * diff --git a/src/core/public/index.ts b/src/core/public/index.ts index f53bf44bcdcfd0..f83ca2564de8ee 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -111,6 +111,13 @@ export { SavedObjectsClientContract, SavedObjectsClient, SimpleSavedObject, + SavedObjectsImportResponse, + SavedObjectsImportConflictError, + SavedObjectsImportUnsupportedTypeError, + SavedObjectsImportMissingReferencesError, + SavedObjectsImportUnknownError, + SavedObjectsImportError, + SavedObjectsImportRetry, } from './saved_objects'; export { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 37c204a5198017..83b4e67c1cb158 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -939,6 +939,82 @@ export interface SavedObjectsFindResponsePublic; + // (undocumented) + references: Array<{ + type: string; + id: string; + }>; + // (undocumented) + type: 'missing_references'; +} + +// @public +export interface SavedObjectsImportResponse { + // (undocumented) + errors?: SavedObjectsImportError[]; + // (undocumented) + success: boolean; + // (undocumented) + successCount: number; +} + +// @public +export interface SavedObjectsImportRetry { + // (undocumented) + id: string; + // (undocumented) + overwrite: boolean; + // (undocumented) + replaceReferences: Array<{ + type: string; + from: string; + to: string; + }>; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportUnknownError { + // (undocumented) + message: string; + // (undocumented) + statusCode: number; + // (undocumented) + type: 'unknown'; +} + +// @public +export interface SavedObjectsImportUnsupportedTypeError { + // (undocumented) + type: 'unsupported_type'; +} + // @public export interface SavedObjectsMigrationVersion { // (undocumented) diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index 453c3e42a1687b..5015a9c3db78e2 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -40,4 +40,11 @@ export { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsMigrationVersion, + SavedObjectsImportResponse, + SavedObjectsImportConflictError, + SavedObjectsImportUnsupportedTypeError, + SavedObjectsImportMissingReferencesError, + SavedObjectsImportUnknownError, + SavedObjectsImportError, + SavedObjectsImportRetry, } from '../../server/types'; diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts new file mode 100644 index 00000000000000..41a319748a1c93 --- /dev/null +++ b/src/core/server/csp/config.ts @@ -0,0 +1,42 @@ +/* + * 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 { TypeOf, schema } from '@kbn/config-schema'; + +/** + * @internal + */ +export type CspConfigType = TypeOf; + +export const config = { + // TODO: Move this to server.csp using config deprecations + // ? https://github.com/elastic/kibana/pull/52251 + path: 'csp', + schema: schema.object({ + rules: schema.arrayOf(schema.string(), { + defaultValue: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src blob: 'self'`, + `style-src 'unsafe-inline' 'self'`, + ], + }), + strict: schema.boolean({ defaultValue: true }), + warnLegacyBrowsers: schema.boolean({ defaultValue: true }), + }), +}; diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts new file mode 100644 index 00000000000000..45fa8445791b07 --- /dev/null +++ b/src/core/server/csp/csp_config.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { CspConfig } from '.'; + +// CSP rules aren't strictly additive, so any change can potentially expand or +// restrict the policy in a way we consider a breaking change. For that reason, +// we test the default rules exactly so any change to those rules gets flagged +// for manual review. In other words, this test is intentionally fragile to draw +// extra attention if defaults are modified in any way. +// +// A test failure here does not necessarily mean this change cannot be made, +// but any change here should undergo sufficient scrutiny by the Kibana +// security team. +// +// The tests use inline snapshots to make it as easy as possible to identify +// the nature of a change in defaults during a PR review. + +describe('CspConfig', () => { + test('DEFAULT', () => { + expect(CspConfig.DEFAULT).toMatchInlineSnapshot(` + CspConfig { + "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + "rules": Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ], + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); + + test('defaults from config', () => { + expect(new CspConfig()).toMatchInlineSnapshot(` + CspConfig { + "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + "rules": Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ], + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); + + test('creates from partial config', () => { + expect(new CspConfig({ strict: false, warnLegacyBrowsers: false })).toMatchInlineSnapshot(` + CspConfig { + "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + "rules": Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ], + "strict": false, + "warnLegacyBrowsers": false, + } + `); + }); + + test('computes header from rules', () => { + const cspConfig = new CspConfig({ rules: ['alpha', 'beta', 'gamma'] }); + + expect(cspConfig).toMatchInlineSnapshot(` + CspConfig { + "header": "alpha; beta; gamma", + "rules": Array [ + "alpha", + "beta", + "gamma", + ], + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); +}); diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts new file mode 100644 index 00000000000000..bb57702a4a2414 --- /dev/null +++ b/src/core/server/csp/csp_config.ts @@ -0,0 +1,77 @@ +/* + * 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 { config } from './config'; + +const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); + +/** + * CSP configuration for use in Kibana. + * @public + */ +export interface ICspConfig { + /** + * The CSP rules used for Kibana. + */ + readonly rules: string[]; + + /** + * Specify whether browsers that do not support CSP should be + * able to use Kibana. Use `true` to block and `false` to allow. + */ + readonly strict: boolean; + + /** + * Specify whether users with legacy browsers should be warned + * about their lack of Kibana security compliance. + */ + readonly warnLegacyBrowsers: boolean; + + /** + * The CSP rules in a formatted directives string for use + * in a `Content-Security-Policy` header. + */ + readonly header: string; +} + +/** + * CSP configuration for use in Kibana. + * @public + */ +export class CspConfig implements ICspConfig { + static readonly DEFAULT = new CspConfig(); + + public readonly rules: string[]; + public readonly strict: boolean; + public readonly warnLegacyBrowsers: boolean; + public readonly header: string; + + /** + * Returns the default CSP configuration when passed with no config + * @internal + */ + constructor(rawCspConfig: Partial> = {}) { + const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; + + this.rules = source.rules; + this.strict = source.strict; + this.warnLegacyBrowsers = source.warnLegacyBrowsers; + this.header = source.rules.join('; '); + } +} diff --git a/src/legacy/core_plugins/navigation/public/legacy.ts b/src/core/server/csp/index.ts similarity index 76% rename from src/legacy/core_plugins/navigation/public/legacy.ts rename to src/core/server/csp/index.ts index 8a2fdb5ea4964b..a9e320ac5afa50 100644 --- a/src/legacy/core_plugins/navigation/public/legacy.ts +++ b/src/core/server/csp/index.ts @@ -17,13 +17,7 @@ * under the License. */ -import { npSetup, npStart } from 'ui/new_platform'; -import { plugin } from '.'; +import { CspConfig, ICspConfig } from './csp_config'; +import { CspConfigType, config } from './config'; -const navPlugin = plugin(); - -export const setup = navPlugin.setup(npSetup.core); - -export const start = navPlugin.start(npStart.core, { - data: npStart.plugins.data, -}); +export { CspConfig, CspConfigType, config, ICspConfig }; diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 1ee7e13d5e8511..888313e1478cb7 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -256,6 +256,7 @@ describe('with TLS', () => { clientAuthentication: 'none', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); @@ -273,6 +274,7 @@ describe('with TLS', () => { clientAuthentication: 'optional', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); @@ -290,6 +292,7 @@ describe('with TLS', () => { clientAuthentication: 'required', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index cb7726de4da5a6..912459c83df6e6 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -19,6 +19,7 @@ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; import { Env } from '../config'; +import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; @@ -132,23 +133,25 @@ export class HttpConfig { public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; + public csp: ICspConfig; /** * @internal */ - constructor(rawConfig: HttpConfigType, env: Env) { - this.autoListen = rawConfig.autoListen; - this.host = rawConfig.host; - this.port = rawConfig.port; - this.cors = rawConfig.cors; - this.maxPayload = rawConfig.maxPayload; - this.basePath = rawConfig.basePath; - this.keepaliveTimeout = rawConfig.keepaliveTimeout; - this.socketTimeout = rawConfig.socketTimeout; - this.rewriteBasePath = rawConfig.rewriteBasePath; + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { + this.autoListen = rawHttpConfig.autoListen; + this.host = rawHttpConfig.host; + this.port = rawHttpConfig.port; + this.cors = rawHttpConfig.cors; + this.maxPayload = rawHttpConfig.maxPayload; + this.basePath = rawHttpConfig.basePath; + this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; + this.socketTimeout = rawHttpConfig.socketTimeout; + this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.publicDir = env.staticFilesDir; - this.ssl = new SslConfig(rawConfig.ssl || {}); - this.defaultRoute = rawConfig.defaultRoute; - this.compression = rawConfig.compression; + this.ssl = new SslConfig(rawHttpConfig.ssl || {}); + this.defaultRoute = rawHttpConfig.defaultRoute; + this.compression = rawHttpConfig.compression; + this.csp = new CspConfig(rawCspConfig); } } diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 244b3cca60f31d..994a6cced89145 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -46,6 +46,7 @@ export interface HttpServerSetup { */ registerRouter: (router: IRouter) => void; basePath: HttpServiceSetup['basePath']; + csp: HttpServiceSetup['csp']; createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; @@ -109,6 +110,7 @@ export class HttpServer { this.createCookieSessionStorageFactory(cookieOptions, config.basePath), registerAuth: this.registerAuth.bind(this), basePath: basePathService, + csp: config.csp, auth: { get: this.authState.get, isAuthenticated: this.authState.isAuthenticated, diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 444aa04171dbdd..1668b409050b74 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -18,6 +18,7 @@ */ import { Server } from 'hapi'; +import { CspConfig } from '../csp'; import { mockRouter } from './router/router.mock'; import { InternalHttpServiceSetup } from './types'; import { HttpService } from './http_service'; @@ -55,6 +56,7 @@ const createSetupContractMock = () => { registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), + csp: CspConfig.DEFAULT, auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index a2546709a318ca..8b500caf217dc5 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -28,6 +28,7 @@ import { ConfigService, Env } from '../config'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; +import { config as cspConfig } from '../csp'; const logger = loggingServiceMock.create(); const env = Env.createDefault(getEnvOptions()); @@ -45,6 +46,7 @@ const createConfigService = (value: Partial = {}) => { logger ); configService.setSchema(config.path, config.schema); + configService.setSchema(cspConfig.path, cspConfig.schema); return configService; }; const contextSetup = contextServiceMock.createSetupContract(); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index caebd768c70e56..faeae0b559b6be 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Observable, Subscription } from 'rxjs'; +import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { Server } from 'hapi'; @@ -28,9 +28,10 @@ import { Logger } from '../logging'; import { ContextSetup } from '../context'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; +import { CspConfigType, config as cspConfig } from '../csp'; import { Router } from './router'; -import { HttpConfig, HttpConfigType } from './http_config'; +import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config'; import { HttpServer } from './http_server'; import { HttpsRedirectServer } from './https_redirect_server'; @@ -60,16 +61,16 @@ export class HttpService implements CoreService('server') - .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); - - this.httpServer = new HttpServer(coreContext.logger, 'Kibana'); - this.httpsRedirectServer = new HttpsRedirectServer( - coreContext.logger.get('http', 'redirect', 'server') - ); + const { logger, configService, env } = coreContext; + + this.logger = logger; + this.log = logger.get('http'); + this.config$ = combineLatest( + configService.atPath(httpConfig.path), + configService.atPath(cspConfig.path) + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + this.httpServer = new HttpServer(logger, 'Kibana'); + this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } public async setup(deps: SetupDeps) { @@ -79,7 +80,7 @@ export class HttpService implements CoreService { certificate: 'some-certificate-path', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` - Object { - "ca": undefined, - "cert": "content-some-certificate-path", - "ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", - "honorCipherOrder": true, - "key": "content-some-key-path", - "passphrase": undefined, - "rejectUnauthorized": false, - "requestCert": false, - "secureOptions": 67108864, - } - `); + Object { + "ca": undefined, + "cert": "content-some-certificate-path", + "ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", + "honorCipherOrder": true, + "key": "content-some-key-path", + "passphrase": undefined, + "rejectUnauthorized": false, + "requestCert": false, + "secureOptions": 67108864, + } + `); }); it('properly configures TLS with client authentication', () => { @@ -151,6 +152,7 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 94c1982a18c0ac..92217515a22a1c 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -17,6 +17,7 @@ * under the License. */ import { IContextProvider, IContextContainer } from '../context'; +import { ICspConfig } from '../csp'; import { RequestHandler, IRouter } from './router'; import { HttpServerSetup } from './http_server'; import { SessionStorageCookieOptions } from './cookie_session_storage'; @@ -182,6 +183,11 @@ export interface HttpServiceSetup { */ basePath: IBasePath; + /** + * The CSP config used for Kibana. + */ + csp: ICspConfig; + /** * Flag showing whether a server was configured to use TLS connection. */ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index c304958f78bb70..835c5872d51a30 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,6 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; +export { CspConfig, ICspConfig } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts index 201f761701a35c..db2bc117280ca0 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts @@ -45,6 +45,22 @@ describe('#get', () => { expect(configAdapter.get('container')).toEqual({ value: 'some' }); }); + test('correctly handles csp config.', () => { + const configAdapter = new LegacyObjectToConfigAdapter({ + csp: { + rules: ['strict'], + }, + }); + + expect(configAdapter.get('csp')).toMatchInlineSnapshot(` + Object { + "rules": Array [ + "strict", + ], + } + `); + }); + test('correctly handles silent logging config.', () => { const configAdapter = new LegacyObjectToConfigAdapter({ logging: { silent: true }, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 4c2e57dc69b29c..662cc0bdf2f3a4 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -25,8 +25,9 @@ import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { SavedObjectsLegacyUiExports } from '../types'; import { Config, ConfigDeprecationProvider } from '../config'; import { CoreContext } from '../core_context'; -import { DevConfig, DevConfigType } from '../dev'; -import { BasePathProxyServer, HttpConfig, HttpConfigType } from '../http'; +import { CspConfigType, config as cspConfig } from '../csp'; +import { DevConfig, DevConfigType, config as devConfig } from '../dev'; +import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http'; import { Logger } from '../logging'; import { PluginsServiceSetup, PluginsServiceStart } from '../plugins'; import { findLegacyPluginSpecs } from './plugins'; @@ -112,13 +113,16 @@ export class LegacyService implements CoreService { private settings: Record | undefined; constructor(private readonly coreContext: CoreContext) { - this.log = coreContext.logger.get('legacy-service'); - this.devConfig$ = coreContext.configService - .atPath('dev') + const { logger, configService, env } = coreContext; + + this.log = logger.get('legacy-service'); + this.devConfig$ = configService + .atPath(devConfig.path) .pipe(map(rawConfig => new DevConfig(rawConfig))); - this.httpConfig$ = coreContext.configService - .atPath('server') - .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); + this.httpConfig$ = combineLatest( + configService.atPath(httpConfig.path), + configService.atPath(cspConfig.path) + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); } public async discoverPlugins(): Promise { @@ -240,8 +244,8 @@ export class LegacyService implements CoreService { ? combineLatest(this.devConfig$, this.httpConfig$).pipe( first(), map( - ([devConfig, httpConfig]) => - new BasePathProxyServer(this.coreContext.logger.get('server'), httpConfig, devConfig) + ([dev, http]) => + new BasePathProxyServer(this.coreContext.logger.get('server'), http, dev) ) ) : EMPTY; @@ -284,6 +288,7 @@ export class LegacyService implements CoreService { registerOnPostAuth: setupDeps.core.http.registerOnPostAuth, registerOnPreResponse: setupDeps.core.http.registerOnPreResponse, basePath: setupDeps.core.http.basePath, + csp: setupDeps.core.http.csp, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, savedObjects: { @@ -339,9 +344,9 @@ export class LegacyService implements CoreService { require('../../../cli/repl').startRepl(kbnServer); } - const httpConfig = await this.httpConfig$.pipe(first()).toPromise(); + const { autoListen } = await this.httpConfig$.pipe(first()).toPromise(); - if (httpConfig.autoListen) { + if (autoListen) { try { await kbnServer.listen(); } catch (err) { diff --git a/src/core/server/logging/logger.mock.ts b/src/core/server/logging/logger.mock.ts new file mode 100644 index 00000000000000..a3bb07ea4c0951 --- /dev/null +++ b/src/core/server/logging/logger.mock.ts @@ -0,0 +1,46 @@ +/* + * 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 { Logger } from './logger'; + +export type MockedLogger = jest.Mocked & { context: string[] }; + +const createLoggerMock = (context: string[] = []) => { + const mockLog: MockedLogger = { + context, + debug: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + log: jest.fn(), + trace: jest.fn(), + warn: jest.fn(), + get: jest.fn(), + }; + mockLog.get.mockImplementation((...ctx) => ({ + ctx, + ...mockLog, + })); + + return mockLog; +}; + +export const loggerMock = { + create: createLoggerMock, +}; diff --git a/src/core/server/logging/logger.test.ts b/src/core/server/logging/logger.test.ts index 40c310c4e94c77..026e24fc5df543 100644 --- a/src/core/server/logging/logger.test.ts +++ b/src/core/server/logging/logger.test.ts @@ -25,15 +25,20 @@ import { BaseLogger } from './logger'; const context = LoggingConfig.getLoggerContext(['context', 'parent', 'child']); let appenderMocks: Appender[]; let logger: BaseLogger; +const factory = { + get: jest.fn().mockImplementation(() => logger), +}; + const timestamp = new Date(2012, 1, 1); beforeEach(() => { jest.spyOn(global, 'Date').mockImplementation(() => timestamp); appenderMocks = [{ append: jest.fn() }, { append: jest.fn() }]; - logger = new BaseLogger(context, LogLevel.All, appenderMocks); + logger = new BaseLogger(context, LogLevel.All, appenderMocks, factory); }); afterEach(() => { + jest.resetAllMocks(); jest.restoreAllMocks(); }); @@ -263,8 +268,22 @@ test('`log()` just passes the record to all appenders.', () => { } }); +test('`get()` calls the logger factory with proper context and return the result', () => { + logger.get('sub', 'context'); + expect(factory.get).toHaveBeenCalledTimes(1); + expect(factory.get).toHaveBeenCalledWith(context, 'sub', 'context'); + + factory.get.mockClear(); + factory.get.mockImplementation(() => 'some-logger'); + + const childLogger = logger.get('other', 'sub'); + expect(factory.get).toHaveBeenCalledTimes(1); + expect(factory.get).toHaveBeenCalledWith(context, 'other', 'sub'); + expect(childLogger).toEqual('some-logger'); +}); + test('logger with `Off` level does not pass any records to appenders.', () => { - const turnedOffLogger = new BaseLogger(context, LogLevel.Off, appenderMocks); + const turnedOffLogger = new BaseLogger(context, LogLevel.Off, appenderMocks, factory); turnedOffLogger.trace('trace-message'); turnedOffLogger.debug('debug-message'); turnedOffLogger.info('info-message'); @@ -278,7 +297,7 @@ test('logger with `Off` level does not pass any records to appenders.', () => { }); test('logger with `All` level passes all records to appenders.', () => { - const catchAllLogger = new BaseLogger(context, LogLevel.All, appenderMocks); + const catchAllLogger = new BaseLogger(context, LogLevel.All, appenderMocks, factory); catchAllLogger.trace('trace-message'); for (const appenderMock of appenderMocks) { @@ -348,7 +367,7 @@ test('logger with `All` level passes all records to appenders.', () => { }); test('passes log record to appenders only if log level is supported.', () => { - const warnLogger = new BaseLogger(context, LogLevel.Warn, appenderMocks); + const warnLogger = new BaseLogger(context, LogLevel.Warn, appenderMocks, factory); warnLogger.trace('trace-message'); warnLogger.debug('debug-message'); diff --git a/src/core/server/logging/logger.ts b/src/core/server/logging/logger.ts index e10e79d5cf45b5..ac79c1916c07ba 100644 --- a/src/core/server/logging/logger.ts +++ b/src/core/server/logging/logger.ts @@ -20,6 +20,7 @@ import { Appender } from './appenders/appenders'; import { LogLevel } from './log_level'; import { LogRecord } from './log_record'; +import { LoggerFactory } from './logger_factory'; /** * Contextual metadata @@ -84,6 +85,17 @@ export interface Logger { /** @internal */ log(record: LogRecord): void; + + /** + * Returns a new {@link Logger} instance extending the current logger context. + * + * @example + * ```typescript + * const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context + * const subLogger = logger.get('feature'); // 'plugin.service.feature' context + * ``` + */ + get(...childContextPaths: string[]): Logger; } function isError(x: any): x is Error { @@ -95,7 +107,8 @@ export class BaseLogger implements Logger { constructor( private readonly context: string, private readonly level: LogLevel, - private readonly appenders: Appender[] + private readonly appenders: Appender[], + private readonly factory: LoggerFactory ) {} public trace(message: string, meta?: LogMeta): void { @@ -132,6 +145,10 @@ export class BaseLogger implements Logger { } } + public get(...childContextPaths: string[]): Logger { + return this.factory.get(...[this.context, ...childContextPaths]); + } + private createLogRecord( level: LogLevel, errorOrMessage: string | Error, diff --git a/src/core/server/logging/logger_adapter.test.ts b/src/core/server/logging/logger_adapter.test.ts index 075e8f4d47ffe1..c4d7a56e074a7a 100644 --- a/src/core/server/logging/logger_adapter.test.ts +++ b/src/core/server/logging/logger_adapter.test.ts @@ -29,6 +29,7 @@ test('proxies all method calls to the internal logger.', () => { log: jest.fn(), trace: jest.fn(), warn: jest.fn(), + get: jest.fn(), }; const adapter = new LoggerAdapter(internalLogger); @@ -56,6 +57,10 @@ test('proxies all method calls to the internal logger.', () => { adapter.fatal('fatal-message'); expect(internalLogger.fatal).toHaveBeenCalledTimes(1); expect(internalLogger.fatal).toHaveBeenCalledWith('fatal-message', undefined); + + adapter.get('context'); + expect(internalLogger.get).toHaveBeenCalledTimes(1); + expect(internalLogger.get).toHaveBeenCalledWith('context'); }); test('forwards all method calls to new internal logger if it is updated.', () => { @@ -67,6 +72,7 @@ test('forwards all method calls to new internal logger if it is updated.', () => log: jest.fn(), trace: jest.fn(), warn: jest.fn(), + get: jest.fn(), }; const newInternalLogger: Logger = { @@ -77,6 +83,7 @@ test('forwards all method calls to new internal logger if it is updated.', () => log: jest.fn(), trace: jest.fn(), warn: jest.fn(), + get: jest.fn(), }; const adapter = new LoggerAdapter(oldInternalLogger); diff --git a/src/core/server/logging/logger_adapter.ts b/src/core/server/logging/logger_adapter.ts index ffc212631e7b42..14e5712e55c584 100644 --- a/src/core/server/logging/logger_adapter.ts +++ b/src/core/server/logging/logger_adapter.ts @@ -63,4 +63,8 @@ export class LoggerAdapter implements Logger { public log(record: LogRecord) { this.logger.log(record); } + + public get(...contextParts: string[]): Logger { + return this.logger.get(...contextParts); + } } diff --git a/src/core/server/logging/logging_service.mock.ts b/src/core/server/logging/logging_service.mock.ts index 50e6edc227bb56..15d66c2e8535ca 100644 --- a/src/core/server/logging/logging_service.mock.ts +++ b/src/core/server/logging/logging_service.mock.ts @@ -18,22 +18,17 @@ */ // Test helpers to simplify mocking logs and collecting all their outputs -import { Logger } from './logger'; import { ILoggingService } from './logging_service'; import { LoggerFactory } from './logger_factory'; - -type MockedLogger = jest.Mocked; +import { loggerMock, MockedLogger } from './logger.mock'; const createLoggingServiceMock = () => { - const mockLog: MockedLogger = { - debug: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - log: jest.fn(), - trace: jest.fn(), - warn: jest.fn(), - }; + const mockLog = loggerMock.create(); + + mockLog.get.mockImplementation((...context) => ({ + ...mockLog, + context, + })); const mocked: jest.Mocked = { get: jest.fn(), @@ -42,8 +37,8 @@ const createLoggingServiceMock = () => { stop: jest.fn(), }; mocked.get.mockImplementation((...context) => ({ - context, ...mockLog, + context, })); mocked.asLoggerFactory.mockImplementation(() => mocked); mocked.stop.mockResolvedValue(); @@ -84,4 +79,5 @@ export const loggingServiceMock = { create: createLoggingServiceMock, collect: collectLoggingServiceMock, clear: clearLoggingServiceMock, + createLogger: loggerMock.create, }; diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts index ada02c3b6901ad..f9535e6c8283e1 100644 --- a/src/core/server/logging/logging_service.ts +++ b/src/core/server/logging/logging_service.ts @@ -37,12 +37,9 @@ export class LoggingService implements LoggerFactory { public get(...contextParts: string[]): Logger { const context = LoggingConfig.getLoggerContext(contextParts); - if (this.loggers.has(context)) { - return this.loggers.get(context)!; + if (!this.loggers.has(context)) { + this.loggers.set(context, new LoggerAdapter(this.createLogger(context, this.config))); } - - this.loggers.set(context, new LoggerAdapter(this.createLogger(context, this.config))); - return this.loggers.get(context)!; } @@ -55,7 +52,7 @@ export class LoggingService implements LoggerFactory { /** * Updates all current active loggers with the new config values. - * @param config New config instance. + * @param rawConfig New config instance. */ public upgrade(rawConfig: LoggingConfigType) { const config = new LoggingConfig(rawConfig); @@ -106,14 +103,14 @@ export class LoggingService implements LoggerFactory { if (config === undefined) { // If we don't have config yet, use `buffered` appender that will store all logged messages in the memory // until the config is ready. - return new BaseLogger(context, LogLevel.All, [this.bufferAppender]); + return new BaseLogger(context, LogLevel.All, [this.bufferAppender], this.asLoggerFactory()); } const { level, appenders } = this.getLoggerConfigByContext(config, context); const loggerLevel = LogLevel.fromId(level); const loggerAppenders = appenders.map(appenderKey => this.appenders.get(appenderKey)!); - return new BaseLogger(context, loggerLevel, loggerAppenders); + return new BaseLogger(context, loggerLevel, loggerAppenders, this.asLoggerFactory()); } private getLoggerConfigByContext(config: LoggingConfig, context: string): LoggerConfigType { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c07caaa04ba52a..07b60e771d6434 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -19,6 +19,7 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { PluginInitializerContext, CoreSetup, CoreStart } from '.'; +import { CspConfig } from './csp'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -92,6 +93,7 @@ function createCoreSetupMock() { registerOnPostAuth: httpService.registerOnPostAuth, registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, + csp: CspConfig.DEFAULT, isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), registerRouteHandlerContext: jest.fn(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 6829784e6e0a19..26c65baf955357 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -161,6 +161,7 @@ export function createPluginSetupContext( registerOnPostAuth: deps.http.registerOnPostAuth, registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, + csp: deps.http.csp, isTlsEnabled: deps.http.isTlsEnabled, }, savedObjects: { diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 2c6f5e4a520a7f..c1fa820ef2019d 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -23,6 +23,16 @@ import { MigrationDefinition } from './migrations/core/document_migrator'; import { SavedObjectsSchemaDefinition } from './schema'; import { PropertyValidators } from './validation'; +export { + SavedObjectsImportResponse, + SavedObjectsImportConflictError, + SavedObjectsImportUnsupportedTypeError, + SavedObjectsImportMissingReferencesError, + SavedObjectsImportUnknownError, + SavedObjectsImportError, + SavedObjectsImportRetry, +} from './import/types'; + /** * Information about the migrations that have been applied to this SavedObject. * When Kibana starts up, KibanaMigrator detects outdated documents and diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 18e76324ff3093..780a1532a859e2 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -574,6 +574,22 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; } +// @public +export class CspConfig implements ICspConfig { + // @internal + constructor(rawCspConfig?: Partial>); + // (undocumented) + static readonly DEFAULT: CspConfig; + // (undocumented) + readonly header: string; + // (undocumented) + readonly rules: string[]; + // (undocumented) + readonly strict: boolean; + // (undocumented) + readonly warnLegacyBrowsers: boolean; +} + // @public export interface CustomHttpResponseOptions { body?: T; @@ -713,6 +729,7 @@ export interface HttpServiceSetup { basePath: IBasePath; createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; createRouter: () => IRouter; + csp: ICspConfig; isTlsEnabled: boolean; registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; @@ -741,6 +758,14 @@ export interface IContextContainer> { // @public export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +// @public +export interface ICspConfig { + readonly header: string; + readonly rules: string[]; + readonly strict: boolean; + readonly warnLegacyBrowsers: boolean; +} + // @public export interface IKibanaResponse { // (undocumented) @@ -903,6 +928,7 @@ export interface Logger { debug(message: string, meta?: LogMeta): void; error(errorOrMessage: string | Error, meta?: LogMeta): void; fatal(errorOrMessage: string | Error, meta?: LogMeta): void; + get(...childContextPaths: string[]): Logger; info(message: string, meta?: LogMeta): void; // @internal (undocumented) log(record: LogRecord): void; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index e7bc57ea5fb948..725a45f1319922 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -35,6 +35,7 @@ import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; +import { config as cspConfig } from './csp'; import { config as elasticsearchConfig } from './elasticsearch'; import { config as httpConfig } from './http'; import { config as loggingConfig } from './logging'; @@ -218,6 +219,7 @@ export class Server { public async setupCoreConfig() { const schemas: Array<[ConfigPath, Type]> = [ [pathConfig.path, pathConfig.schema], + [cspConfig.path, cspConfig.schema], [elasticsearchConfig.path, elasticsearchConfig.schema], [loggingConfig.path, loggingConfig.schema], [httpConfig.path, httpConfig.schema], diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 4878fb9ccae19c..6e3e6bfe208a60 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -22,3 +22,4 @@ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; +export { ICspConfig } from './csp'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js index b38c61c046db32..801580f4e158c5 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js @@ -61,7 +61,7 @@ export function ScriptHighlightRules() { }, { token: 'script.keyword.operator', - // eslint-disable-next-line max-len + regex: '\\?\\.|\\*\\.|=~|==~|!|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|->|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|instanceof|new|typeof|void)', }, diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js index 2e91845ac2e7ef..f79a171c650826 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js @@ -18,7 +18,6 @@ */ /* eslint import/no-unresolved: 0 */ -/* eslint max-len: 0 */ const ace = require('brace'); ace.define('ace/theme/sense-dark', ['require', 'exports', 'module'], function(require, exports) { diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js index 374ad670601586..f6828f354a1bc7 100644 --- a/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js @@ -76,33 +76,28 @@ describe('Utils class', () => { expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning" "Mon, 27 Feb 2017 14:52:14 GMT", 299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a second warning" "Mon, 27 Feb 2017 14:52:14 GMT"' ) ).toEqual(['#! Deprecation: this is a warning', '#! Deprecation: this is a second warning']); expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning", 299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a second warning"' ) ).toEqual(['#! Deprecation: this is a warning', '#! Deprecation: this is a second warning']); expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning, and it includes a comma" "Mon, 27 Feb 2017 14:52:14 GMT"' ) ).toEqual(['#! Deprecation: this is a warning, and it includes a comma']); expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning, and it includes a comma"' ) ).toEqual(['#! Deprecation: this is a warning, and it includes a comma']); expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning, and it includes an escaped backslash \\\\ and a pair of \\"escaped quotes\\"" "Mon, 27 Feb 2017 14:52:14 GMT"' ) ).toEqual([ @@ -110,7 +105,6 @@ describe('Utils class', () => { ]); expect( utils.extractDeprecationMessages( - //eslint-disable-next-line max-len '299 Elasticsearch-6.0.0-alpha1-SNAPSHOT-abcdef1 "this is a warning, and it includes an escaped backslash \\\\ and a pair of \\"escaped quotes\\""' ) ).toEqual([ diff --git a/src/legacy/core_plugins/console/server/api_server/spec/.eslintrc b/src/legacy/core_plugins/console/server/api_server/spec/.eslintrc index 46052f0e19a0da..10f7444ecaf1ba 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/.eslintrc +++ b/src/legacy/core_plugins/console/server/api_server/spec/.eslintrc @@ -6,13 +6,10 @@ extends: '../../../../../../../.eslintrc.js' rules: block-scoped-var: off camelcase: off - curly: off dot-location: off dot-notation: off eqeqeq: off guard-for-in: off - indent: off - max-len: off new-cap: off no-caller: off no-empty: off @@ -25,12 +22,6 @@ rules: no-undef: off no-use-before-define: off one-var: off - quotes: off - space-before-blocks: off - space-in-parens: off - space-infix-ops: off - semi: off strict: off - wrap-iife: off no-var: off prefer-const: off diff --git a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.test.ts b/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.test.ts deleted file mode 100644 index 4463758e30bd60..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.test.ts +++ /dev/null @@ -1,201 +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 { ajaxStream, XMLHttpRequestLike } from './ajax_stream'; - -// eslint-disable-next-line no-empty -function noop() {} - -describe('ajaxStream', () => { - it('pulls items from the stream and calls the handler', async () => { - const handler = jest.fn(() => ({})); - const { req, sendText, done } = mockRequest(); - const messages = ['{ "hello": "world" }\n', '{ "tis": "fate" }\n']; - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - }); - - sendText(messages[0]); - sendText(messages[1]); - done(); - - await promise; - expect(handler).toHaveBeenCalledTimes(2); - expect(handler).toHaveBeenCalledWith({ hello: 'world' }); - expect(handler).toHaveBeenCalledWith({ tis: 'fate' }); - }); - - it('handles newlines in values', async () => { - const handler = jest.fn(() => ({})); - const { req, sendText, done } = mockRequest(); - const messages = [ - JSON.stringify({ hello: 'wo\nrld' }), - '\n', - JSON.stringify({ tis: 'fa\nte' }), - '\n', - ]; - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - }); - - messages.forEach(sendText); - done(); - - await promise; - expect(handler).toHaveBeenCalledTimes(2); - expect(handler).toHaveBeenCalledWith({ hello: 'wo\nrld' }); - expect(handler).toHaveBeenCalledWith({ tis: 'fa\nte' }); - }); - - it('handles partial messages', async () => { - const handler = jest.fn(() => ({})); - const { req, sendText, done } = mockRequest(); - const messages = ['{ "hello": "world" }\n', '{ "tis": "fate" }\n'].join(''); - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - }); - - for (const s of messages) { - sendText(s); - } - done(); - - await promise; - expect(handler).toHaveBeenCalledTimes(2); - expect(handler).toHaveBeenCalledWith({ hello: 'world' }); - expect(handler).toHaveBeenCalledWith({ tis: 'fate' }); - }); - - it('sends the request', async () => { - const handler = jest.fn(() => ({})); - const { req, done } = mockRequest(); - - const promise = ajaxStream('mehBasePath', { a: 'b' }, req, { - url: '/test/endpoint', - onResponse: handler, - body: 'whatup', - headers: { foo: 'bar' }, - }); - - done(); - - await promise; - expect(req.open).toHaveBeenCalledWith('POST', 'mehBasePath/test/endpoint'); - expect(req.setRequestHeader).toHaveBeenCalledWith('foo', 'bar'); - expect(req.setRequestHeader).toHaveBeenCalledWith('a', 'b'); - expect(req.send).toHaveBeenCalledWith('whatup'); - }); - - it('rejects if network failure', async () => { - const handler = jest.fn(() => ({})); - const { req, done } = mockRequest(); - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - body: 'whatup', - }); - - done(0); - expect(await promise.then(() => true).catch(() => false)).toBeFalsy(); - }); - - it('rejects if http status error', async () => { - const handler = jest.fn(() => ({})); - const { req, done } = mockRequest(); - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - body: 'whatup', - }); - - done(400); - expect(await promise.then(() => true).catch(() => false)).toBeFalsy(); - }); - - it('rejects if the payload contains invalid JSON', async () => { - const handler = jest.fn(() => ({})); - const { req, sendText, done } = mockRequest(); - const messages = ['{ waut? }\n'].join(''); - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - }); - - sendText(messages); - done(); - - expect(await promise.then(() => true).catch(() => false)).toBeFalsy(); - }); - - it('rejects if the handler throws', async () => { - const handler = jest.fn(() => { - throw new Error('DOH!'); - }); - const { req, sendText, done } = mockRequest(); - const messages = ['{ "hello": "world" }\n', '{ "tis": "fate" }\n'].join(''); - - const promise = ajaxStream('', {}, req, { - url: '/test/endpoint', - onResponse: handler, - }); - - sendText(messages); - done(); - - expect(await promise.then(() => true).catch(({ message }) => message)).toMatch(/doh!/i); - }); -}); - -function mockRequest() { - const req: XMLHttpRequestLike = { - onprogress: noop, - onreadystatechange: noop, - open: jest.fn(), - readyState: 0, - responseText: '', - send: jest.fn(), - setRequestHeader: jest.fn(), - abort: jest.fn(), - status: 0, - withCredentials: false, - }; - - return { - req, - sendText(text: string) { - req.responseText += text; - req.onreadystatechange(); - req.onprogress(); - }, - done(status = 200) { - req.status = status; - req.readyState = 4; - req.onreadystatechange(); - }, - }; -} diff --git a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.ts b/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.ts deleted file mode 100644 index 867581081f82fd..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/ajax_stream.ts +++ /dev/null @@ -1,152 +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'; - -/** - * This file contains the client-side logic for processing a streaming AJAX response. - * This allows things like request batching to process individual batch item results - * as soon as the server sends them, instead of waiting for the entire response before - * client-side processing can begin. - * - * The server sends responses in this format: {length}:{json}, for example: - * - * 18:{"hello":"world"}\n16:{"hello":"you"}\n - */ - -// T is the response payload (the JSON), and we don't really -// care what it's type / shape is. -export type BatchResponseHandler = (result: T) => void; - -export interface BatchOpts { - url: string; - onResponse: BatchResponseHandler; - method?: string; - body?: string; - headers?: { [k: string]: string }; -} - -// The subset of XMLHttpRequest that we use -export interface XMLHttpRequestLike { - abort: () => void; - onreadystatechange: any; - onprogress: any; - open: (method: string, url: string) => void; - readyState: number; - responseText: string; - send: (body?: string) => void; - setRequestHeader: (header: string, value: string) => void; - status: number; - withCredentials: boolean; -} - -// Create a function which, when successively passed streaming response text, -// calls a handler callback with each response in the batch. -function processBatchResponseStream(handler: BatchResponseHandler) { - let index = 0; - - return (text: string) => { - // While there's text to process... - while (index < text.length) { - // We're using new line-delimited JSON. - const delim = '\n'; - const delimIndex = text.indexOf(delim, index); - - // We've got an incomplete batch length - if (delimIndex < 0) { - return; - } - - const payload = JSON.parse(text.slice(index, delimIndex)); - handler(payload); - - index = delimIndex + 1; - } - }; -} - -/** - * Sends an AJAX request to the server, and processes the result as a - * streaming HTTP/1 response. - * - * @param basePath - The Kibana basepath - * @param defaultHeaders - The default HTTP headers to be sent with each request - * @param req - The XMLHttpRequest - * @param opts - The request options - * @returns A promise which resolves when the entire batch response has been processed. - */ -export function ajaxStream( - basePath: string, - defaultHeaders: { [k: string]: string }, - req: XMLHttpRequestLike, - opts: BatchOpts -) { - return new Promise((resolve, reject) => { - const { url, method, headers } = opts; - - // There are several paths by which the promise may resolve or reject. We wrap this - // in "once" as a safeguard against cases where we attempt more than one call. (e.g. - // a batch handler fails, so we reject the promise, but then new data comes in for - // a subsequent batch item) - const complete = once((err: Error | undefined = undefined) => - err ? reject(err) : resolve(req) - ); - - // Begin the request - req.open(method || 'POST', `${basePath}/${url.replace(/^\//, '')}`); - req.withCredentials = true; - - // Set the HTTP headers - Object.entries(Object.assign({}, defaultHeaders, headers)).forEach(([k, v]) => - req.setRequestHeader(k, v) - ); - - const batchHandler = processBatchResponseStream(opts.onResponse); - const processBatch = () => { - try { - batchHandler(req.responseText); - } catch (err) { - req.abort(); - complete(err); - } - }; - - req.onprogress = processBatch; - - req.onreadystatechange = () => { - // Older browsers don't support onprogress, so we need - // to call this here, too. It's safe to call this multiple - // times even for the same progress event. - processBatch(); - - // 4 is the magic number that means the request is done - if (req.readyState === 4) { - // 0 indicates a network failure. 400+ messages are considered server errors - if (req.status === 0 || req.status >= 400) { - complete(new Error(`Batch request failed with status ${req.status}`)); - } else { - complete(); - } - } - }; - - // Send the payload to the server - req.send(opts.body); - }); -} diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts b/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts index f5b1960b608a31..3da15cf54cda07 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts +++ b/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts @@ -18,23 +18,35 @@ */ import { batchedFetch, Request } from './batched_fetch'; +import { defer } from '../../../../../plugins/kibana_utils/public'; +import { Subject } from 'rxjs'; const serialize = (o: any) => JSON.stringify(o); -const ajaxStream = jest.fn(async ({ body, onResponse }) => { +const fetchStreaming = jest.fn(({ body }) => { const { functions } = JSON.parse(body); - functions.map(({ id, functionName, context, args }: Request) => - onResponse({ - id, - statusCode: context, - result: Number(context) >= 400 ? { err: {} } : `${functionName}${context}${args}`, - }) - ); -}); + const { promise, resolve } = defer(); + const stream = new Subject(); + + setTimeout(() => { + functions.map(({ id, functionName, context, args }: Request) => + stream.next( + JSON.stringify({ + id, + statusCode: context, + result: Number(context) >= 400 ? { err: {} } : `${functionName}${context}${args}`, + }) + '\n' + ) + ); + resolve(); + }, 1); + + return { promise, stream }; +}) as any; describe('batchedFetch', () => { it('resolves the correct promise', async () => { - const ajax = batchedFetch({ ajaxStream, serialize, ms: 1 }); + const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); const result = await Promise.all([ ajax({ functionName: 'a', context: 1, args: 'aaa' }), @@ -45,7 +57,7 @@ describe('batchedFetch', () => { }); it('dedupes duplicate calls', async () => { - const ajax = batchedFetch({ ajaxStream, serialize, ms: 1 }); + const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); const result = await Promise.all([ ajax({ functionName: 'a', context: 1, args: 'aaa' }), @@ -55,11 +67,11 @@ describe('batchedFetch', () => { ]); expect(result).toEqual(['a1aaa', 'b2bbb', 'a1aaa', 'a1aaa']); - expect(ajaxStream).toHaveBeenCalledTimes(2); + expect(fetchStreaming).toHaveBeenCalledTimes(2); }); it('rejects responses whose statusCode is >= 300', async () => { - const ajax = batchedFetch({ ajaxStream, serialize, ms: 1 }); + const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); const result = await Promise.all([ ajax({ functionName: 'a', context: 500, args: 'aaa' }).catch(() => 'fail'), diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts b/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts index cba70fe1da8fc4..717a87fc90f9f8 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts +++ b/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts @@ -18,13 +18,15 @@ */ import _ from 'lodash'; +import { filter, map } from 'rxjs/operators'; +// eslint-disable-next-line +import { split } from '../../../../../plugins/bfetch/public/streaming'; +import { BfetchPublicApi } from '../../../../../plugins/bfetch/public'; +import { defer } from '../../../../../plugins/kibana_utils/public'; import { FUNCTIONS_URL } from './consts'; -// TODO: Import this type from kibana_util. -type AjaxStream = any; - export interface Options { - ajaxStream: any; + fetchStreaming: BfetchPublicApi['fetchStreaming']; serialize: any; ms?: number; } @@ -47,7 +49,7 @@ export interface Request { * Create a function which executes an Expression function on the * server as part of a larger batch of executions. */ -export function batchedFetch({ ajaxStream, serialize, ms = 10 }: Options) { +export function batchedFetch({ fetchStreaming, serialize, ms = 10 }: Options) { // Uniquely identifies each function call in a batch operation // so that the appropriate promise can be resolved / rejected later. let id = 0; @@ -66,7 +68,7 @@ export function batchedFetch({ ajaxStream, serialize, ms = 10 }: Options) { }; const runBatch = () => { - processBatch(ajaxStream, batch); + processBatch(fetchStreaming, batch); reset(); }; @@ -92,7 +94,7 @@ export function batchedFetch({ ajaxStream, serialize, ms = 10 }: Options) { } // If not, create a new promise, id, and add it to the batched collection. - const future = createFuture(); + const future = defer(); const newId = nextId(); request.id = newId; @@ -106,48 +108,39 @@ export function batchedFetch({ ajaxStream, serialize, ms = 10 }: Options) { } /** - * An externally resolvable / rejectable promise, used to make sure - * individual batch responses go to the correct caller. + * Runs the specified batch of functions on the server, then resolves + * the related promises. */ -function createFuture() { - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; +async function processBatch(fetchStreaming: BfetchPublicApi['fetchStreaming'], batch: Batch) { + const { stream, promise } = fetchStreaming({ + url: FUNCTIONS_URL, + body: JSON.stringify({ + functions: Object.values(batch).map(({ request }) => request), + }), }); - return { - resolve, - reject, - promise, - }; -} + stream + .pipe( + split('\n'), + filter(Boolean), + map((json: string) => JSON.parse(json)) + ) + .subscribe((message: any) => { + const { id, statusCode, result } = message; + const { future } = batch[id]; + + if (statusCode >= 400) { + future.reject(result); + } else { + future.resolve(result); + } + }); -/** - * Runs the specified batch of functions on the server, then resolves - * the related promises. - */ -async function processBatch(ajaxStream: AjaxStream, batch: Batch) { try { - await ajaxStream({ - url: FUNCTIONS_URL, - body: JSON.stringify({ - functions: Object.values(batch).map(({ request }) => request), - }), - onResponse({ id, statusCode, result }: any) { - const { future } = batch[id]; - - if (statusCode >= 400) { - future.reject(result); - } else { - future.resolve(result); - } - }, - }); - } catch (err) { + await promise; + } catch (error) { Object.values(batch).forEach(({ future }) => { - future.reject(err); + future.reject(error); }); } } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts index 1a67c14185def8..2c2f79b3d6f513 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts +++ b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts @@ -30,9 +30,8 @@ import { get, identity } from 'lodash'; // @ts-ignore -import { npSetup } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; import { FUNCTIONS_URL } from './consts'; -import { ajaxStream } from './ajax_stream'; import { batchedFetch } from './batched_fetch'; export function getType(node: any) { @@ -69,10 +68,7 @@ export const loadLegacyServerFunctionWrappers = async () => { const types = npSetup.plugins.expressions.__LEGACY.types.toJS(); const { serialize } = serializeProvider(types); const batch = batchedFetch({ - ajaxStream: ajaxStream( - npSetup.core.injectedMetadata.getKibanaVersion(), - npSetup.core.injectedMetadata.getBasePath() - ), + fetchStreaming: npStart.plugins.bfetch.fetchStreaming, serialize, }); diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index 9bab5d25a63032..e39bc59201e7fd 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -64,7 +64,6 @@ Object { const input = { attributes: { title: 'test', - // eslint-disable-next-line max-len fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":"multi","parent":"customer_name"}]', }, @@ -72,7 +71,6 @@ Object { const expected = { attributes: { title: 'test', - // eslint-disable-next-line max-len fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_name"}}}]', }, @@ -466,7 +464,7 @@ Object { }, }; const migratedDoc = migrate(doc); - /* eslint-disable max-len */ + expect(migratedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -492,7 +490,6 @@ Object { "type": "visualization", } `); - /* eslint-enable max-len */ }); it('extracts index patterns from controls', () => { @@ -518,7 +515,7 @@ Object { }, }; const migratedDoc = migrate(doc); - /* eslint-disable max-len */ + expect(migratedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -536,7 +533,6 @@ Object { "type": "visualization", } `); - /* eslint-enable max-len */ }); it('skips extracting savedSearchId when missing', () => { @@ -1601,7 +1597,7 @@ Object { }, }; const migratedDoc = migration(doc); - /* eslint-disable max-len */ + expect(migratedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -1631,7 +1627,6 @@ Object { "type": "dashboard", } `); - /* eslint-enable max-len */ }); test('skips error when panelsJSON is not a string', () => { @@ -1949,7 +1944,7 @@ Object { }, }; const migratedDoc = migration(doc); - /* eslint-disable max-len */ + expect(migratedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -1969,7 +1964,6 @@ Object { "type": "search", } `); - /* eslint-enable max-len */ }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index ef1bcab589c4a0..3482197e381e68 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -48,7 +48,7 @@ import { // @ts-ignore import { initDashboardApp } from './legacy_app'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; -import { NavigationStart } from '../../../navigation/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; import { SharePluginStart } from '../../../../../plugins/share/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index d37cf5d7139ecf..dccc4c11e334f8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -27,7 +27,6 @@ import { import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import { start as navigation } from '../../../navigation/public/legacy'; import './saved_dashboard/saved_dashboards'; import './dashboard_config'; @@ -62,6 +61,6 @@ async function getAngularDependencies(): Promise { }, }; const updatedDoc = extractReferences(doc); - /* eslint-disable max-len */ + expect(updatedDoc).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -61,7 +61,6 @@ Object { ], } `); - /* eslint-enable max-len */ }); test('fails when "type" attribute is missing from a panel', () => { @@ -136,7 +135,7 @@ describe('injectReferences', () => { }, ]; injectReferences(context, references); - /* eslint-disable max-len */ + expect(context).toMatchInlineSnapshot(` Object { "foo": true, @@ -144,7 +143,6 @@ Object { "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\",\\"type\\":\\"visualization\\"},{\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\",\\"type\\":\\"visualization\\"}]", } `); - /* eslint-enable max-len */ }); test('skips when panelsJSON is missing', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ee8f04d168d41e..39a7bb96e11e0e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -536,7 +536,6 @@ function discoverController( $scope.getBucketIntervalToolTipText = () => { return i18n.translate('kbn.discover.bucketIntervalTooltip', { - // eslint-disable-next-line max-len defaultMessage: 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}', values: { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js index 6dc1b4aece9a91..50f4e541bb205c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js @@ -73,7 +73,6 @@ function getFieldValueCounts(params) { error: i18n.translate( 'kbn.discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage', { - // eslint-disable-next-line max-len defaultMessage: 'This field is present in your Elasticsearch mapping but not in the {hitsLength} documents shown in the doc table. You may still be able to visualize or search on it.', values: { diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index f6982e13d7d039..1335dbb01d4a73 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -68,7 +68,7 @@ import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_to import { configureAppAngularModule } from 'ui/legacy_compat'; import { IndexPatterns } from '../../../../../plugins/data/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { NavigationStart } from '../../../navigation/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { createDocTableDirective } from './angular/doc_table/doc_table'; import { createTableHeaderDirective } from './angular/doc_table/components/table_header'; import { diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 35c8da9e3f2655..6ea658682c89da 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -20,7 +20,6 @@ import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { SavedObjectRegistryProvider } from 'ui/saved_objects'; import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; -import { start as navigation } from '../../../navigation/public/legacy'; // Core will be looking for this when loading our plugin in the new platform export const plugin: PluginInitializer = () => { @@ -31,7 +30,7 @@ export const plugin: PluginInitializer = () => { export const pluginInstance = plugin({} as PluginInitializerContext); (async () => { pluginInstance.setup(npSetup.core, npSetup.plugins); - pluginInstance.start(npStart.core, { ...npStart.plugins, navigation }); + pluginInstance.start(npStart.core, npStart.plugins); })(); SavedObjectRegistryProvider.register((savedSearches: any) => { diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index cb84463c184386..b5a8e25dc11eac 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -25,7 +25,7 @@ import './kibana_services'; import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embeddable/public'; import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; import { setAngularModule, setServices } from './kibana_services'; -import { NavigationStart } from '../../../navigation/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public'; import { buildServices } from './helpers/build_services'; import { SharePluginStart } from '../../../../../plugins/share/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts index 029cb620b52c7f..2444d18133af4d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts @@ -24,7 +24,7 @@ import { SavedObjectsImportMissingReferencesError, SavedObjectsImportUnknownError, SavedObjectsImportError, -} from 'src/core/server'; +} from 'src/core/public'; export interface ProcessedImportResponse { failedImports: Array<{ diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 7684f982de7e02..3161576eacf71d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -39,7 +39,7 @@ import { PromiseServiceCreator, StateManagementConfigProvider, } from './legacy_imports'; -import { NavigationStart } from '../../../navigation/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; // @ts-ignore import { initVisualizeApp } from './legacy_app'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 5e9f2fdeb89991..7c22bb3d0eaeb8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -30,7 +30,6 @@ import { } from './legacy_imports'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import { start as navigation } from '../../../navigation/public/legacy'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; /** @@ -64,7 +63,6 @@ async function getAngularDependencies(): Promise ({ State: () => null, AppState: () => null, diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts index 36e7dc81d47084..395cb605878328 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts @@ -17,79 +17,74 @@ * under the License. */ -import sinon from 'sinon'; -import { Server } from 'hapi'; -import { DEFAULT_CSP_RULES } from '../../../../../server/csp'; +import { CspConfig, ICspConfig } from '../../../../../../core/server'; import { createCspCollector } from './csp_collector'; -interface MockConfig { - get: (x: string) => any; -} - -const getMockKbnServer = (mockConfig: MockConfig) => ({ - config: () => mockConfig, +const createMockKbnServer = () => ({ + newPlatform: { + setup: { + core: { + http: { + csp: new CspConfig(), + }, + }, + }, + }, }); -test('fetches whether strict mode is enabled', async () => { - const { collector, mockConfig } = setupCollector(); +describe('csp collector', () => { + let kbnServer: ReturnType; - expect((await collector.fetch()).strict).toEqual(true); + function updateCsp(config: Partial) { + kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config); + } - mockConfig.get.withArgs('csp.strict').returns(false); - expect((await collector.fetch()).strict).toEqual(false); -}); + beforeEach(() => { + kbnServer = createMockKbnServer(); + }); -test('fetches whether the legacy browser warning is enabled', async () => { - const { collector, mockConfig } = setupCollector(); + test('fetches whether strict mode is enabled', async () => { + const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true); + expect((await collector.fetch()).strict).toEqual(true); - mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(false); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false); -}); + updateCsp({ strict: false }); + expect((await collector.fetch()).strict).toEqual(false); + }); -test('fetches whether the csp rules have been changed or not', async () => { - const { collector, mockConfig } = setupCollector(); + test('fetches whether the legacy browser warning is enabled', async () => { + const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false); + expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true); - mockConfig.get.withArgs('csp.rules').returns(['not', 'default']); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true); -}); + updateCsp({ warnLegacyBrowsers: false }); + expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false); + }); -test('does not include raw csp.rules under any property names', async () => { - const { collector } = setupCollector(); - - // It's important that we do not send the value of csp.rules here as it - // can be customized with values that can be identifiable to given - // installs, such as URLs - // - // We use a snapshot here to ensure csp.rules isn't finding its way into the - // payload under some new and unexpected variable name (e.g. cspRules). - expect(await collector.fetch()).toMatchInlineSnapshot(` - Object { - "rulesChangedFromDefault": false, - "strict": true, - "warnLegacyBrowsers": true, - } - `); -}); + test('fetches whether the csp rules have been changed or not', async () => { + const collector = createCspCollector(kbnServer as any); -test('does not arbitrarily fetch other csp configurations (e.g. whitelist only)', async () => { - const { collector, mockConfig } = setupCollector(); + expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false); - mockConfig.get.withArgs('csp.foo').returns('bar'); + updateCsp({ rules: ['not', 'default'] }); + expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true); + }); - expect(await collector.fetch()).not.toHaveProperty('foo'); -}); - -function setupCollector() { - const mockConfig = { get: sinon.stub() }; - mockConfig.get.withArgs('csp.rules').returns(DEFAULT_CSP_RULES); - mockConfig.get.withArgs('csp.strict').returns(true); - mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(true); + test('does not include raw csp rules under any property names', async () => { + const collector = createCspCollector(kbnServer as any); - const mockKbnServer = getMockKbnServer(mockConfig); - - return { mockConfig, collector: createCspCollector(mockKbnServer as Server) }; -} + // It's important that we do not send the value of csp.rules here as it + // can be customized with values that can be identifiable to given + // installs, such as URLs + // + // We use a snapshot here to ensure csp.rules isn't finding its way into the + // payload under some new and unexpected variable name (e.g. cspRules). + expect(await collector.fetch()).toMatchInlineSnapshot(` + Object { + "rulesChangedFromDefault": false, + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); +}); diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts index 9890aaf187a130..6622ed4bef478e 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts @@ -18,7 +18,7 @@ */ import { Server } from 'hapi'; -import { createCSPRuleString, DEFAULT_CSP_RULES } from '../../../../../server/csp'; +import { CspConfig } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -26,18 +26,15 @@ export function createCspCollector(server: Server) { type: 'csp', isReady: () => true, async fetch() { - const config = server.config(); - - // It's important that we do not send the value of csp.rules here as it - // can be customized with values that can be identifiable to given - // installs, such as URLs - const defaultRulesString = createCSPRuleString([...DEFAULT_CSP_RULES]); - const actualRulesString = createCSPRuleString(config.get('csp.rules')); + const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp; return { - strict: config.get('csp.strict'), - warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), - rulesChangedFromDefault: defaultRulesString !== actualRulesString, + strict, + warnLegacyBrowsers, + // It's important that we do not send the value of csp.header here as it + // can be customized with values that can be identifiable to given + // installs, such as URLs + rulesChangedFromDefault: header !== CspConfig.DEFAULT.header, }; }, }; diff --git a/src/legacy/core_plugins/navigation/public/index.scss b/src/legacy/core_plugins/navigation/public/index.scss index 4c969b1a37e8c9..8f2221eb4d4c74 100644 --- a/src/legacy/core_plugins/navigation/public/index.scss +++ b/src/legacy/core_plugins/navigation/public/index.scss @@ -1,3 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import './top_nav_menu/index'; +@import '../../../../plugins/navigation/public/top_nav_menu/index'; diff --git a/src/legacy/core_plugins/navigation/public/index.ts b/src/legacy/core_plugins/navigation/public/index.ts index 439405cc90b57a..7ddb4819cdb3a2 100644 --- a/src/legacy/core_plugins/navigation/public/index.ts +++ b/src/legacy/core_plugins/navigation/public/index.ts @@ -21,12 +21,3 @@ // Once the new platform is ready, they can get removed // and handled by the platform itself in the setup method // of the ExpressionExectorService - -/** @public types */ -export { TopNavMenu, TopNavMenuData } from './top_nav_menu'; -export { NavigationSetup, NavigationStart } from './plugin'; - -import { NavigationPlugin as Plugin } from './plugin'; -export function plugin() { - return new Plugin(); -} diff --git a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts index ea7fe2ee5d9f2b..af908bea7f4b10 100644 --- a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts +++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts @@ -18,7 +18,6 @@ */ import moment from 'moment'; -import { setCanTrackUiMetrics } from 'ui/ui_metric'; // @ts-ignore import { banners, toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; @@ -69,8 +68,6 @@ export function TelemetryOptInProvider($injector: any, chrome: any, sendOptInSta 'telemetryNotifyUserAboutOptInDefault' ) as boolean; - setCanTrackUiMetrics(currentOptInStatus); - const provider = { getBannerId: () => bannerId, getOptInBannerNoticeId: () => optInBannerNoticeId, @@ -116,7 +113,6 @@ export function TelemetryOptInProvider($injector: any, chrome: any, sendOptInSta if (!allowChangingOptInStatus) { return; } - setCanTrackUiMetrics(enabled); const $http = $injector.get('$http'); try { diff --git a/src/legacy/core_plugins/timelion/server/routes/run.js b/src/legacy/core_plugins/timelion/server/routes/run.ts similarity index 50% rename from src/legacy/core_plugins/timelion/server/routes/run.js rename to src/legacy/core_plugins/timelion/server/routes/run.ts index da0e3d5cb21ebd..17f87825cd8b04 100644 --- a/src/legacy/core_plugins/timelion/server/routes/run.js +++ b/src/legacy/core_plugins/timelion/server/routes/run.ts @@ -16,13 +16,44 @@ * specific language governing permissions and limitations * under the License. */ - +import Joi from 'joi'; import Bluebird from 'bluebird'; import _ from 'lodash'; +import { Legacy } from 'kibana'; +// @ts-ignore import chainRunnerFn from '../handlers/chain_runner.js'; -const timelionDefaults = require('../lib/get_namespaced_settings')(); +// @ts-ignore +import getNamespacesSettings from '../lib/get_namespaced_settings'; +// @ts-ignore +import getTlConfig from '../handlers/lib/tl_config'; + +const timelionDefaults = getNamespacesSettings(); + +export interface TimelionRequestQuery { + payload: { + sheet: string[]; + extended?: { + es: { + filter: { + bool: { + filter: string[] | object; + must: string[]; + should: string[]; + must_not: string[]; + }; + }; + }; + }; + }; + time?: { + from?: string; + interval: string; + timezone: string; + to?: string; + }; +} -function formatErrorResponse(e, h) { +function formatErrorResponse(e: Error, h: Legacy.ResponseToolkit) { return h .response({ title: e.toString(), @@ -31,34 +62,50 @@ function formatErrorResponse(e, h) { .code(500); } -export function runRoute(server) { +const requestPayload = { + payload: Joi.object({ + sheet: Joi.array() + .items(Joi.string()) + .required(), + extended: Joi.object({ + es: Joi.object({ + filter: Joi.object({ + bool: Joi.object({ + filter: Joi.array().allow(null), + must: Joi.array().allow(null), + should: Joi.array().allow(null), + must_not: Joi.array().allow(null), + }), + }), + }), + }).optional(), + time: Joi.object({ + from: Joi.string(), + interval: Joi.string().required(), + timezone: Joi.string().required(), + to: Joi.string(), + }).required(), + }), +}; + +export function runRoute(server: Legacy.Server) { server.route({ - method: ['POST', 'GET'], + method: 'POST', path: '/api/timelion/run', - handler: async (request, h) => { + options: { + validate: requestPayload, + }, + handler: async (request: Legacy.Request & TimelionRequestQuery, h: Legacy.ResponseToolkit) => { try { const uiSettings = await request.getUiSettingsService().getAll(); - const tlConfig = require('../handlers/lib/tl_config.js')({ + const tlConfig = getTlConfig({ server, request, settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. }); - const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all( - chainRunner.processRequest( - request.payload || { - sheet: [request.query.expression], - time: { - from: request.query.from, - to: request.query.to, - interval: request.query.interval, - timezone: request.query.timezone, - }, - } - ) - ); + const sheet = await Bluebird.all(chainRunner.processRequest(request.payload)); return { sheet, diff --git a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/es_response.js b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/es_response.js index 22352258b2f518..65aed311e232b8 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/es_response.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/es_response.js @@ -17,8 +17,6 @@ * under the License. */ -/* eslint-disable quotes */ - /* Really didn't want to do this, but testing the agg flatten logic in units isn't really possible since the functions depend on each other diff --git a/src/legacy/core_plugins/timelion/server/types.ts b/src/legacy/core_plugins/timelion/server/types.ts index 4971fa34863d3b..e612bc14a0daa5 100644 --- a/src/legacy/core_plugins/timelion/server/types.ts +++ b/src/legacy/core_plugins/timelion/server/types.ts @@ -24,3 +24,5 @@ export { TimelionFunctionArgsSuggestion, TimelionFunctionArgsTypes, } from './lib/classes/timelion_function'; + +export { TimelionRequestQuery } from './routes/run'; diff --git a/src/legacy/core_plugins/ui_metric/README.md b/src/legacy/core_plugins/ui_metric/README.md deleted file mode 100644 index 90855faff61a69..00000000000000 --- a/src/legacy/core_plugins/ui_metric/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# UI Metric app - -## Purpose - -The purpose of the UI Metric app is to provide a tool for gathering data on how users interact with -various UIs within Kibana. It's useful for gathering _aggregate_ information, e.g. "How many times -has Button X been clicked" or "How many times has Page Y been viewed". - -With some finagling, it's even possible to add more meaning to the info you gather, such as "How many -visualizations were created in less than 5 minutes". - -### What it doesn't do - -The UI Metric app doesn't gather any metadata around a user interaction, e.g. the user's identity, -the name of a dashboard they've viewed, or the timestamp of the interaction. - -## How to use it - -To track a user interaction, import the `createUiStatsReporter` helper function from UI Metric app: - -```js -import { createUiStatsReporter, METRIC_TYPE } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public'; -const trackMetric = createUiStatsReporter(``); -trackMetric(METRIC_TYPE.CLICK, ``); -trackMetric('click', ``); -``` - -Metric Types: - - `METRIC_TYPE.CLICK` for tracking clicks `trackMetric(METRIC_TYPE.CLICK, 'my_button_clicked');` - - `METRIC_TYPE.LOADED` for a component load or page load `trackMetric(METRIC_TYPE.LOADED', 'my_component_loaded');` - - `METRIC_TYPE.COUNT` for a tracking a misc count `trackMetric(METRIC_TYPE.COUNT', 'my_counter', });` - -Call this function whenever you would like to track a user interaction within your app. The function -accepts two arguments, `metricType` and `eventNames`. These should be underscore-delimited strings. -For example, to track the `my_event` metric in the app `my_app` call `trackUiMetric(METRIC_TYPE.*, 'my_event)`. - -That's all you need to do! - -To track multiple metrics within a single request, provide an array of events, e.g. `trackMetric(METRIC_TYPE.*, ['my_event1', 'my_event2', 'my_event3'])`. - -### Disallowed characters - -The colon character (`:`) should not be used in app name or event names. Colons play -a special role in how metrics are stored as saved objects. - -### Tracking timed interactions - -If you want to track how long it takes a user to do something, you'll need to implement the timing -logic yourself. You'll also need to predefine some buckets into which the UI metric can fall. -For example, if you're timing how long it takes to create a visualization, you may decide to -measure interactions that take less than 1 minute, 1-5 minutes, 5-20 minutes, and longer than 20 minutes. -To track these interactions, you'd use the timed length of the interaction to determine whether to -use a `eventName` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`. - -## How it works - -Under the hood, your app and metric type will be stored in a saved object of type `user-metric` and the -ID `ui-metric:my_app:my_metric`. This saved object will have a `count` property which will be incremented -every time the above URI is hit. - -These saved objects are automatically consumed by the stats API and surfaced under the -`ui_metric` namespace. - -```json -{ - "ui_metric": { - "my_app": [ - { - "key": "my_metric", - "value": 3 - } - ] - } -} -``` - -By storing these metrics and their counts as key-value pairs, we can add more metrics without having -to worry about exceeding the 1000-field soft limit in Elasticsearch. \ No newline at end of file diff --git a/src/legacy/core_plugins/ui_metric/index.ts b/src/legacy/core_plugins/ui_metric/index.ts index 964e3497accba7..86d75a9f1818a8 100644 --- a/src/legacy/core_plugins/ui_metric/index.ts +++ b/src/legacy/core_plugins/ui_metric/index.ts @@ -18,10 +18,7 @@ */ import { resolve } from 'path'; -import JoiNamespace from 'joi'; -import { Server } from 'hapi'; import { Legacy } from '../../../../kibana'; -import { registerUiMetricRoute } from './server/routes/api/ui_metric'; // eslint-disable-next-line import/no-default-export export default function(kibana: any) { @@ -29,25 +26,16 @@ export default function(kibana: any) { id: 'ui_metric', require: ['kibana', 'elasticsearch'], publicDir: resolve(__dirname, 'public'), - config(Joi: typeof JoiNamespace) { - return Joi.object({ - enabled: Joi.boolean().default(true), - debug: Joi.boolean().default(Joi.ref('$dev')), - }).default(); - }, uiExports: { - injectDefaultVars(server: Server) { - const config = server.config(); - return { - uiMetricEnabled: config.get('ui_metric.enabled'), - debugUiMetric: config.get('ui_metric.debug'), - }; - }, mappings: require('./mappings.json'), - hacks: ['plugins/ui_metric/hacks/ui_metric_init'], }, init(server: Legacy.Server) { - registerUiMetricRoute(server); + const { getSavedObjectsRepository } = server.savedObjects; + const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); + const internalRepository = getSavedObjectsRepository(callWithInternalUser); + const { usageCollection } = server.newPlatform.setup.plugins; + + usageCollection.registerLegacySavedObjects(internalRepository); }, }); } diff --git a/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts b/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts deleted file mode 100644 index 983434f09922b3..00000000000000 --- a/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts +++ /dev/null @@ -1,45 +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. - */ - -// @ts-ignore -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; -import { kfetch } from 'ui/kfetch'; -import { - createAnalyticsReporter, - setTelemetryReporter, - trackUserAgent, -} from '../services/telemetry_analytics'; -import { isUnauthenticated } from '../../../telemetry/public/services'; - -function telemetryInit($injector: any) { - const uiMetricEnabled = chrome.getInjected('uiMetricEnabled'); - const debug = chrome.getInjected('debugUiMetric'); - if (!uiMetricEnabled || isUnauthenticated()) { - return; - } - const localStorage = $injector.get('localStorage'); - - const uiReporter = createAnalyticsReporter({ localStorage, debug, kfetch }); - setTelemetryReporter(uiReporter); - uiReporter.start(); - trackUserAgent('kibana'); -} - -uiModules.get('kibana').run(telemetryInit); diff --git a/src/legacy/core_plugins/ui_metric/public/index.ts b/src/legacy/core_plugins/ui_metric/public/index.ts index 5c327234b1e7cd..19246b571cb842 100644 --- a/src/legacy/core_plugins/ui_metric/public/index.ts +++ b/src/legacy/core_plugins/ui_metric/public/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { createUiStatsReporter, trackUserAgent } from './services/telemetry_analytics'; +export { createUiStatsReporter } from './services/telemetry_analytics'; export { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; diff --git a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts index ee928b8a1d9ee8..0e517e6ff22449 100644 --- a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts +++ b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts @@ -16,61 +16,9 @@ * specific language governing permissions and limitations * under the License. */ +import { npSetup } from 'ui/new_platform'; -import { Reporter, UiStatsMetricType } from '@kbn/analytics'; -// @ts-ignore -import { addSystemApiHeader } from 'ui/system_api'; - -let telemetryReporter: Reporter; - -export const setTelemetryReporter = (aTelemetryReporter: Reporter): void => { - telemetryReporter = aTelemetryReporter; -}; - -export const getTelemetryReporter = () => { - return telemetryReporter; -}; - -export const createUiStatsReporter = (appName: string) => ( - type: UiStatsMetricType, - eventNames: string | string[], - count?: number -): void => { - if (telemetryReporter) { - return telemetryReporter.reportUiStats(appName, type, eventNames, count); - } +export const createUiStatsReporter = (appName: string) => { + const { usageCollection } = npSetup.plugins; + return usageCollection.reportUiStats.bind(usageCollection, appName); }; - -export const trackUserAgent = (appName: string) => { - if (telemetryReporter) { - return telemetryReporter.reportUserAgent(appName); - } -}; - -interface AnalyicsReporterConfig { - localStorage: any; - debug: boolean; - kfetch: any; -} - -export function createAnalyticsReporter(config: AnalyicsReporterConfig) { - const { localStorage, debug, kfetch } = config; - - return new Reporter({ - debug, - storage: localStorage, - async http(report) { - const response = await kfetch({ - method: 'POST', - pathname: '/api/telemetry/report', - body: JSON.stringify(report), - headers: addSystemApiHeader({}), - }); - - if (response.status !== 'ok') { - throw Error('Unable to store report.'); - } - return response; - }, - }); -} diff --git a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts b/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts deleted file mode 100644 index e2de23ea806e44..00000000000000 --- a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts +++ /dev/null @@ -1,102 +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 Joi from 'joi'; -import { Report } from '@kbn/analytics'; -import { Server } from 'hapi'; - -export async function storeReport(server: any, report: Report) { - const { getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - - const uiStatsMetrics = report.uiStatsMetrics ? Object.entries(report.uiStatsMetrics) : []; - const userAgents = report.userAgent ? Object.entries(report.userAgent) : []; - return Promise.all([ - ...userAgents.map(async ([key, metric]) => { - const { userAgent } = metric; - const savedObjectId = `${key}:${userAgent}`; - return await internalRepository.create( - 'ui-metric', - { count: 1 }, - { - id: savedObjectId, - overwrite: true, - } - ); - }), - ...uiStatsMetrics.map(async ([key, metric]) => { - const { appName, eventName } = metric; - const savedObjectId = `${appName}:${eventName}`; - return await internalRepository.incrementCounter('ui-metric', savedObjectId, 'count'); - }), - ]); -} - -export function registerUiMetricRoute(server: Server) { - server.route({ - method: 'POST', - path: '/api/telemetry/report', - options: { - validate: { - payload: Joi.object({ - reportVersion: Joi.number().optional(), - userAgent: Joi.object() - .pattern( - /.*/, - Joi.object({ - key: Joi.string().required(), - type: Joi.string().required(), - appName: Joi.string().required(), - userAgent: Joi.string().required(), - }) - ) - .allow(null) - .optional(), - uiStatsMetrics: Joi.object() - .pattern( - /.*/, - Joi.object({ - key: Joi.string().required(), - type: Joi.string().required(), - appName: Joi.string().required(), - eventName: Joi.string().required(), - stats: Joi.object({ - min: Joi.number(), - sum: Joi.number(), - max: Joi.number(), - avg: Joi.number(), - }).allow(null), - }) - ) - .allow(null), - }), - }, - }, - handler: async (req: any, h: any) => { - try { - const report = req.payload; - await storeReport(server, report); - return { status: 'ok' }; - } catch (error) { - return { status: 'fail' }; - } - }, - }); -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js index d64038dddb3304..c1d767656f4515 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js @@ -17,7 +17,6 @@ * under the License. */ -/* eslint max-len:0 */ /* eslint-disable jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ // Markdown builder is not yet properly accessible diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js index 97d974f608dbcd..865bd67ea9c354 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js @@ -21,7 +21,6 @@ import React from 'react'; export const bombIcon = () => ( - {/* eslint-disable-next-line max-len */} ); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js index d20af55db69fb1..9ec45907d4636a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js @@ -21,7 +21,6 @@ import React from 'react'; export const fireIcon = () => ( - {/* eslint-disable-next-line max-len */} ); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js index 6973e93b9f17da..32a75b1268d06d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js @@ -17,7 +17,6 @@ * under the License. */ -/* eslint max-len:0 */ const filter = metric => metric.type === 'filter_ratio'; import { bucketTransform } from '../../helpers/bucket_transform'; import _ from 'lodash'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js index 658c5cc83a3173..a05c414f1a3116 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js @@ -17,7 +17,6 @@ * under the License. */ -/* eslint max-len:0 */ const filter = metric => metric.type === 'filter_ratio'; import { bucketTransform } from '../../helpers/bucket_transform'; import _ from 'lodash'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js index 7479912ad06a2b..2f25f70610a81e 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js @@ -109,7 +109,6 @@ export class EsQueryParser { ) { throw new Error( i18n.translate('visTypeVega.esQueryParser.legacyContextCanBeTrueErrorMessage', { - // eslint-disable-next-line max-len defaultMessage: 'Legacy {legacyContext} can either be {trueValue} (ignores time range picker), or it can be the name of the time field, e.g. {timestampParam}', values: { diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a54e5d52bfec10..00993b01179efe 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -22,7 +22,6 @@ import os from 'os'; import { join } from 'path'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getDataPath } from '../../../core/server/path'; // Still used by optimize config schema -import { DEFAULT_CSP_RULES, DEFAULT_CSP_STRICT, DEFAULT_CSP_WARN_LEGACY_BROWSERS } from '../csp'; const HANDLED_IN_NEW_PLATFORM = Joi.any().description( 'This key is handled in the new platform ONLY' @@ -51,13 +50,7 @@ export default () => exclusive: Joi.boolean().default(false), }).default(), - csp: Joi.object({ - rules: Joi.array() - .items(Joi.string()) - .default(DEFAULT_CSP_RULES), - strict: Joi.boolean().default(DEFAULT_CSP_STRICT), - warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_WARN_LEGACY_BROWSERS), - }).default(), + csp: HANDLED_IN_NEW_PLATFORM, cpu: Joi.object({ cgroup: Joi.object({ diff --git a/src/legacy/server/csp/index.test.ts b/src/legacy/server/csp/index.test.ts deleted file mode 100644 index fbb63bd49bf6fe..00000000000000 --- a/src/legacy/server/csp/index.test.ts +++ /dev/null @@ -1,61 +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 { - createCSPRuleString, - DEFAULT_CSP_RULES, - DEFAULT_CSP_STRICT, - DEFAULT_CSP_WARN_LEGACY_BROWSERS, -} from './'; - -// CSP rules aren't strictly additive, so any change can potentially expand or -// restrict the policy in a way we consider a breaking change. For that reason, -// we test the default rules exactly so any change to those rules gets flagged -// for manual review. In otherwords, this test is intentionally fragile to draw -// extra attention if defaults are modified in any way. -// -// A test failure here does not necessarily mean this change cannot be made, -// but any change here should undergo sufficient scrutiny by the Kibana -// security team. -// -// The tests use inline snapshots to make it as easy as possible to identify -// the nature of a change in defaults during a PR review. -test('default CSP rules', () => { - expect(DEFAULT_CSP_RULES).toMatchInlineSnapshot(` - Array [ - "script-src 'unsafe-eval' 'self'", - "worker-src blob: 'self'", - "style-src 'unsafe-inline' 'self'", - ] - `); -}); - -test('CSP strict mode defaults to disabled', () => { - expect(DEFAULT_CSP_STRICT).toBe(true); -}); - -test('CSP legacy browser warning defaults to enabled', () => { - expect(DEFAULT_CSP_WARN_LEGACY_BROWSERS).toBe(true); -}); - -test('createCSPRuleString() converts an array of rules into a CSP header string', () => { - const csp = createCSPRuleString([`string-src 'self'`, 'worker-src blob:', 'img-src data: blob:']); - - expect(csp).toMatchInlineSnapshot(`"string-src 'self'; worker-src blob:; img-src data: blob:"`); -}); diff --git a/src/legacy/server/logging/log_interceptor.test.js b/src/legacy/server/logging/log_interceptor.test.js index 853a109c9bc369..c99373ebe0a639 100644 --- a/src/legacy/server/logging/log_interceptor.test.js +++ b/src/legacy/server/logging/log_interceptor.test.js @@ -147,7 +147,7 @@ describe('server logging LogInterceptor', () => { describe('#downgradeIfHTTPWhenHTTPS', () => { it('transforms http requests when serving https errors', () => { const message = - '40735139278848:error:1407609C:SSL routines:SSL23_GET_CLIENT_HELLO:http request:../deps/openssl/openssl/ssl/s23_srvr.c:394'; // eslint-disable-line max-len + '40735139278848:error:1407609C:SSL routines:SSL23_GET_CLIENT_HELLO:http request:../deps/openssl/openssl/ssl/s23_srvr.c:394'; const interceptor = new LogInterceptor(); const event = stubClientErrorEvent({ message }); assertDowngraded(interceptor.downgradeIfHTTPWhenHTTPS(event)); diff --git a/src/legacy/server/sample_data/data_sets/ecommerce/field_mappings.js b/src/legacy/server/sample_data/data_sets/ecommerce/field_mappings.js index 850489703ee084..5fecf8699bec82 100644 --- a/src/legacy/server/sample_data/data_sets/ecommerce/field_mappings.js +++ b/src/legacy/server/sample_data/data_sets/ecommerce/field_mappings.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - export const fieldMappings = { category: { type: 'text', diff --git a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js index c8632a3f577573..1438ce4bbc3cdb 100644 --- a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - import { i18n } from '@kbn/i18n'; export const getSavedObjects = () => [ diff --git a/src/legacy/server/sample_data/data_sets/flights/field_mappings.js b/src/legacy/server/sample_data/data_sets/flights/field_mappings.js index 7557e1a37c0e00..ffd0b01297d3bf 100644 --- a/src/legacy/server/sample_data/data_sets/flights/field_mappings.js +++ b/src/legacy/server/sample_data/data_sets/flights/field_mappings.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - export const fieldMappings = { timestamp: { type: 'date', diff --git a/src/legacy/server/sample_data/data_sets/flights/saved_objects.js b/src/legacy/server/sample_data/data_sets/flights/saved_objects.js index ba6a8034f54570..69e77e67c5d9c6 100644 --- a/src/legacy/server/sample_data/data_sets/flights/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/flights/saved_objects.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - import { i18n } from '@kbn/i18n'; export const getSavedObjects = () => [ diff --git a/src/legacy/server/sample_data/data_sets/logs/field_mappings.js b/src/legacy/server/sample_data/data_sets/logs/field_mappings.js index c6259d17cc9c90..af1238e62dc86c 100644 --- a/src/legacy/server/sample_data/data_sets/logs/field_mappings.js +++ b/src/legacy/server/sample_data/data_sets/logs/field_mappings.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - export const fieldMappings = { request: { type: 'text', diff --git a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js index 61e6317dc3917f..9ce6e0d001e9e1 100644 --- a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js @@ -17,9 +17,6 @@ * under the License. */ -/* eslint max-len: 0 */ -/* eslint quotes: 0 */ - import { i18n } from '@kbn/i18n'; export const getSavedObjects = () => [ diff --git a/src/legacy/server/sass/build.test.js b/src/legacy/server/sass/build.test.js index bde552e9df00ff..7092f6ad129217 100644 --- a/src/legacy/server/sass/build.test.js +++ b/src/legacy/server/sass/build.test.js @@ -141,7 +141,6 @@ it('rewrites url imports', async () => { }, }).build(); - /* eslint-disable max-len */ expect(readFileSync(targetPath, 'utf8').replace(/(\/\*# sourceMappingURL=).*( \*\/)/, '$1...$2')) .toMatchInlineSnapshot(` "/* 1 */ diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js b/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js index 926d528a6b88e8..6397c3b0b41bb5 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js +++ b/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js @@ -77,7 +77,6 @@ describe('buildHierarchicalData convertTable', () => { { id: 'col-5-agg_1', name: 'Average bytes' }, ], rows: [ - /* eslint-disable max-len */ { 'col-0-agg_2': 'png', 'col-2-agg_3': 'IT', @@ -174,7 +173,6 @@ describe('buildHierarchicalData convertTable', () => { 'col-3-agg_1': 8293, 'col-5-agg_1': 3029, }, - /* eslint-enable max-len */ ], }; dimensions = { diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/ui/public/agg_types/buckets/histogram.ts index d287cbddb7834c..623cffe4915ad6 100644 --- a/src/legacy/ui/public/agg_types/buckets/histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/histogram.ts @@ -123,7 +123,6 @@ export const histogramBucketAgg = new BucketAggType({ if (e.name === 'AbortError') return; toastNotifications.addWarning( i18n.translate('common.ui.aggTypes.histogram.missingMaxMinValuesWarning', { - // eslint-disable-next-line max-len defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', }) diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 7f72529153a5a5..0788afd5f74ebd 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -20,7 +20,7 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; -import { start as navigation } from '../../../core_plugins/navigation/public/legacy'; +import { npStart } from 'ui/new_platform'; const module = uiModules.get('kibana'); @@ -116,4 +116,4 @@ export const createTopNavHelper = ({ TopNavMenu }) => reactDirective => { ]); }; -module.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +module.directive('kbnTopNavHelper', createTopNavHelper(npStart.plugins.navigation.ui)); diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts index 5c7f7be0603741..a0a917bc480499 100644 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts @@ -21,21 +21,26 @@ import { coreMock } from '../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; import { embeddablePluginMock } from '../../../../../plugins/embeddable/public/mocks'; +import { navigationPluginMock } from '../../../../../plugins/navigation/public/mocks'; import { expressionsPluginMock } from '../../../../../plugins/expressions/public/mocks'; import { inspectorPluginMock } from '../../../../../plugins/inspector/public/mocks'; import { uiActionsPluginMock } from '../../../../../plugins/ui_actions/public/mocks'; +import { usageCollectionPluginMock } from '../../../../../plugins/usage_collection/public/mocks'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export const pluginsMock = { createSetup: () => ({ data: dataPluginMock.createSetupContract(), + navigation: navigationPluginMock.createSetupContract(), embeddable: embeddablePluginMock.createSetupContract(), inspector: inspectorPluginMock.createSetupContract(), expressions: expressionsPluginMock.createSetupContract(), uiActions: uiActionsPluginMock.createSetupContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), }), createStart: () => ({ data: dataPluginMock.createStartContract(), + navigation: navigationPluginMock.createStartContract(), embeddable: embeddablePluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), expressions: expressionsPluginMock.createStartContract(), diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3c32a4eddfca73..20a7e0d5dde8d3 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -19,6 +19,7 @@ import sinon from 'sinon'; import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats'; +import { METRIC_TYPE } from '@kbn/analytics'; const mockObservable = () => { return { @@ -50,6 +51,11 @@ export const npSetup = { uiSettings: mockUiSettings, }, plugins: { + usageCollection: { + allowTrackUserAgent: sinon.fake(), + reportUiStats: sinon.fake(), + METRIC_TYPE, + }, embeddable: { registerEmbeddableFactory: sinon.fake(), }, @@ -238,6 +244,11 @@ export const npStart = { register: sinon.fake(), }, }, + navigation: { + ui: { + TopNavMenu: mockComponent, + }, + }, }, }; diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 547d22c58cfc1d..138bb4135c507d 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -32,22 +32,30 @@ import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/publ import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public'; import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public'; import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; -import { LicensingPluginSetup } from '../../../../../x-pack/plugins/licensing/common/types'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; +import { + NavigationPublicPluginSetup, + NavigationPublicPluginStart, +} from '../../../../plugins/navigation/public'; export interface PluginsSetup { + bfetch: BfetchPublicSetup; data: ReturnType; embeddable: IEmbeddableSetup; expressions: ReturnType; home: HomePublicPluginSetup; inspector: InspectorSetup; uiActions: IUiActionsSetup; + navigation: NavigationPublicPluginSetup; dev_tools: DevToolsSetup; kibana_legacy: KibanaLegacySetup; share: SharePluginSetup; - licensing: LicensingPluginSetup; + usageCollection: UsageCollectionSetup; } export interface PluginsStart { + bfetch: BfetchPublicStart; data: ReturnType; embeddable: IEmbeddableStart; eui_utils: EuiUtilsStart; @@ -55,6 +63,7 @@ export interface PluginsStart { home: HomePublicPluginStart; inspector: InspectorStart; uiActions: IUiActionsStart; + navigation: NavigationPublicPluginStart; dev_tools: DevToolsStart; kibana_legacy: KibanaLegacyStart; share: SharePluginStart; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index aee6f10e93dbd6..e3189f02bbbf20 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -28,7 +28,6 @@ import { AppBootstrap } from './bootstrap'; import { mergeVariables } from './lib'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; -import { createCSPRuleString } from '../../server/csp'; export function uiRenderMixin(kbnServer, server, config) { function replaceInjectedVars(request, injectedVars) { @@ -248,9 +247,10 @@ export function uiRenderMixin(kbnServer, server, config) { } ) ); + const { strict, warnLegacyBrowsers, header } = kbnServer.newPlatform.setup.core.http.csp; const response = h.view('ui_app', { - strictCsp: config.get('csp.strict'), + strictCsp: strict, uiPublicUrl: `${basePath}/ui`, bootstrapScriptUrl: `${basePath}/bundles/app/${app.getId()}/bootstrap.js`, i18n: (id, options) => i18n.translate(id, options), @@ -268,7 +268,7 @@ export function uiRenderMixin(kbnServer, server, config) { translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`, }, csp: { - warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), + warnLegacyBrowsers, }, vars: await replaceInjectedVars( request, @@ -285,8 +285,7 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - const csp = createCSPRuleString(config.get('csp.rules')); - response.header('content-security-policy', csp); + response.header('content-security-policy', header); return response; } diff --git a/src/plugins/bfetch/README.md b/src/plugins/bfetch/README.md new file mode 100644 index 00000000000000..9c18720e30d969 --- /dev/null +++ b/src/plugins/bfetch/README.md @@ -0,0 +1,9 @@ +# `bfetch` plugin + +`bfetch` allows to batch HTTP requests and streams responses back. + + +## Reference + +- [Browser](./docs/browser/reference.md) +- Server diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts b/src/plugins/bfetch/common/index.ts similarity index 84% rename from src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts rename to src/plugins/bfetch/common/index.ts index dd139bbd6410fd..afa73ade800840 100644 --- a/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts +++ b/src/plugins/bfetch/common/index.ts @@ -17,6 +17,5 @@ * under the License. */ -export { TopNavMenu } from './top_nav_menu'; -export { TopNavMenuData } from './top_nav_menu_data'; -export * from './top_nav_menu_extensions_registry'; +export * from './util'; +export * from './streaming'; diff --git a/src/legacy/core_plugins/ui_metric/common/index.ts b/src/plugins/bfetch/common/streaming/index.ts similarity index 94% rename from src/legacy/core_plugins/ui_metric/common/index.ts rename to src/plugins/bfetch/common/streaming/index.ts index 02aa55c30965d2..d8f7b5091eb8f6 100644 --- a/src/legacy/core_plugins/ui_metric/common/index.ts +++ b/src/plugins/bfetch/common/streaming/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export const API_BASE_PATH = '/api/ui_metric'; +export * from './types'; diff --git a/src/legacy/ui/public/ui_metric/index.ts b/src/plugins/bfetch/common/streaming/types.ts similarity index 80% rename from src/legacy/ui/public/ui_metric/index.ts rename to src/plugins/bfetch/common/streaming/types.ts index ad43f27201ce4d..1ee92edbc89ffa 100644 --- a/src/legacy/ui/public/ui_metric/index.ts +++ b/src/plugins/bfetch/common/streaming/types.ts @@ -17,12 +17,8 @@ * under the License. */ -let _canTrackUiMetrics = false; +import { Observable } from 'rxjs'; -export function setCanTrackUiMetrics(flag: boolean) { - _canTrackUiMetrics = flag; -} - -export function getCanTrackUiMetrics(): boolean { - return _canTrackUiMetrics; +export interface StreamingResponseHandler { + onRequest(payload: Payload): Observable; } diff --git a/src/plugins/bfetch/common/types.ts b/src/plugins/bfetch/common/types.ts new file mode 100644 index 00000000000000..132f0e06e2c269 --- /dev/null +++ b/src/plugins/bfetch/common/types.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './streaming/types'; diff --git a/src/plugins/bfetch/common/util/index.ts b/src/plugins/bfetch/common/util/index.ts new file mode 100644 index 00000000000000..02843af9b43503 --- /dev/null +++ b/src/plugins/bfetch/common/util/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './remove_leading_slash'; diff --git a/src/plugins/bfetch/common/util/remove_leading_slash.ts b/src/plugins/bfetch/common/util/remove_leading_slash.ts new file mode 100644 index 00000000000000..99b185a3d4848d --- /dev/null +++ b/src/plugins/bfetch/common/util/remove_leading_slash.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const removeLeadingSlash = (text: string) => (text[0] === '/' ? text.substr(1) : text); diff --git a/src/plugins/bfetch/docs/browser/reference.md b/src/plugins/bfetch/docs/browser/reference.md new file mode 100644 index 00000000000000..47a67c08a4c1f7 --- /dev/null +++ b/src/plugins/bfetch/docs/browser/reference.md @@ -0,0 +1,15 @@ +# `bfetch` browser reference + +- [`fetchStreaming`](#fetchStreaming) + + +## `fetchStreaming` + +Executes an HTTP request and expects that server streams back results using +HTTP/1 `Transfer-Encoding: chunked`. + +```ts +const { stream } = bfetch.fetchStreaming({ url: 'http://elastic.co' }); + +stream.subscribe(value => {}); +``` \ No newline at end of file diff --git a/src/plugins/bfetch/kibana.json b/src/plugins/bfetch/kibana.json new file mode 100644 index 00000000000000..462d2f4b8bb7d9 --- /dev/null +++ b/src/plugins/bfetch/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "bfetch", + "version": "kibana", + "server": true, + "ui": true +} diff --git a/src/plugins/bfetch/public/index.ts b/src/plugins/bfetch/public/index.ts new file mode 100644 index 00000000000000..a57dd77fe7e678 --- /dev/null +++ b/src/plugins/bfetch/public/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { PluginInitializerContext } from '../../../core/public'; +import { BfetchPublicPlugin } from './plugin'; + +export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicApi } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new BfetchPublicPlugin(initializerContext); +} diff --git a/src/plugins/bfetch/public/mocks.ts b/src/plugins/bfetch/public/mocks.ts new file mode 100644 index 00000000000000..e8caf5c9cb739d --- /dev/null +++ b/src/plugins/bfetch/public/mocks.ts @@ -0,0 +1,63 @@ +/* + * 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 { BfetchPublicSetup, BfetchPublicStart } from '.'; +import { plugin as pluginInitializer } from '.'; +import { coreMock } from '../../../core/public/mocks'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + fetchStreaming: jest.fn(), + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = { + fetchStreaming: jest.fn(), + }; + + return startContract; +}; + +const createPlugin = async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = await plugin.setup(coreSetup, {}); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: async () => await plugin.start(coreStart, {}), + }; +}; + +export const uiActionsPluginMock = { + createSetupContract, + createStartContract, + createPlugin, +}; diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts new file mode 100644 index 00000000000000..db18a15afa1e79 --- /dev/null +++ b/src/plugins/bfetch/public/plugin.ts @@ -0,0 +1,81 @@ +/* + * 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 { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; +import { fetchStreaming as fetchStreamingStatic, FetchStreamingParams } from './streaming'; +import { removeLeadingSlash } from '../common'; + +// eslint-disable-next-line +export interface BfetchPublicSetupDependencies {} + +// eslint-disable-next-line +export interface BfetchPublicStartDependencies {} + +export interface BfetchPublicApi { + fetchStreaming: (params: FetchStreamingParams) => ReturnType; +} + +export type BfetchPublicSetup = BfetchPublicApi; +export type BfetchPublicStart = BfetchPublicApi; + +export class BfetchPublicPlugin + implements + Plugin< + BfetchPublicSetup, + BfetchPublicStart, + BfetchPublicSetupDependencies, + BfetchPublicStartDependencies + > { + private api!: BfetchPublicApi; + + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: BfetchPublicSetupDependencies): BfetchPublicSetup { + const { version } = this.initializerContext.env.packageInfo; + const basePath = core.http.basePath.get(); + + const fetchStreaming = this.fetchStreaming(version, basePath); + + this.api = { + fetchStreaming, + }; + + return this.api; + } + + public start(core: CoreStart, plugins: BfetchPublicStartDependencies): BfetchPublicStart { + return this.api; + } + + public stop() {} + + private fetchStreaming = ( + version: string, + basePath: string + ): BfetchPublicSetup['fetchStreaming'] => params => + fetchStreamingStatic({ + ...params, + url: `${basePath}/${removeLeadingSlash(params.url)}`, + headers: { + 'Content-Type': 'application/json', + 'kbn-version': version, + ...(params.headers || {}), + }, + }); +} diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts new file mode 100644 index 00000000000000..e59af71cb76bc6 --- /dev/null +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -0,0 +1,248 @@ +/* + * 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 { fetchStreaming } from './fetch_streaming'; +import { mockXMLHttpRequest } from '../test_helpers/xhr'; + +const tick = () => new Promise(resolve => setTimeout(resolve, 1)); + +const setup = () => { + const { xhr, XMLHttpRequest } = mockXMLHttpRequest(); + window.XMLHttpRequest = XMLHttpRequest; + return { xhr }; +}; + +test('returns XHR request', () => { + setup(); + const { xhr } = fetchStreaming({ + url: 'http://example.com', + }); + expect(typeof xhr.readyState).toBe('number'); +}); + +test('returns promise', () => { + setup(); + const { promise } = fetchStreaming({ + url: 'http://example.com', + }); + expect(typeof promise.then).toBe('function'); +}); + +test('returns stream', () => { + setup(); + const { stream } = fetchStreaming({ + url: 'http://example.com', + }); + expect(typeof stream.subscribe).toBe('function'); +}); + +test('promise resolves when request completes', async () => { + const env = setup(); + const { promise } = fetchStreaming({ + url: 'http://example.com', + }); + + let resolved = false; + promise.then(() => (resolved = true)); + + await tick(); + expect(resolved).toBe(false); + + (env.xhr as any).responseText = 'foo'; + env.xhr.onprogress!({} as any); + + await tick(); + expect(resolved).toBe(false); + + (env.xhr as any).responseText = 'foo\nbar'; + env.xhr.onprogress!({} as any); + + await tick(); + expect(resolved).toBe(false); + + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 200; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(resolved).toBe(true); +}); + +test('streams incoming text as it comes through', async () => { + const env = setup(); + const { stream } = fetchStreaming({ + url: 'http://example.com', + }); + + const spy = jest.fn(); + stream.subscribe(spy); + + await tick(); + expect(spy).toHaveBeenCalledTimes(0); + + (env.xhr as any).responseText = 'foo'; + env.xhr.onprogress!({} as any); + + await tick(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('foo'); + + (env.xhr as any).responseText = 'foo\nbar'; + env.xhr.onprogress!({} as any); + + await tick(); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith('\nbar'); + + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 200; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(spy).toHaveBeenCalledTimes(2); +}); + +test('completes stream observable when request finishes', async () => { + const env = setup(); + const { stream } = fetchStreaming({ + url: 'http://example.com', + }); + + const spy = jest.fn(); + stream.subscribe({ + complete: spy, + }); + + expect(spy).toHaveBeenCalledTimes(0); + + (env.xhr as any).responseText = 'foo'; + env.xhr.onprogress!({} as any); + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 200; + env.xhr.onreadystatechange!({} as any); + + expect(spy).toHaveBeenCalledTimes(1); +}); + +test('promise throws when request errors', async () => { + const env = setup(); + const { promise } = fetchStreaming({ + url: 'http://example.com', + }); + + const spy = jest.fn(); + promise.catch(spy); + + await tick(); + expect(spy).toHaveBeenCalledTimes(0); + + (env.xhr as any).responseText = 'foo'; + env.xhr.onprogress!({} as any); + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 400; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][0]).toBeInstanceOf(Error); + expect(spy.mock.calls[0][0].message).toMatchInlineSnapshot( + `"Batch request failed with status 400"` + ); +}); + +test('stream observable errors when request errors', async () => { + const env = setup(); + const { promise, stream } = fetchStreaming({ + url: 'http://example.com', + }); + + const spy = jest.fn(); + promise.catch(() => {}); + stream.subscribe({ + error: spy, + }); + + await tick(); + expect(spy).toHaveBeenCalledTimes(0); + + (env.xhr as any).responseText = 'foo'; + env.xhr.onprogress!({} as any); + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 400; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][0]).toBeInstanceOf(Error); + expect(spy.mock.calls[0][0].message).toMatchInlineSnapshot( + `"Batch request failed with status 400"` + ); +}); + +test('sets custom headers', async () => { + const env = setup(); + fetchStreaming({ + url: 'http://example.com', + headers: { + 'Content-Type': 'text/plain', + Authorization: 'Bearer 123', + }, + }); + + expect(env.xhr.setRequestHeader).toHaveBeenCalledWith('Content-Type', 'text/plain'); + expect(env.xhr.setRequestHeader).toHaveBeenCalledWith('Authorization', 'Bearer 123'); +}); + +test('uses credentials', async () => { + const env = setup(); + + expect(env.xhr.withCredentials).toBe(false); + + fetchStreaming({ + url: 'http://example.com', + }); + + expect(env.xhr.withCredentials).toBe(true); +}); + +test('opens XHR request and sends specified body', async () => { + const env = setup(); + + expect(env.xhr.open).toHaveBeenCalledTimes(0); + expect(env.xhr.send).toHaveBeenCalledTimes(0); + + fetchStreaming({ + url: 'http://elastic.co', + method: 'GET', + body: 'foobar', + }); + + expect(env.xhr.open).toHaveBeenCalledTimes(1); + expect(env.xhr.send).toHaveBeenCalledTimes(1); + expect(env.xhr.open).toHaveBeenCalledWith('GET', 'http://elastic.co'); + expect(env.xhr.send).toHaveBeenCalledWith('foobar'); +}); + +test('uses POST request method by default', async () => { + const env = setup(); + fetchStreaming({ + url: 'http://elastic.co', + }); + expect(env.xhr.open).toHaveBeenCalledWith('POST', 'http://elastic.co'); +}); diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.ts new file mode 100644 index 00000000000000..44a3693e7010b2 --- /dev/null +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.ts @@ -0,0 +1,65 @@ +/* + * 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 { defer } from '../../../kibana_utils/common'; +import { fromStreamingXhr } from './from_streaming_xhr'; + +export interface FetchStreamingParams { + url: string; + headers?: Record; + method?: 'GET' | 'POST'; + body?: string; +} + +/** + * Sends an AJAX request to the server, and processes the result as a + * streaming HTTP/1 response. Streams data as text through observable. + */ +export function fetchStreaming({ + url, + headers = {}, + method = 'POST', + body = '', +}: FetchStreamingParams) { + const xhr = new window.XMLHttpRequest(); + const { promise, resolve, reject } = defer(); + + // Begin the request + xhr.open(method, url); + xhr.withCredentials = true; + + // Set the HTTP headers + Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); + + const stream = fromStreamingXhr(xhr); + + stream.subscribe({ + complete: () => resolve(), + error: error => reject(error), + }); + + // Send the payload to the server + xhr.send(body); + + return { + xhr, + promise, + stream, + }; +} diff --git a/src/plugins/bfetch/public/streaming/from_streaming_xhr.test.ts b/src/plugins/bfetch/public/streaming/from_streaming_xhr.test.ts new file mode 100644 index 00000000000000..40eb3d5e2556bb --- /dev/null +++ b/src/plugins/bfetch/public/streaming/from_streaming_xhr.test.ts @@ -0,0 +1,217 @@ +/* + * 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 { fromStreamingXhr } from './from_streaming_xhr'; + +const createXhr = (): XMLHttpRequest => + (({ + onprogress: () => {}, + onreadystatechange: () => {}, + readyState: 0, + responseText: '', + status: 0, + } as unknown) as XMLHttpRequest); + +test('returns observable', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + expect(typeof observable.subscribe).toBe('function'); +}); + +test('emits an event to observable', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const spy = jest.fn(); + observable.subscribe(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + (xhr as any).responseText = 'foo'; + xhr.onprogress!({} as any); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('foo'); +}); + +test('streams multiple events to observable', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const spy = jest.fn(); + observable.subscribe(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + (xhr as any).responseText = '1'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '12'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '123'; + xhr.onprogress!({} as any); + + expect(spy).toHaveBeenCalledTimes(3); + expect(spy.mock.calls[0][0]).toBe('1'); + expect(spy.mock.calls[1][0]).toBe('2'); + expect(spy.mock.calls[2][0]).toBe('3'); +}); + +test('completes observable when request reaches end state', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const next = jest.fn(); + const complete = jest.fn(); + observable.subscribe({ + next, + complete, + }); + + (xhr as any).responseText = '1'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '2'; + xhr.onprogress!({} as any); + + expect(complete).toHaveBeenCalledTimes(0); + + (xhr as any).readyState = 4; + (xhr as any).status = 200; + xhr.onreadystatechange!({} as any); + + expect(complete).toHaveBeenCalledTimes(1); +}); + +test('errors observable if request returns with error', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const next = jest.fn(); + const complete = jest.fn(); + const error = jest.fn(); + observable.subscribe({ + next, + complete, + error, + }); + + (xhr as any).responseText = '1'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '2'; + xhr.onprogress!({} as any); + + expect(complete).toHaveBeenCalledTimes(0); + + (xhr as any).readyState = 4; + (xhr as any).status = 400; + xhr.onreadystatechange!({} as any); + + expect(complete).toHaveBeenCalledTimes(0); + expect(error).toHaveBeenCalledTimes(1); + expect(error.mock.calls[0][0]).toBeInstanceOf(Error); + expect(error.mock.calls[0][0].message).toMatchInlineSnapshot( + `"Batch request failed with status 400"` + ); +}); + +test('when .onprogress called multiple times with same text, does not create new observable events', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const spy = jest.fn(); + observable.subscribe(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + (xhr as any).responseText = '1'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '1'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '12'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '12'; + xhr.onprogress!({} as any); + + (xhr as any).responseText = '123'; + xhr.onprogress!({} as any); + + expect(spy).toHaveBeenCalledTimes(3); + expect(spy.mock.calls[0][0]).toBe('1'); + expect(spy.mock.calls[1][0]).toBe('2'); + expect(spy.mock.calls[2][0]).toBe('3'); +}); + +test('generates new observable events on .onreadystatechange', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const spy = jest.fn(); + observable.subscribe(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + (xhr as any).responseText = '{"foo":"bar"}'; + xhr.onreadystatechange!({} as any); + + (xhr as any).responseText = '{"foo":"bar"}\n'; + xhr.onreadystatechange!({} as any); + + (xhr as any).responseText = '{"foo":"bar"}\n123'; + xhr.onreadystatechange!({} as any); + + expect(spy).toHaveBeenCalledTimes(3); + expect(spy.mock.calls[0][0]).toBe('{"foo":"bar"}'); + expect(spy.mock.calls[1][0]).toBe('\n'); + expect(spy.mock.calls[2][0]).toBe('123'); +}); + +test('.onreadystatechange and .onprogress can be called in any order', () => { + const xhr = createXhr(); + const observable = fromStreamingXhr(xhr); + + const spy = jest.fn(); + observable.subscribe(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + (xhr as any).responseText = '{"foo":"bar"}'; + xhr.onreadystatechange!({} as any); + xhr.onprogress!({} as any); + + (xhr as any).responseText = '{"foo":"bar"}\n'; + xhr.onprogress!({} as any); + xhr.onreadystatechange!({} as any); + + (xhr as any).responseText = '{"foo":"bar"}\n123'; + xhr.onreadystatechange!({} as any); + xhr.onprogress!({} as any); + xhr.onreadystatechange!({} as any); + xhr.onprogress!({} as any); + + expect(spy).toHaveBeenCalledTimes(3); + expect(spy.mock.calls[0][0]).toBe('{"foo":"bar"}'); + expect(spy.mock.calls[1][0]).toBe('\n'); + expect(spy.mock.calls[2][0]).toBe('123'); +}); diff --git a/src/plugins/bfetch/public/streaming/from_streaming_xhr.ts b/src/plugins/bfetch/public/streaming/from_streaming_xhr.ts new file mode 100644 index 00000000000000..bba8151958492b --- /dev/null +++ b/src/plugins/bfetch/public/streaming/from_streaming_xhr.ts @@ -0,0 +1,62 @@ +/* + * 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 { Observable, Subject } from 'rxjs'; + +/** + * Creates observable from streaming XMLHttpRequest, where each event + * corresponds to a streamed chunk. + */ +export const fromStreamingXhr = ( + xhr: Pick< + XMLHttpRequest, + 'onprogress' | 'onreadystatechange' | 'readyState' | 'status' | 'responseText' + > +): Observable => { + const subject = new Subject(); + let index = 0; + + const processBatch = () => { + const { responseText } = xhr; + if (index >= responseText.length) return; + subject.next(responseText.substr(index)); + index = responseText.length; + }; + + xhr.onprogress = processBatch; + + xhr.onreadystatechange = () => { + // Older browsers don't support onprogress, so we need + // to call this here, too. It's safe to call this multiple + // times even for the same progress event. + processBatch(); + + // 4 is the magic number that means the request is done + if (xhr.readyState === 4) { + // 0 indicates a network failure. 400+ messages are considered server errors + if (xhr.status === 0 || xhr.status >= 400) { + subject.error(new Error(`Batch request failed with status ${xhr.status}`)); + } else { + subject.complete(); + } + } + }; + + return subject; +}; diff --git a/src/plugins/bfetch/public/streaming/index.ts b/src/plugins/bfetch/public/streaming/index.ts new file mode 100644 index 00000000000000..6368c0d1ac697a --- /dev/null +++ b/src/plugins/bfetch/public/streaming/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './split'; +export * from './from_streaming_xhr'; +export * from './fetch_streaming'; diff --git a/src/plugins/bfetch/public/streaming/split.test.ts b/src/plugins/bfetch/public/streaming/split.test.ts new file mode 100644 index 00000000000000..6eb3e27ad85984 --- /dev/null +++ b/src/plugins/bfetch/public/streaming/split.test.ts @@ -0,0 +1,71 @@ +/* + * 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 { split } from './split'; +import { Subject } from 'rxjs'; + +test('splits a single IP address', () => { + const ip = '127.0.0.1'; + const list: string[] = []; + const subject = new Subject(); + const splitted = split('.')(subject); + + splitted.subscribe(value => list.push(value)); + + subject.next(ip); + subject.complete(); + expect(list).toEqual(['127', '0', '0', '1']); +}); + +const streams = [ + 'adsf.asdf.asdf', + 'single.dot', + 'empty..split', + 'trailingdot.', + '.leadingdot', + '.', + '....', + 'no_delimiter', + '1.2.3.4.5', + '1.2.3.4.5.', + '.1.2.3.4.5.', + '.1.2.3.4.5', +]; + +for (const stream of streams) { + test(`splits stream by delimiter correctly "${stream}"`, () => { + const correctResult = stream.split('.').filter(Boolean); + + for (let j = 0; j < 100; j++) { + const list: string[] = []; + const subject = new Subject(); + const splitted = split('.')(subject); + splitted.subscribe(value => list.push(value)); + let i = 0; + while (i < stream.length) { + const len = Math.round(Math.random() * 10); + const chunk = stream.substr(i, len); + subject.next(chunk); + i += len; + } + subject.complete(); + expect(list).toEqual(correctResult); + } + }); +} diff --git a/src/plugins/bfetch/public/streaming/split.ts b/src/plugins/bfetch/public/streaming/split.ts new file mode 100644 index 00000000000000..665411f472ac35 --- /dev/null +++ b/src/plugins/bfetch/public/streaming/split.ts @@ -0,0 +1,59 @@ +/* + * 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 { Observable, Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +/** + * Receives observable that emits strings, and returns a new observable + * that also returns strings separated by delimiter. + * + * Input stream: + * + * asdf.f -> df..aaa. -> dfsdf + * + * Output stream, assuming "." is used as delimiter: + * + * asdf -> fdf -> aaa -> dfsdf + * + */ +export const split = (delimiter: string = '\n') => ( + in$: Observable +): Observable => { + const out$ = new Subject(); + let startingText = ''; + + in$.subscribe( + chunk => { + const messages = (startingText + chunk).split(delimiter); + + // We don't want to send the last message here, since it may or + // may not be a partial message. + messages.slice(0, -1).forEach(out$.next.bind(out$)); + startingText = messages.length ? messages[messages.length - 1] : ''; + }, + out$.error.bind(out$), + () => { + out$.next(startingText); + out$.complete(); + } + ); + + return out$.pipe(filter(Boolean)); +}; diff --git a/src/plugins/bfetch/public/test_helpers/xhr.ts b/src/plugins/bfetch/public/test_helpers/xhr.ts new file mode 100644 index 00000000000000..d7046e9a89458e --- /dev/null +++ b/src/plugins/bfetch/public/test_helpers/xhr.ts @@ -0,0 +1,73 @@ +/* + * 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-classes-per-file */ + +export const mockXMLHttpRequest = (): { + xhr: XMLHttpRequest; + XMLHttpRequest: typeof window.XMLHttpRequest; +} => { + class MockXMLHttpRequest implements XMLHttpRequest { + DONE = 0; + HEADERS_RECEIVED = 0; + LOADING = 0; + OPENED = 0; + UNSENT = 0; + abort = jest.fn(); + addEventListener = jest.fn(); + dispatchEvent = jest.fn(); + getAllResponseHeaders = jest.fn(); + getResponseHeader = jest.fn(); + onabort = jest.fn(); + onerror = jest.fn(); + onload = jest.fn(); + onloadend = jest.fn(); + onloadstart = jest.fn(); + onprogress = jest.fn(); + onreadystatechange = jest.fn(); + ontimeout = jest.fn(); + open = jest.fn(); + overrideMimeType = jest.fn(); + readyState = 0; + removeEventListener = jest.fn(); + response = null; + responseText = ''; + responseType = null as any; + responseURL = ''; + responseXML = null; + send = jest.fn(); + setRequestHeader = jest.fn(); + status = 0; + statusText = ''; + timeout = 0; + upload = null as any; + withCredentials = false; + } + + const xhr = new MockXMLHttpRequest(); + + return { + xhr, + XMLHttpRequest: class { + constructor() { + return xhr; + } + } as any, + }; +}; diff --git a/src/plugins/bfetch/public/types.ts b/src/plugins/bfetch/public/types.ts new file mode 100644 index 00000000000000..9880b336e76e5c --- /dev/null +++ b/src/plugins/bfetch/public/types.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ diff --git a/src/plugins/bfetch/server/index.ts b/src/plugins/bfetch/server/index.ts new file mode 100644 index 00000000000000..f1a3f7fd44cf68 --- /dev/null +++ b/src/plugins/bfetch/server/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { PluginInitializerContext } from '../../../core/server'; +import { BfetchServerPlugin } from './plugin'; + +export { BfetchServerSetup, BfetchServerStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new BfetchServerPlugin(initializerContext); +} diff --git a/src/plugins/bfetch/server/mocks.ts b/src/plugins/bfetch/server/mocks.ts new file mode 100644 index 00000000000000..8ec68650a60dc0 --- /dev/null +++ b/src/plugins/bfetch/server/mocks.ts @@ -0,0 +1,61 @@ +/* + * 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 { BfetchServerSetup, BfetchServerStart } from '.'; +import { plugin as pluginInitializer } from '.'; +import { coreMock } from '../../../core/server/mocks'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + addStreamingResponseRoute: jest.fn(), + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +const createPlugin = async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = await plugin.setup(coreSetup, {}); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: async () => await plugin.start(coreStart, {}), + }; +}; + +export const uiActionsPluginMock = { + createSetupContract, + createStartContract, + createPlugin, +}; diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts new file mode 100644 index 00000000000000..75baeafc176692 --- /dev/null +++ b/src/plugins/bfetch/server/plugin.ts @@ -0,0 +1,92 @@ +/* + * 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 { CoreStart, PluginInitializerContext, CoreSetup, Plugin, Logger } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; +import { StreamingResponseHandler, removeLeadingSlash } from '../common'; +import { createNDJSONStream } from './streaming'; + +// eslint-disable-next-line +export interface BfetchServerSetupDependencies {} + +// eslint-disable-next-line +export interface BfetchServerStartDependencies {} + +export interface BfetchServerSetup { + addStreamingResponseRoute: (path: string, handler: StreamingResponseHandler) => void; +} + +// eslint-disable-next-line +export interface BfetchServerStart {} + +export class BfetchServerPlugin + implements + Plugin< + BfetchServerSetup, + BfetchServerStart, + BfetchServerSetupDependencies, + BfetchServerStartDependencies + > { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: BfetchServerSetupDependencies): BfetchServerSetup { + const logger = this.initializerContext.logger.get(); + const router = core.http.createRouter(); + const addStreamingResponseRoute = this.addStreamingResponseRoute({ router, logger }); + + return { + addStreamingResponseRoute, + }; + } + + public start(core: CoreStart, plugins: BfetchServerStartDependencies): BfetchServerStart { + return {}; + } + + public stop() {} + + private addStreamingResponseRoute = ({ + router, + logger, + }: { + router: ReturnType; + logger: Logger; + }): BfetchServerSetup['addStreamingResponseRoute'] => (path, handler) => { + router.post( + { + path: `/${removeLeadingSlash(path)}`, + validate: { + body: schema.any(), + }, + }, + async (context, request, response) => { + const data = request.body; + return response.ok({ + headers: { + 'Content-Type': 'application/x-ndjson', + Connection: 'keep-alive', + 'Transfer-Encoding': 'chunked', + 'Cache-Control': 'no-cache', + }, + body: createNDJSONStream(data, handler, logger), + }); + } + ); + }; +} diff --git a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts new file mode 100644 index 00000000000000..b1f39f4acbcb5e --- /dev/null +++ b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts @@ -0,0 +1,52 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { Stream, PassThrough } from 'stream'; +import { StreamingResponseHandler } from '../../common/types'; + +const delimiter = '\n'; + +export const createNDJSONStream = ( + payload: Payload, + handler: StreamingResponseHandler, + logger: Logger +): Stream => { + const stream = new PassThrough(); + const results = handler.onRequest(payload); + + results.subscribe({ + next: (message: Response) => { + try { + const line = JSON.stringify(message); + stream.write(`${line}${delimiter}`); + } catch (error) { + logger.error('Could not serialize or stream a message.'); + logger.error(error); + } + }, + error: error => { + stream.end(); + logger.error(error); + }, + complete: () => stream.end(), + }); + + return stream; +}; diff --git a/src/plugins/bfetch/server/streaming/index.ts b/src/plugins/bfetch/server/streaming/index.ts new file mode 100644 index 00000000000000..5c9904a75786ae --- /dev/null +++ b/src/plugins/bfetch/server/streaming/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './create_ndjson_stream'; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 19e465104cf4cd..e3482dd4830357 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -437,13 +437,10 @@ export class IndexPattern implements IIndexPattern { } if (unresolvedCollision) { - const message = i18n.translate( - 'data.indexPatterns.unableWriteLabel', - { - defaultMessage: - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', - } // eslint-disable-line max-len - ); + const message = i18n.translate('data.indexPatterns.unableWriteLabel', { + defaultMessage: + 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', + }); const { toasts } = getNotifications(); toasts.addDanger(message); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts index 4c7ea6676f9ef9..acc2da15144831 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SavedObjectAttributes } from 'src/core/server'; +import { SavedObjectAttributes } from 'src/core/public'; import { SavedObjectMetaData } from '../types'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { ErrorEmbeddable } from './error_embeddable'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx index 51fbbd2ba3046d..bd2beaf77a3059 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx @@ -44,7 +44,7 @@ import { import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; -import { SavedObjectAttributes } from '../../../../core/server'; +import { SavedObjectAttributes } from '../../../../core/public'; import { SimpleSavedObject, CoreStart } from '../../../../core/public'; import { useKibana } from '../context'; diff --git a/src/plugins/kibana_utils/common/defer.test.ts b/src/plugins/kibana_utils/common/defer.test.ts new file mode 100644 index 00000000000000..e05727847f247d --- /dev/null +++ b/src/plugins/kibana_utils/common/defer.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { Defer } from './defer'; + +const tick = () => new Promise(resolve => setTimeout(resolve, 1)); + +describe('new Defer()', () => { + test('has .promise Promise object', () => { + expect(new Defer().promise).toBeInstanceOf(Promise); + }); + + test('has .resolve() method', () => { + expect(typeof new Defer().resolve).toBe('function'); + }); + + test('has .reject() method', () => { + expect(typeof new Defer().reject).toBe('function'); + }); + + test('resolves promise when .reject() is called', async () => { + const defer = new Defer(); + const then = jest.fn(); + defer.promise.then(then); + + await tick(); + expect(then).toHaveBeenCalledTimes(0); + + defer.resolve(123); + + await tick(); + expect(then).toHaveBeenCalledTimes(1); + expect(then).toHaveBeenCalledWith(123); + }); + + test('rejects promise when .reject() is called', async () => { + const defer = new Defer(); + const then = jest.fn(); + const spy = jest.fn(); + defer.promise.then(then).catch(spy); + + await tick(); + expect(then).toHaveBeenCalledTimes(0); + expect(spy).toHaveBeenCalledTimes(0); + + defer.reject('oops'); + + await tick(); + expect(then).toHaveBeenCalledTimes(0); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('oops'); + }); +}); diff --git a/src/plugins/kibana_utils/common/defer.ts b/src/plugins/kibana_utils/common/defer.ts new file mode 100644 index 00000000000000..bf8fa836ed1725 --- /dev/null +++ b/src/plugins/kibana_utils/common/defer.ts @@ -0,0 +1,41 @@ +/* + * 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. + */ + +/** + * An externally resolvable/rejectable "promise". Use it to resolve/reject + * promise at any time. + * + * ```ts + * const future = new Defer(); + * + * future.promise.then(value => console.log(value)); + * + * future.resolve(123); + * ``` + */ +export class Defer { + public readonly resolve!: (data: T) => void; + public readonly reject!: (error: any) => void; + public readonly promise: Promise = new Promise((resolve, reject) => { + (this as any).resolve = resolve; + (this as any).reject = reject; + }); +} + +export const defer = () => new Defer(); diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts new file mode 100644 index 00000000000000..d13a250cedf2e3 --- /dev/null +++ b/src/plugins/kibana_utils/common/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './defer'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 3f5aeebac54d84..6e6b5c582b0eb9 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -17,9 +17,9 @@ * under the License. */ +export { defer } from '../common'; export * from './core'; export * from './errors'; -export * from './errors'; export * from './field_mapping'; export * from './parse'; export * from './render_complete'; diff --git a/src/plugins/navigation/README.md b/src/plugins/navigation/README.md new file mode 100644 index 00000000000000..2b32cb50f0b112 --- /dev/null +++ b/src/plugins/navigation/README.md @@ -0,0 +1,5 @@ +# navigation + +The navigation plugins exports the `TopNavMenu` component. +It also provides a stateful version of it on the `start` contract. + diff --git a/src/plugins/navigation/kibana.json b/src/plugins/navigation/kibana.json new file mode 100644 index 00000000000000..000d5acf2635f7 --- /dev/null +++ b/src/plugins/navigation/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "navigation", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["data"] +} \ No newline at end of file diff --git a/src/plugins/navigation/public/index.ts b/src/plugins/navigation/public/index.ts new file mode 100644 index 00000000000000..1c0a36c597ce7f --- /dev/null +++ b/src/plugins/navigation/public/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { PluginInitializerContext } from '../../../core/public'; +export function plugin(initializerContext: PluginInitializerContext) { + return new NavigationPublicPlugin(initializerContext); +} + +export { TopNavMenuData, TopNavMenu } from './top_nav_menu'; + +export { NavigationPublicPluginSetup, NavigationPublicPluginStart } from './types'; + +// Export plugin after all other imports +import { NavigationPublicPlugin } from './plugin'; +export { NavigationPublicPlugin as Plugin }; diff --git a/src/plugins/navigation/public/mocks.ts b/src/plugins/navigation/public/mocks.ts new file mode 100644 index 00000000000000..3e30217f6b6fbd --- /dev/null +++ b/src/plugins/navigation/public/mocks.ts @@ -0,0 +1,44 @@ +/* + * 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 { Plugin } from '.'; + +export type Setup = jest.Mocked>; +export type Start = jest.Mocked>; + +const createSetupContract = (): jest.Mocked => { + const setupContract = { + registerMenuItem: jest.fn(), + }; + + return setupContract; +}; + +const createStartContract = (): jest.Mocked => { + const startContract = { + ui: { + TopNavMenu: jest.fn(), + }, + }; + return startContract; +}; + +export const navigationPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/legacy/core_plugins/navigation/public/plugin.ts b/src/plugins/navigation/public/plugin.ts similarity index 52% rename from src/legacy/core_plugins/navigation/public/plugin.ts rename to src/plugins/navigation/public/plugin.ts index 27533e6c8fcf69..e8df5bcd616d9b 100644 --- a/src/legacy/core_plugins/navigation/public/plugin.ts +++ b/src/plugins/navigation/public/plugin.ts @@ -17,40 +17,21 @@ * under the License. */ -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { TopNavMenuExtensionsRegistry, TopNavMenuExtensionsRegistrySetup } from './top_nav_menu'; -import { createTopNav } from './top_nav_menu/create_top_nav_menu'; -import { TopNavMenuProps } from './top_nav_menu/top_nav_menu'; - -/** - * Interface for this plugin's returned `setup` contract. - * - * @public - */ -export interface NavigationSetup { - registerMenuItem: TopNavMenuExtensionsRegistrySetup['register']; -} - -/** - * Interface for this plugin's returned `start` contract. - * - * @public - */ -export interface NavigationStart { - ui: { - TopNavMenu: React.ComponentType; - }; -} - -export interface NavigationPluginStartDependencies { - data: DataPublicPluginStart; -} - -export class NavigationPlugin implements Plugin { +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { + NavigationPublicPluginSetup, + NavigationPublicPluginStart, + NavigationPluginStartDependencies, +} from './types'; +import { TopNavMenuExtensionsRegistry, createTopNav } from './top_nav_menu'; + +export class NavigationPublicPlugin + implements Plugin { private readonly topNavMenuExtensionsRegistry: TopNavMenuExtensionsRegistry = new TopNavMenuExtensionsRegistry(); - public setup(core: CoreSetup): NavigationSetup { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup): NavigationPublicPluginSetup { return { registerMenuItem: this.topNavMenuExtensionsRegistry.register.bind( this.topNavMenuExtensionsRegistry @@ -58,7 +39,10 @@ export class NavigationPlugin implements Plugin & { appName: string; diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx similarity index 100% rename from src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx rename to src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts similarity index 100% rename from src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts rename to src/plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx similarity index 100% rename from src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx rename to src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx similarity index 100% rename from src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx rename to src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx diff --git a/src/plugins/navigation/public/types.ts b/src/plugins/navigation/public/types.ts new file mode 100644 index 00000000000000..d07c636b274289 --- /dev/null +++ b/src/plugins/navigation/public/types.ts @@ -0,0 +1,35 @@ +/* + * 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 { TopNavMenuProps, TopNavMenuExtensionsRegistrySetup } from './top_nav_menu'; +import { DataPublicPluginStart } from '../../data/public'; + +export interface NavigationPublicPluginSetup { + registerMenuItem: TopNavMenuExtensionsRegistrySetup['register']; +} + +export interface NavigationPublicPluginStart { + ui: { + TopNavMenu: React.ComponentType; + }; +} + +export interface NavigationPluginStartDependencies { + data: DataPublicPluginStart; +} diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md index 4502e1a6ceacfe..4b81fe9b22083a 100644 --- a/src/plugins/usage_collection/README.md +++ b/src/plugins/usage_collection/README.md @@ -137,3 +137,83 @@ There are a few ways you can test that your usage collector is working properly. Keep it simple, and keep it to a model that Kibana will be able to understand. In short, that means don't rely on nested fields (arrays with objects). Flat arrays, such as arrays of strings are fine. 2. **If I accumulate an event counter in server memory, which my fetch method returns, won't it reset when the Kibana server restarts?** Yes, but that is not a major concern. A visualization on such info might be a date histogram that gets events-per-second or something, which would be impacted by server restarts, so we'll have to offset the beginning of the time range when we detect that the latest metric is smaller than the earliest metric. That would be a pretty custom visualization, but perhaps future Kibana enhancements will be able to support that. + + +# UI Metric app + +## Purpose + +The purpose of the UI Metric app is to provide a tool for gathering data on how users interact with +various UIs within Kibana. It's useful for gathering _aggregate_ information, e.g. "How many times +has Button X been clicked" or "How many times has Page Y been viewed". + +With some finagling, it's even possible to add more meaning to the info you gather, such as "How many +visualizations were created in less than 5 minutes". + +### What it doesn't do + +The UI Metric app doesn't gather any metadata around a user interaction, e.g. the user's identity, +the name of a dashboard they've viewed, or the timestamp of the interaction. + +## How to use it + +To track a user interaction, import the `createUiStatsReporter` helper function from UI Metric app: + +```js +import { createUiStatsReporter, METRIC_TYPE } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public'; +const trackMetric = createUiStatsReporter(``); +trackMetric(METRIC_TYPE.CLICK, ``); +trackMetric('click', ``); +``` + +Metric Types: + - `METRIC_TYPE.CLICK` for tracking clicks `trackMetric(METRIC_TYPE.CLICK, 'my_button_clicked');` + - `METRIC_TYPE.LOADED` for a component load or page load `trackMetric(METRIC_TYPE.LOADED', 'my_component_loaded');` + - `METRIC_TYPE.COUNT` for a tracking a misc count `trackMetric(METRIC_TYPE.COUNT', 'my_counter', });` + +Call this function whenever you would like to track a user interaction within your app. The function +accepts two arguments, `metricType` and `eventNames`. These should be underscore-delimited strings. +For example, to track the `my_event` metric in the app `my_app` call `trackUiMetric(METRIC_TYPE.*, 'my_event)`. + +That's all you need to do! + +To track multiple metrics within a single request, provide an array of events, e.g. `trackMetric(METRIC_TYPE.*, ['my_event1', 'my_event2', 'my_event3'])`. + +### Disallowed characters + +The colon character (`:`) should not be used in app name or event names. Colons play +a special role in how metrics are stored as saved objects. + +### Tracking timed interactions + +If you want to track how long it takes a user to do something, you'll need to implement the timing +logic yourself. You'll also need to predefine some buckets into which the UI metric can fall. +For example, if you're timing how long it takes to create a visualization, you may decide to +measure interactions that take less than 1 minute, 1-5 minutes, 5-20 minutes, and longer than 20 minutes. +To track these interactions, you'd use the timed length of the interaction to determine whether to +use a `eventName` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`. + +## How it works + +Under the hood, your app and metric type will be stored in a saved object of type `user-metric` and the +ID `ui-metric:my_app:my_metric`. This saved object will have a `count` property which will be incremented +every time the above URI is hit. + +These saved objects are automatically consumed by the stats API and surfaced under the +`ui_metric` namespace. + +```json +{ + "ui_metric": { + "my_app": [ + { + "key": "my_metric", + "value": 3 + } + ] + } +} +``` + +By storing these metrics and their counts as key-value pairs, we can add more metrics without having +to worry about exceeding the 1000-field soft limit in Elasticsearch. \ No newline at end of file diff --git a/src/plugins/usage_collection/common/constants.ts b/src/plugins/usage_collection/common/constants.ts index edd06b171a72c8..96b24c5e7475ed 100644 --- a/src/plugins/usage_collection/common/constants.ts +++ b/src/plugins/usage_collection/common/constants.ts @@ -18,3 +18,4 @@ */ export const KIBANA_STATS_TYPE = 'kibana_stats'; +export const DEFAULT_MAXIMUM_WAIT_TIME_FOR_ALL_COLLECTORS_IN_S = 60; diff --git a/src/plugins/usage_collection/kibana.json b/src/plugins/usage_collection/kibana.json index 145cd89ff884d8..ae86b6c5d7ad18 100644 --- a/src/plugins/usage_collection/kibana.json +++ b/src/plugins/usage_collection/kibana.json @@ -3,5 +3,5 @@ "configPath": ["usageCollection"], "version": "kibana", "server": true, - "ui": false + "ui": true } diff --git a/src/legacy/server/csp/index.ts b/src/plugins/usage_collection/public/index.ts similarity index 70% rename from src/legacy/server/csp/index.ts rename to src/plugins/usage_collection/public/index.ts index ae5cb63ad6ff82..712e6a76152a26 100644 --- a/src/legacy/server/csp/index.ts +++ b/src/plugins/usage_collection/public/index.ts @@ -17,16 +17,12 @@ * under the License. */ -export const DEFAULT_CSP_RULES = Object.freeze([ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, -]); +import { PluginInitializerContext } from '../../../core/public'; +import { UsageCollectionPlugin } from './plugin'; -export const DEFAULT_CSP_STRICT = true; +export { METRIC_TYPE } from '@kbn/analytics'; +export { UsageCollectionSetup } from './plugin'; -export const DEFAULT_CSP_WARN_LEGACY_BROWSERS = true; - -export function createCSPRuleString(rules: string[]) { - return rules.join('; '); +export function plugin(initializerContext: PluginInitializerContext) { + return new UsageCollectionPlugin(initializerContext); } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/index.ts b/src/plugins/usage_collection/public/mocks.ts similarity index 68% rename from src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/index.ts rename to src/plugins/usage_collection/public/mocks.ts index 5a584144995948..69fbf56ca5604d 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/ajax_stream/index.ts +++ b/src/plugins/usage_collection/public/mocks.ts @@ -17,12 +17,20 @@ * under the License. */ -import { ajaxStream as ajax, BatchOpts } from './ajax_stream'; +import { UsageCollectionSetup, METRIC_TYPE } from '.'; -export const ajaxStream = (version: string, basePath: string) => (opts: BatchOpts) => { - const defaultHeaders = { - 'Content-Type': 'application/json', - 'kbn-version': version, +export type Setup = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + allowTrackUserAgent: jest.fn(), + reportUiStats: jest.fn(), + METRIC_TYPE, }; - return ajax(basePath, defaultHeaders, new XMLHttpRequest(), opts); + + return setupContract; +}; + +export const usageCollectionPluginMock = { + createSetupContract, }; diff --git a/src/plugins/usage_collection/public/plugin.ts b/src/plugins/usage_collection/public/plugin.ts new file mode 100644 index 00000000000000..2ecc6c8bc2038f --- /dev/null +++ b/src/plugins/usage_collection/public/plugin.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 { Reporter, METRIC_TYPE } from '@kbn/analytics'; +import { Storage } from '../../kibana_utils/public'; +import { createReporter } from './services'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + CoreStart, + HttpServiceBase, +} from '../../../core/public'; + +interface PublicConfigType { + uiMetric: { + enabled: boolean; + debug: boolean; + }; +} + +export interface UsageCollectionSetup { + allowTrackUserAgent: (allow: boolean) => void; + reportUiStats: Reporter['reportUiStats']; + METRIC_TYPE: typeof METRIC_TYPE; +} + +export function isUnauthenticated(http: HttpServiceBase) { + const { anonymousPaths } = http; + return anonymousPaths.isAnonymous(window.location.pathname); +} + +export class UsageCollectionPlugin implements Plugin { + private trackUserAgent: boolean = true; + private reporter?: Reporter; + private config: PublicConfigType; + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + } + + public setup({ http }: CoreSetup): UsageCollectionSetup { + const localStorage = new Storage(window.localStorage); + const debug = this.config.uiMetric.debug; + + this.reporter = createReporter({ + localStorage, + debug, + fetch: http, + }); + + return { + allowTrackUserAgent: (allow: boolean) => { + this.trackUserAgent = allow; + }, + reportUiStats: this.reporter.reportUiStats, + METRIC_TYPE, + }; + } + + public start({ http }: CoreStart) { + if (!this.reporter) { + return; + } + + if (this.config.uiMetric.enabled && !isUnauthenticated(http)) { + this.reporter.start(); + } + + if (this.trackUserAgent) { + this.reporter.reportUserAgent('kibana'); + } + } + + public stop() {} +} diff --git a/src/plugins/usage_collection/public/services/create_reporter.ts b/src/plugins/usage_collection/public/services/create_reporter.ts new file mode 100644 index 00000000000000..6bc35de8972c32 --- /dev/null +++ b/src/plugins/usage_collection/public/services/create_reporter.ts @@ -0,0 +1,46 @@ +/* + * 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 { Reporter, Storage } from '@kbn/analytics'; +import { HttpServiceBase } from 'kibana/public'; + +interface AnalyicsReporterConfig { + localStorage: Storage; + debug: boolean; + fetch: HttpServiceBase; +} + +export function createReporter(config: AnalyicsReporterConfig): Reporter { + const { localStorage, debug, fetch } = config; + + return new Reporter({ + debug, + storage: localStorage, + async http(report) { + const response = await fetch.post('/api/ui_metric/report', { + body: JSON.stringify({ report }), + }); + + if (response.status !== 'ok') { + throw Error('Unable to store report.'); + } + return response; + }, + }); +} diff --git a/src/plugins/usage_collection/public/services/index.ts b/src/plugins/usage_collection/public/services/index.ts new file mode 100644 index 00000000000000..7703d5cc3bfeb1 --- /dev/null +++ b/src/plugins/usage_collection/public/services/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createReporter } from './create_reporter'; diff --git a/src/plugins/usage_collection/server/config.ts b/src/plugins/usage_collection/server/config.ts index 987db1f2b0ff3c..76379d9385cff7 100644 --- a/src/plugins/usage_collection/server/config.ts +++ b/src/plugins/usage_collection/server/config.ts @@ -17,8 +17,29 @@ * under the License. */ -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from 'kibana/server'; +import { DEFAULT_MAXIMUM_WAIT_TIME_FOR_ALL_COLLECTORS_IN_S } from '../common/constants'; -export const ConfigSchema = schema.object({ - maximumWaitTimeForAllCollectorsInS: schema.number({ defaultValue: 60 }), +export const configSchema = schema.object({ + uiMetric: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + debug: schema.boolean({ defaultValue: schema.contextRef('dev') }), + }), + maximumWaitTimeForAllCollectorsInS: schema.number({ + defaultValue: DEFAULT_MAXIMUM_WAIT_TIME_FOR_ALL_COLLECTORS_IN_S, + }), }); + +export type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('ui_metric.enabled', 'usageCollection.uiMetric.enabled'), + renameFromRoot('ui_metric.debug', 'usageCollection.uiMetric.debug'), + ], + exposeToBrowser: { + uiMetric: true, + }, +}; diff --git a/src/plugins/usage_collection/server/index.ts b/src/plugins/usage_collection/server/index.ts index 33a1a0adc67135..6a28dba50a9151 100644 --- a/src/plugins/usage_collection/server/index.ts +++ b/src/plugins/usage_collection/server/index.ts @@ -18,10 +18,9 @@ */ import { PluginInitializerContext } from '../../../../src/core/server'; -import { Plugin } from './plugin'; -import { ConfigSchema } from './config'; +import { UsageCollectionPlugin } from './plugin'; export { UsageCollectionSetup } from './plugin'; -export const config = { schema: ConfigSchema }; +export { config } from './config'; export const plugin = (initializerContext: PluginInitializerContext) => - new Plugin(initializerContext); + new UsageCollectionPlugin(initializerContext); diff --git a/src/plugins/usage_collection/server/plugin.ts b/src/plugins/usage_collection/server/plugin.ts index e8bbc8e512a41d..5c5b58ae849360 100644 --- a/src/plugins/usage_collection/server/plugin.ts +++ b/src/plugins/usage_collection/server/plugin.ts @@ -18,22 +18,25 @@ */ import { first } from 'rxjs/operators'; -import { TypeOf } from '@kbn/config-schema'; -import { ConfigSchema } from './config'; -import { PluginInitializerContext, Logger } from '../../../../src/core/server'; +import { ConfigType } from './config'; +import { PluginInitializerContext, Logger, CoreSetup } from '../../../../src/core/server'; import { CollectorSet } from './collector'; +import { setupRoutes } from './routes'; -export type UsageCollectionSetup = CollectorSet; +export type UsageCollectionSetup = CollectorSet & { + registerLegacySavedObjects: (legacySavedObjects: any) => void; +}; -export class Plugin { +export class UsageCollectionPlugin { logger: Logger; + private legacySavedObjects: any; constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); } - public async setup(): Promise { + public async setup(core: CoreSetup) { const config = await this.initializerContext.config - .create>() + .create() .pipe(first()) .toPromise(); @@ -42,7 +45,16 @@ export class Plugin { maximumWaitTimeForAllCollectorsInS: config.maximumWaitTimeForAllCollectorsInS, }); - return collectorSet; + const router = core.http.createRouter(); + const getLegacySavedObjects = () => this.legacySavedObjects; + setupRoutes(router, getLegacySavedObjects); + + return { + ...collectorSet, + registerLegacySavedObjects: (legacySavedObjects: any) => { + this.legacySavedObjects = legacySavedObjects; + }, + }; } public start() { diff --git a/src/plugins/usage_collection/server/report/index.ts b/src/plugins/usage_collection/server/report/index.ts new file mode 100644 index 00000000000000..bb8c1d149fdd29 --- /dev/null +++ b/src/plugins/usage_collection/server/report/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { storeReport } from './store_report'; +export { reportSchema } from './schema'; diff --git a/src/plugins/usage_collection/server/report/schema.ts b/src/plugins/usage_collection/server/report/schema.ts new file mode 100644 index 00000000000000..5adf7d6575a707 --- /dev/null +++ b/src/plugins/usage_collection/server/report/schema.ts @@ -0,0 +1,59 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { METRIC_TYPE } from '@kbn/analytics'; + +export const reportSchema = schema.object({ + reportVersion: schema.maybe(schema.literal(1)), + userAgent: schema.maybe( + schema.recordOf( + schema.string(), + schema.object({ + key: schema.string(), + type: schema.string(), + appName: schema.string(), + userAgent: schema.string(), + }) + ) + ), + uiStatsMetrics: schema.maybe( + schema.recordOf( + schema.string(), + schema.object({ + key: schema.string(), + type: schema.oneOf([ + schema.literal(METRIC_TYPE.CLICK), + schema.literal(METRIC_TYPE.LOADED), + schema.literal(METRIC_TYPE.COUNT), + ]), + appName: schema.string(), + eventName: schema.string(), + stats: schema.object({ + min: schema.number(), + sum: schema.number(), + max: schema.number(), + avg: schema.number(), + }), + }) + ) + ), +}); + +export type ReportSchemaType = TypeOf; diff --git a/src/plugins/usage_collection/server/report/store_report.ts b/src/plugins/usage_collection/server/report/store_report.ts new file mode 100644 index 00000000000000..9232a23d6151b6 --- /dev/null +++ b/src/plugins/usage_collection/server/report/store_report.ts @@ -0,0 +1,44 @@ +/* + * 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 { ReportSchemaType } from './schema'; + +export async function storeReport(internalRepository: any, report: ReportSchemaType) { + const uiStatsMetrics = report.uiStatsMetrics ? Object.entries(report.uiStatsMetrics) : []; + const userAgents = report.userAgent ? Object.entries(report.userAgent) : []; + return Promise.all([ + ...userAgents.map(async ([key, metric]) => { + const { userAgent } = metric; + const savedObjectId = `${key}:${userAgent}`; + return await internalRepository.create( + 'ui-metric', + { count: 1 }, + { + id: savedObjectId, + overwrite: true, + } + ); + }), + ...uiStatsMetrics.map(async ([key, metric]) => { + const { appName, eventName } = metric; + const savedObjectId = `${appName}:${eventName}`; + return await internalRepository.incrementCounter('ui-metric', savedObjectId, 'count'); + }), + ]); +} diff --git a/src/plugins/usage_collection/server/routes/index.ts b/src/plugins/usage_collection/server/routes/index.ts new file mode 100644 index 00000000000000..9e0d74add57bdc --- /dev/null +++ b/src/plugins/usage_collection/server/routes/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { IRouter } from '../../../../../src/core/server'; +import { registerUiMetricRoute } from './report_metrics'; + +export function setupRoutes(router: IRouter, getLegacySavedObjects: any) { + registerUiMetricRoute(router, getLegacySavedObjects); +} diff --git a/src/plugins/usage_collection/server/routes/report_metrics.ts b/src/plugins/usage_collection/server/routes/report_metrics.ts new file mode 100644 index 00000000000000..93f03ea8067d21 --- /dev/null +++ b/src/plugins/usage_collection/server/routes/report_metrics.ts @@ -0,0 +1,45 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { IRouter } from '../../../../../src/core/server'; +import { storeReport, reportSchema } from '../report'; + +export function registerUiMetricRoute(router: IRouter, getLegacySavedObjects: () => any) { + router.post( + { + path: '/api/ui_metric/report', + validate: { + body: schema.object({ + report: reportSchema, + }), + }, + }, + async (context, req, res) => { + const { report } = req.body; + try { + const internalRepository = getLegacySavedObjects(); + await storeReport(internalRepository, report); + return res.ok({ body: { status: 'ok' } }); + } catch (error) { + return res.ok({ body: { status: 'fail' } }); + } + } + ); +} diff --git a/test/api_integration/apis/elasticsearch/index.js b/test/api_integration/apis/elasticsearch/index.js index df390fb839eb42..78f00ee84d408d 100644 --- a/test/api_integration/apis/elasticsearch/index.js +++ b/test/api_integration/apis/elasticsearch/index.js @@ -34,7 +34,7 @@ export default function({ getService }) { .set('content-type', 'application/x-ndjson') .send( '{"index":"logstash-2015.04.21","ignore_unavailable":true}\n{"size":500,"sort":{"@timestamp":"desc"},"query":{"bool":{"must":[{"query_string":{"analyze_wildcard":true,"query":"*"}},{"bool":{"must":[{"range":{"@timestamp":{"gte":1429577068175,"lte":1429577968175}}}],"must_not":[]}}],"must_not":[]}},"highlight":{"pre_tags":["@kibana-highlighted-field@"],"post_tags":["@/kibana-highlighted-field@"],"fields":{"*":{}}},"aggs":{"2":{"date_histogram":{"field":"@timestamp","interval":"30s","min_doc_count":0,"extended_bounds":{"min":1429577068175,"max":1429577968175}}}},"stored_fields":["*"],"_source": true,"script_fields":{},"docvalue_fields":["timestamp_offset","@timestamp","utc_time"]}\n' - ) // eslint-disable-line max-len + ) .expect(200)); }); } diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js index b6339105e4cbf5..5b02ba7e72430f 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.js @@ -49,10 +49,10 @@ export default function({ getService }) { }, }; await supertest - .post('/api/telemetry/report') + .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') .set('content-type', 'application/json') - .send(report) + .send({ report }) .expect(200); const response = await es.search({ index: '.kibana', q: 'type:ui-metric' }); @@ -77,10 +77,10 @@ export default function({ getService }) { }, }; await supertest - .post('/api/telemetry/report') + .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') .set('content-type', 'application/json') - .send(report) + .send({ report }) .expect(200); const response = await es.search({ index: '.kibana', q: 'type:ui-metric' }); diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx index 8678ab705df9c4..6a6bf9e82d5c0f 100644 --- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx @@ -18,10 +18,7 @@ */ import React from 'react'; -import { - setup as navSetup, - start as navStart, -} from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; +import { npSetup, npStart } from 'ui/new_platform'; const customExtension = { id: 'registered-prop', @@ -31,10 +28,10 @@ const customExtension = { testId: 'demoRegisteredNewButton', }; -navSetup.registerMenuItem(customExtension); +npSetup.plugins.navigation.registerMenuItem(customExtension); export const AppWithTopNav = () => { - const { TopNavMenu } = navStart.ui; + const { TopNavMenu } = npStart.plugins.navigation.ui; const config = [ { id: 'new', diff --git a/x-pack/legacy/common/poller.d.ts b/x-pack/legacy/common/poller.d.ts index c23d18dd62e872..df39d93a28a815 100644 --- a/x-pack/legacy/common/poller.d.ts +++ b/x-pack/legacy/common/poller.d.ts @@ -8,4 +8,7 @@ export declare class Poller { constructor(options: any); public start(): void; + public stop(): void; + public isRunning(): boolean; + public getPollFrequency(): number; } diff --git a/x-pack/legacy/plugins/actions/README.md b/x-pack/legacy/plugins/actions/README.md index 72e4d28bad97dd..3c420704fcb349 100644 --- a/x-pack/legacy/plugins/actions/README.md +++ b/x-pack/legacy/plugins/actions/README.md @@ -32,8 +32,14 @@ Built-In-Actions are configured using the _xpack.actions_ namespoace under _kiba | Namespaced Key | Description | Type | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. Currently defaulted to false while Actions are experimental. | boolean | -| _xpack.actions._**WhitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array | +| _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. | boolean | +| _xpack.actions._**whitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array | + +#### Whitelisting Built-in Action Types +It is worth noting that the **whitelistedHosts** configuation applies to built-in action types (such as Slack, or PagerDuty) as well. + +Uniquely, the _PagerDuty Action Type_ has been configured to support the service's Events API (at _https://events.pagerduty.com/v2/enqueue_, which you can read about [here](https://v2.developer.pagerduty.com/docs/events-api-v2)) as a default, but this too, must be included in the whitelist before the PagerDuty action can be used. + ### Configuration Utilities diff --git a/x-pack/legacy/plugins/actions/server/actions_config.mock.ts b/x-pack/legacy/plugins/actions/server/actions_config.mock.ts new file mode 100644 index 00000000000000..0430d712e62671 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/actions_config.mock.ts @@ -0,0 +1,14 @@ +/* + * 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 { ActionsConfigurationUtilities } from './actions_config'; + +export const configUtilsMock: ActionsConfigurationUtilities = { + isWhitelistedHostname: _ => true, + isWhitelistedUri: _ => true, + ensureWhitelistedHostname: _ => {}, + ensureWhitelistedUri: _ => {}, +}; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts index 4f212e702fcff5..9b58c124d02053 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts @@ -5,20 +5,14 @@ */ import { ActionExecutor, TaskRunnerFactory } from '../lib'; -import { ActionsConfigurationUtilities } from '../actions_config'; import { ActionTypeRegistry } from '../action_type_registry'; import { taskManagerMock } from '../../../task_manager/task_manager.mock'; import { registerBuiltInActionTypes } from './index'; import { Logger } from '../../../../../../src/core/server'; import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; +import { configUtilsMock } from '../actions_config.mock'; const ACTION_TYPE_IDS = ['.index', '.email', '.pagerduty', '.server-log', '.slack', '.webhook']; -const MOCK_KIBANA_CONFIG_UTILS: ActionsConfigurationUtilities = { - isWhitelistedHostname: _ => true, - isWhitelistedUri: _ => true, - ensureWhitelistedHostname: _ => {}, - ensureWhitelistedUri: _ => {}, -}; export function createActionTypeRegistry(): { logger: jest.Mocked; @@ -32,7 +26,7 @@ export function createActionTypeRegistry(): { registerBuiltInActionTypes({ logger, actionTypeRegistry, - actionsConfigUtils: MOCK_KIBANA_CONFIG_UTILS, + actionsConfigUtils: configUtilsMock, }); return { logger, actionTypeRegistry }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts index 0fe572bb57e412..e0568a36f87437 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts @@ -18,20 +18,16 @@ import { getActionType as getWebhookActionType } from './webhook'; export function registerBuiltInActionTypes({ logger, actionTypeRegistry, - actionsConfigUtils, + actionsConfigUtils: configurationUtilities, }: { logger: Logger; actionTypeRegistry: ActionTypeRegistry; actionsConfigUtils: ActionsConfigurationUtilities; }) { actionTypeRegistry.register(getServerLogActionType({ logger })); - actionTypeRegistry.register(getSlackActionType()); - actionTypeRegistry.register( - getEmailActionType({ logger, configurationUtilities: actionsConfigUtils }) - ); + actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); + actionTypeRegistry.register(getEmailActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getIndexActionType({ logger })); - actionTypeRegistry.register(getPagerDutyActionType({ logger })); - actionTypeRegistry.register( - getWebhookActionType({ logger, configurationUtilities: actionsConfigUtils }) - ); + actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index def3b7eea21d96..cb3548524ebbb6 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -8,11 +8,14 @@ jest.mock('./lib/post_pagerduty', () => ({ postPagerduty: jest.fn(), })); +import { getActionType } from './pagerduty'; import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { postPagerduty } from './lib/post_pagerduty'; import { createActionTypeRegistry } from './index.test'; +import { Logger } from '../../../../../../src/core/server'; +import { configUtilsMock } from '../actions_config.mock'; const postPagerdutyMock = postPagerduty as jest.Mock; @@ -24,10 +27,12 @@ const services: Services = { }; let actionType: ActionType; +let mockedLogger: jest.Mocked; beforeAll(() => { - const { actionTypeRegistry } = createActionTypeRegistry(); + const { logger, actionTypeRegistry } = createActionTypeRegistry(); actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + mockedLogger = logger; }); describe('get()', () => { @@ -50,6 +55,40 @@ describe('validateConfig()', () => { `"error validating action type config: [shouldNotBeHere]: definition for this key is missing"` ); }); + + test('should validate and pass when the pagerduty url is whitelisted', () => { + actionType = getActionType({ + logger: mockedLogger, + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: url => { + expect(url).toEqual('https://events.pagerduty.com/v2/enqueue'); + }, + }, + }); + + expect( + validateConfig(actionType, { apiUrl: 'https://events.pagerduty.com/v2/enqueue' }) + ).toEqual({ apiUrl: 'https://events.pagerduty.com/v2/enqueue' }); + }); + + test('config validation returns an error if the specified URL isnt whitelisted', () => { + actionType = getActionType({ + logger: mockedLogger, + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: _ => { + throw new Error(`target url is not whitelisted`); + }, + }, + }); + + expect(() => { + validateConfig(actionType, { apiUrl: 'https://events.pagerduty.com/v2/enqueue' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: error configuring pagerduty action: target url is not whitelisted"` + ); + }); }); describe('validateSecrets()', () => { diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts index e1437b8eef5542..250c169278c571 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -10,6 +10,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { postPagerduty } from './lib/post_pagerduty'; import { Logger } from '../../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; // uses the PagerDuty Events API v2 // https://v2.developer.pagerduty.com/docs/events-api-v2 @@ -19,10 +20,10 @@ const PAGER_DUTY_API_URL = 'https://events.pagerduty.com/v2/enqueue'; export type ActionTypeConfigType = TypeOf; -const ConfigSchema = schema.object({ +const configSchemaProps = { apiUrl: schema.nullable(schema.string()), -}); - +}; +const ConfigSchema = schema.object(configSchemaProps); // secrets definition export type ActionTypeSecretsType = TypeOf; @@ -86,12 +87,20 @@ function validateParams(paramsObject: any): string | void { } // action type definition -export function getActionType({ logger }: { logger: Logger }): ActionType { +export function getActionType({ + logger, + configurationUtilities, +}: { + logger: Logger; + configurationUtilities: ActionsConfigurationUtilities; +}): ActionType { return { id: '.pagerduty', name: 'pagerduty', validate: { - config: ConfigSchema, + config: schema.object(configSchemaProps, { + validate: curry(valdiateActionTypeConfig)(configurationUtilities), + }), secrets: SecretsSchema, params: ParamsSchema, }, @@ -99,6 +108,26 @@ export function getActionType({ logger }: { logger: Logger }): ActionType { }; } +function valdiateActionTypeConfig( + configurationUtilities: ActionsConfigurationUtilities, + configObject: ActionTypeConfigType +) { + try { + configurationUtilities.ensureWhitelistedUri(getPagerDutyApiUrl(configObject)); + } catch (whitelistError) { + return i18n.translate('xpack.actions.builtin.pagerduty.pagerdutyConfigurationError', { + defaultMessage: 'error configuring pagerduty action: {message}', + values: { + message: whitelistError.message, + }, + }); + } +} + +function getPagerDutyApiUrl(config: ActionTypeConfigType): string { + return config.apiUrl || PAGER_DUTY_API_URL; +} + // action executor async function executor( @@ -111,7 +140,7 @@ async function executor( const params = execOptions.params as ActionParamsType; const services = execOptions.services; - const apiUrl = config.apiUrl || PAGER_DUTY_API_URL; + const apiUrl = getPagerDutyApiUrl(config); const headers = { 'Content-Type': 'application/json', 'X-Routing-Key': secrets.routingKey, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts index f6bd2d2b254df3..a2b0db8bdb70f0 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts @@ -5,11 +5,10 @@ */ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; -import { ActionTypeRegistry } from '../action_type_registry'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; -import { ActionExecutor, validateParams, validateSecrets, TaskRunnerFactory } from '../lib'; +import { validateParams, validateSecrets } from '../lib'; import { getActionType } from './slack'; -import { taskManagerMock } from '../../../task_manager/task_manager.mock'; +import { configUtilsMock } from '../actions_config.mock'; const ACTION_TYPE_ID = '.slack'; @@ -18,47 +17,19 @@ const services: Services = { savedObjectsClient: savedObjectsClientMock.create(), }; -let actionTypeRegistry: ActionTypeRegistry; let actionType: ActionType; -async function mockSlackExecutor(options: ActionTypeExecutorOptions): Promise { - const { params } = options; - const { message } = params; - if (message == null) throw new Error('message property required in parameter'); - - const failureMatch = message.match(/^failure: (.*)$/); - if (failureMatch != null) { - const failMessage = failureMatch[1]; - throw new Error(`slack mockExecutor failure: ${failMessage}`); - } - - return { - text: `slack mockExecutor success: ${message}`, - }; -} - beforeAll(() => { - actionTypeRegistry = new ActionTypeRegistry({ - taskManager: taskManagerMock.create(), - taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()), - }); - actionTypeRegistry.register(getActionType({ executor: mockSlackExecutor })); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); - - test('ensure action type is valid', () => { - expect(actionType).toBeTruthy(); + actionType = getActionType({ + async executor(options: ActionTypeExecutorOptions): Promise {}, + configurationUtilities: configUtilsMock, }); }); -describe('action is registered', () => { - test('gets registered with builtin actions', () => { - expect(actionTypeRegistry.has(ACTION_TYPE_ID)).toEqual(true); - }); - +describe('action registeration', () => { test('returns action type', () => { - const returnedActionType = actionTypeRegistry.get(ACTION_TYPE_ID); - expect(returnedActionType.id).toEqual(ACTION_TYPE_ID); - expect(returnedActionType.name).toEqual('slack'); + expect(actionType.id).toEqual(ACTION_TYPE_ID); + expect(actionType.name).toEqual('slack'); }); }); @@ -104,9 +75,64 @@ describe('validateActionTypeSecrets()', () => { `"error validating action type secrets: [webhookUrl]: expected value of type [string] but got [number]"` ); }); + + test('should validate and pass when the slack webhookUrl is whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: url => { + expect(url).toEqual('https://api.slack.com/'); + }, + }, + }); + + expect(validateSecrets(actionType, { webhookUrl: 'https://api.slack.com/' })).toEqual({ + webhookUrl: 'https://api.slack.com/', + }); + }); + + test('config validation returns an error if the specified URL isnt whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: url => { + throw new Error(`target url is not whitelisted`); + }, + }, + }); + + expect(() => { + validateSecrets(actionType, { webhookUrl: 'https://api.slack.com/' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: error configuring slack action: target url is not whitelisted"` + ); + }); }); describe('execute()', () => { + beforeAll(() => { + async function mockSlackExecutor(options: ActionTypeExecutorOptions): Promise { + const { params } = options; + const { message } = params; + if (message == null) throw new Error('message property required in parameter'); + + const failureMatch = message.match(/^failure: (.*)$/); + if (failureMatch != null) { + const failMessage = failureMatch[1]; + throw new Error(`slack mockExecutor failure: ${failMessage}`); + } + + return { + text: `slack mockExecutor success: ${message}`, + }; + } + + actionType = getActionType({ + executor: mockSlackExecutor, + configurationUtilities: configUtilsMock, + }); + }); + test('calls the mock executor with success', async () => { const response = await actionType.executor({ actionId: 'some-id', diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts index 29b89150e3990f..92611d6f162ffb 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook'; @@ -17,14 +18,16 @@ import { ActionTypeExecutorResult, ExecutorType, } from '../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; // secrets definition export type ActionTypeSecretsType = TypeOf; -const SecretsSchema = schema.object({ +const secretsSchemaProps = { webhookUrl: schema.string(), -}); +}; +const SecretsSchema = schema.object(secretsSchemaProps); // params definition @@ -37,20 +40,42 @@ const ParamsSchema = schema.object({ // action type definition // customizing executor is only used for tests -export function getActionType( - { executor }: { executor: ExecutorType } = { executor: slackExecutor } -): ActionType { +export function getActionType({ + configurationUtilities, + executor = slackExecutor, +}: { + configurationUtilities: ActionsConfigurationUtilities; + executor?: ExecutorType; +}): ActionType { return { id: '.slack', name: 'slack', validate: { - secrets: SecretsSchema, + secrets: schema.object(secretsSchemaProps, { + validate: curry(valdiateActionTypeConfig)(configurationUtilities), + }), params: ParamsSchema, }, executor, }; } +function valdiateActionTypeConfig( + configurationUtilities: ActionsConfigurationUtilities, + secretsObject: ActionTypeSecretsType +) { + try { + configurationUtilities.ensureWhitelistedUri(secretsObject.webhookUrl); + } catch (whitelistError) { + return i18n.translate('xpack.actions.builtin.slack.slackConfigurationError', { + defaultMessage: 'error configuring slack action: {message}', + values: { + message: whitelistError.message, + }, + }); + } +} + // action executor async function slackExecutor( diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts index 2d492eb451e801..64dd3a485f8e21 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -6,18 +6,12 @@ import { getActionType } from './webhook'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { ActionsConfigurationUtilities } from '../actions_config'; +import { configUtilsMock } from '../actions_config.mock'; import { ActionType } from '../types'; import { createActionTypeRegistry } from './index.test'; import { Logger } from '../../../../../../src/core/server'; const ACTION_TYPE_ID = '.webhook'; -const configUtilsMock: ActionsConfigurationUtilities = { - isWhitelistedHostname: _ => true, - isWhitelistedUri: _ => true, - ensureWhitelistedHostname: _ => {}, - ensureWhitelistedUri: _ => {}, -}; let actionType: ActionType; let mockedLogger: jest.Mocked; diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index 41a7c17a02c5a3..eb183f1f1d06a8 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -34,7 +34,7 @@ beforeAll(() => { state: {}, attempts: 0, ownerId: '', - status: 'running' as TaskStatus, + status: TaskStatus.Running, startedAt: new Date(), scheduledAt: new Date(), retryAt: new Date(Date.now() + 5 * 60 * 1000), diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index 1af62d276f10bb..c40e4ea79d1c39 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -9,7 +9,7 @@ import { Legacy } from 'kibana'; import * as Rx from 'rxjs'; import { ActionsConfigType } from './types'; import { TaskManager } from '../../task_manager'; -import { XPackMainPlugin } from '../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; import { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 8ff54e25a0c992..37eb6a9b21d447 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -9,6 +9,7 @@ import { AlertsClient } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; +import { TaskStatus } from '../../task_manager'; const taskManager = taskManagerMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -119,7 +120,7 @@ describe('create()', () => { taskType: 'alerting:123', scheduledAt: new Date(), attempts: 1, - status: 'idle', + status: TaskStatus.Idle, runAt: new Date(), startedAt: null, retryAt: null, @@ -351,7 +352,7 @@ describe('create()', () => { taskType: 'alerting:123', scheduledAt: new Date(), attempts: 1, - status: 'idle', + status: TaskStatus.Idle, runAt: new Date(), startedAt: null, retryAt: null, @@ -749,7 +750,7 @@ describe('create()', () => { taskType: 'alerting:123', scheduledAt: new Date(), attempts: 1, - status: 'idle', + status: TaskStatus.Idle, runAt: new Date(), startedAt: null, retryAt: null, @@ -830,7 +831,7 @@ describe('enable()', () => { id: 'task-123', scheduledAt: new Date(), attempts: 0, - status: 'idle', + status: TaskStatus.Idle, runAt: new Date(), state: {}, params: {}, @@ -907,7 +908,7 @@ describe('enable()', () => { id: 'task-123', scheduledAt: new Date(), attempts: 0, - status: 'idle', + status: TaskStatus.Idle, runAt: new Date(), state: {}, params: {}, diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts index 1d91d4a35d5889..c21c419977bbeb 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts @@ -7,7 +7,7 @@ import sinon from 'sinon'; import { schema } from '@kbn/config-schema'; import { AlertExecutorOptions } from '../types'; -import { ConcreteTaskInstance } from '../../../task_manager'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; import { @@ -30,7 +30,7 @@ beforeAll(() => { mockedTaskInstance = { id: '', attempts: 0, - status: 'running', + status: TaskStatus.Running, version: '123', runAt: new Date(), scheduledAt: new Date(), diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index 0ee1ef843d7d05..ef1f1b41049e5e 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -8,7 +8,7 @@ import Hapi from 'hapi'; import { Legacy } from 'kibana'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; import { TaskManager } from '../../task_manager'; -import { XPackMainPlugin } from '../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { PluginSetupContract as EncryptedSavedObjectsSetupContract, diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 91745246687d99..cf2cbd25072151 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Server } from 'hapi'; import { resolve } from 'path'; -import { APMPluginContract } from '../../../plugins/apm/server/plugin'; +import { APMPluginContract } from '../../../plugins/apm/server'; import { LegacyPluginInitializer } from '../../../../src/legacy/types'; import mappings from './mappings.json'; import { makeApmUsageCollector } from './server/lib/apm_telemetry'; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index e5406e59004fb7..241f272b54a1d9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -17,6 +17,8 @@ import { } from '../../../../utils/testHelpers'; import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; +jest.mock('ui/new_platform'); + function wrapper({ children }: { children: ReactChild }) { return (
, trackMetric); diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/types.ts b/x-pack/legacy/plugins/canvas/shareable_runtime/types.ts index 08004f8ff8b459..191c0405d2e2d4 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/types.ts +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/types.ts @@ -7,7 +7,7 @@ import { RefObject } from 'react'; // @ts-ignore Unlinked Webpack Type import ContainerStyle from 'types/interpreter'; -import { SavedObject, SavedObjectAttributes } from 'src/core/server'; +import { SavedObject, SavedObjectAttributes } from 'src/core/public'; import { ElementPosition, CanvasPage, CanvasWorkpad, RendererSpec } from '../types'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js index 5f7e0269356c57..aac04270988131 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js @@ -6,6 +6,7 @@ import { updateFields, updateFormErrors } from './follower_index_form'; +jest.mock('ui/new_platform'); jest.mock('ui/indices', () => ({ INDEX_ILLEGAL_CHARACTERS_VISIBLE: [], })); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js index a3655df1f89566..01dccf70a21d6e 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js @@ -8,6 +8,7 @@ import { reducer, initialState } from './api'; import { API_STATUS } from '../../constants'; import { apiRequestStart, apiRequestEnd, setApiError } from '../actions'; +jest.mock('ui/new_platform'); jest.mock('../../constants', () => ({ API_STATUS: { IDLE: 'idle', diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index e0ce210328897f..995e261a5c7a7f 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -16,7 +16,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { GraphPlugin } from './plugin'; // @ts-ignore @@ -39,17 +39,21 @@ async function getAngularInjectedDependencies(): Promise { const instance = new GraphPlugin(); instance.setup(npSetup.core, { __LEGACY: { Storage, }, - ...npSetup.plugins, + ...(npSetup.plugins as XpackNpSetupDeps), }); instance.start(npStart.core, { npData: npStart.plugins.data, - navigation, + navigation: npStart.plugins.navigation, __LEGACY: { angularDependencies: await getAngularInjectedDependencies(), }, diff --git a/x-pack/legacy/plugins/graph/public/plugin.ts b/x-pack/legacy/plugins/graph/public/plugin.ts index 4319775147610c..f30742f2c00d22 100644 --- a/x-pack/legacy/plugins/graph/public/plugin.ts +++ b/x-pack/legacy/plugins/graph/public/plugin.ts @@ -8,8 +8,8 @@ import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'src/core/public'; import { Plugin as DataPlugin } from 'src/plugins/data/public'; import { LegacyAngularInjectedDependencies } from './render_app'; -import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/common/types'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; export interface GraphPluginStartDependencies { npData: ReturnType; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index 2cad8f629f2abb..e892643cf80315 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -37,9 +37,9 @@ import { Plugin as DataPlugin, IndexPatternsContract, } from '../../../../../src/plugins/data/public'; -import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/common/types'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { checkLicense } from '../../../../plugins/graph/common/check_license'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; /** * These are dependencies of the Graph app besides the base dependencies diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js index c7537222aafcf9..9463eccb93a022 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js @@ -33,6 +33,8 @@ import { maximumDocumentsRequiredMessage, } from '../../public/store/selectors/lifecycle'; +jest.mock('ui/new_platform'); + let server; let store; const policy = { diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js index 7414962bb2eb16..bdb412eace9f84 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js @@ -17,6 +17,7 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { setHttpClient } from '../../public/services/api'; setHttpClient(axios.create({ adapter: axiosXhrAdapter })); import sinon from 'sinon'; +jest.mock('ui/new_platform'); let server = null; let store = null; diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js index 51f918aa0cd245..e23fe59568e14e 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js @@ -14,6 +14,8 @@ import { ilmFilterExtension, ilmSummaryExtension, } from '../public/extend_index_management'; + +jest.mock('ui/new_platform'); const indexWithoutLifecyclePolicy = { health: 'yellow', status: 'open', diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.test.js b/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.test.js index 80681db886e1f2..85e3365d064f20 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.test.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.test.js @@ -16,6 +16,7 @@ import { defaultColdPhase, defaultWarmPhase } from '../store/defaults'; import { PHASE_INDEX_PRIORITY } from '../constants'; import { getUiMetricsForPhases } from './ui_metric'; +jest.mock('ui/new_platform'); describe('getUiMetricsForPhases', () => { test('gets cold phase', () => { diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index dbf1f4ad61de3b..5466f2572a48e8 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -14,7 +14,7 @@ import { getConfigSchema } from './server/kibana.index'; import { savedObjectMappings } from './server/saved_objects'; import { plugin, InfraServerPluginDeps } from './server/new_platform_index'; import { InfraSetup } from '../../../plugins/infra/server'; -import { APMPluginContract } from '../../../plugins/apm/server/plugin'; +import { APMPluginContract } from '../../../plugins/apm/server'; const APP_ID = 'infra'; const logsSampleDataLinkLabel = i18n.translate('xpack.infra.sampleDataLinkLabel', { diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx index 499398b1c1b96e..16a91e3727c984 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx @@ -188,11 +188,7 @@ describe('LogEntryActionsMenu component', () => { expect( elementWrapper.find(`a${testSubject('~apmLogEntryActionsMenuItem')}`).prop('href') - ).toMatchInlineSnapshot( - `"/app/apm#/traces?kuery=${encodeURIComponent( - 'trace.id:1234567' - )}&rangeFrom=now-1y&rangeTo=now"` - ); + ).toBeDefined(); }); it('renders with a trace id filter and timestamp when present in log entry', () => { @@ -224,11 +220,7 @@ describe('LogEntryActionsMenu component', () => { expect( elementWrapper.find(`a${testSubject('~apmLogEntryActionsMenuItem')}`).prop('href') - ).toMatchInlineSnapshot( - `"/app/apm#/traces?kuery=${encodeURIComponent( - 'trace.id:1234567' - )}&rangeFrom=2019-06-27T17:34:08.693Z&rangeTo=2019-06-27T17:54:08.693Z"` - ); + ).toBeDefined(); }); it('renders as disabled when no supported field is present in log entry', () => { diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx index d018b3a0f38ff0..0d20e7b733dfaf 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx @@ -12,6 +12,7 @@ import url from 'url'; import chrome from 'ui/chrome'; import { InfraLogItem } from '../../../graphql/types'; import { useVisibilityState } from '../../../utils/use_visibility_state'; +import { getTraceUrl } from '../../../../../apm/public/components/shared/Links/apm/ExternalLinks'; const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid']; @@ -123,8 +124,6 @@ const getAPMLink = (logItem: InfraLogItem) => { return url.format({ pathname: chrome.addBasePath('/app/apm'), - hash: `/traces?kuery=${encodeURIComponent( - `trace.id:${traceIdEntry.value}` - )}&rangeFrom=${rangeFrom}&rangeTo=${rangeTo}`, + hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }), }); }; diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx index 3a40c4fa6bf0c4..934022d6e6bd04 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx @@ -7,8 +7,7 @@ import { useState, useCallback } from 'react'; import { npStart } from 'ui/new_platform'; -import { SavedObjectsBatchResponse } from 'src/core/public'; -import { SavedObjectAttributes } from 'src/core/server'; +import { SavedObjectAttributes, SavedObjectsBatchResponse } from 'src/core/public'; export const useBulkGetSavedObject = (type: string) => { const [data, setData] = useState | null>(null); diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_create_saved_object.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_create_saved_object.tsx index 80811a6d6c7bf3..f03a198355bb8a 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_create_saved_object.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_create_saved_object.tsx @@ -7,8 +7,11 @@ import { useState, useCallback } from 'react'; import { npStart } from 'ui/new_platform'; -import { SavedObjectsCreateOptions, SimpleSavedObject } from 'src/core/public'; -import { SavedObjectAttributes } from 'src/core/server'; +import { + SavedObjectAttributes, + SavedObjectsCreateOptions, + SimpleSavedObject, +} from 'src/core/public'; export const useCreateSavedObject = (type: string) => { const [data, setData] = useState | null>(null); diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_find_saved_object.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_find_saved_object.tsx index 949a2344418e9b..2487d830266b17 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_find_saved_object.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_find_saved_object.tsx @@ -7,8 +7,7 @@ import { useState, useCallback } from 'react'; import { npStart } from 'ui/new_platform'; -import { SavedObjectsBatchResponse } from 'src/core/public'; -import { SavedObjectAttributes } from 'src/core/server'; +import { SavedObjectAttributes, SavedObjectsBatchResponse } from 'src/core/public'; export const useFindSavedObject = (type: string) => { const [data, setData] = useState | null>(null); diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_space_id.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_space_id.ts index 4642f7fd26f21f..52c896feef1a22 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_space_id.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_space_id.ts @@ -11,6 +11,7 @@ import * as rt from 'io-ts'; import { useKibanaInjectedVar } from './use_kibana_injected_var'; export const useKibanaSpaceId = (): string => { + // NOTICE: use of `activeSpace` is deprecated and will not be made available in the New Platform. const activeSpace = useKibanaInjectedVar('activeSpace'); return pipe( diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts index e88736b08b95b9..a0fd6d3e951bfb 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -9,7 +9,7 @@ import { Lifecycle } from 'hapi'; import { ObjectType } from '@kbn/config-schema'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { RouteMethod, RouteConfig } from '../../../../../../../../src/core/server'; -import { APMPluginContract } from '../../../../../../../plugins/apm/server/plugin'; +import { APMPluginContract } from '../../../../../../../plugins/apm/server'; // NP_TODO: Compose real types from plugins we depend on, no "any" export interface InfraServerPluginDeps { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 3ffc7bcf275d7d..b8176cdb14c125 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -16,26 +16,23 @@ import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plug import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); -import { TopNavMenuData } from '../../../../../../src/legacy/core_plugins/navigation/public'; +import { TopNavMenuData } from '../../../../../../src/plugins/navigation/public'; import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public'; import { coreMock } from 'src/core/public/mocks'; -jest.mock('../../../../../../src/legacy/core_plugins/navigation/public/legacy', () => ({ - start: { - ui: { - TopNavMenu: jest.fn(() => null), - }, - }, -})); - -import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; - -const { TopNavMenu } = navigation.ui; - jest.mock('ui/new_platform'); jest.mock('../persistence'); jest.mock('src/core/public'); +import { npStart } from 'ui/new_platform'; +jest + .spyOn(npStart.plugins.navigation.ui.TopNavMenu.prototype, 'constructor') + .mockImplementation(() => { + return
; + }); + +const { TopNavMenu } = npStart.plugins.navigation.ui; + const waitForPromises = () => new Promise(resolve => setTimeout(resolve)); function createMockFrame(): jest.Mocked { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index fda07c9b6bb774..d3f2ac624a6920 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -11,9 +11,8 @@ import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { AppMountContext, NotificationsStart } from 'src/core/public'; -import { SavedQuery } from 'src/legacy/core_plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; +import { npStart } from 'ui/new_platform'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; @@ -23,6 +22,7 @@ import { esFilters, IndexPattern as IndexPatternInstance, IndexPatternsContract, + SavedQuery, } from '../../../../../../src/plugins/data/public'; interface State { @@ -160,7 +160,7 @@ export function App({ [] ); - const { TopNavMenu } = navigation.ui; + const { TopNavMenu } = npStart.plugins.navigation.ui; return ( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx index 4d9bc6276d52a9..99cc22cc6e8903 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx @@ -79,6 +79,7 @@ export function ChangeIndexPattern({ searchable singleSelection="always" options={indexPatternRefs.map(({ title, id }) => ({ + key: id, label: title, value: id, checked: id === indexPatternId ? 'on' : undefined, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx index 8fc80e14dc166a..6a2f6234279c70 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx @@ -298,6 +298,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ 'data-test-subj': 'indexPattern-switch-link', className: 'lnsInnerIndexPatternDataPanel__triggerButton', }} + indexPatternId={currentIndexPatternId} indexPatternRefs={indexPatternRefs} onChangeIndexPattern={(newId: string) => { onChangeIndexPattern(newId); diff --git a/x-pack/legacy/plugins/lens/server/usage/task.ts b/x-pack/legacy/plugins/lens/server/usage/task.ts index feb73538f44f05..64bc37ce909a28 100644 --- a/x-pack/legacy/plugins/lens/server/usage/task.ts +++ b/x-pack/legacy/plugins/lens/server/usage/task.ts @@ -15,7 +15,7 @@ import { DeleteDocumentByQueryResponse, } from 'elasticsearch'; import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { RunContext } from '../../../task_manager'; import { getVisualizationCounts } from './visualization_counts'; diff --git a/x-pack/legacy/plugins/license_management/__jest__/api_responses/upload_license.js b/x-pack/legacy/plugins/license_management/__jest__/api_responses/upload_license.js index 1c650b3f60e3f0..633c53ab174528 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/api_responses/upload_license.js +++ b/x-pack/legacy/plugins/license_management/__jest__/api_responses/upload_license.js @@ -10,7 +10,6 @@ export const UPLOAD_LICENSE_EXPIRED = [ '{"acknowledged": "true", "license_status": "expired"}', ]; -/* eslint-disable max-len */ export const UPLOAD_LICENSE_REQUIRES_ACK = [ 200, { 'Content-Type': 'application/json' }, @@ -24,7 +23,6 @@ export const UPLOAD_LICENSE_REQUIRES_ACK = [ } }`, ]; -/* eslint-enable max-len */ export const UPLOAD_LICENSE_SUCCESS = [ 200, diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js index a55641b6ee8380..532c1d5e1a32f7 100644 --- a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js @@ -126,7 +126,6 @@ export class StartTrial extends React.PureComponent { securityDocumentationLinkText: ( @@ -143,7 +142,6 @@ export class StartTrial extends React.PureComponent { termsAndConditionsLinkText: ( diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js index eccbf0e09b2e93..51b3af2b6308fa 100644 --- a/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js @@ -64,7 +64,6 @@ const dispatchFromResponse = async ( const first = i18n.translate( 'xpack.licenseMgmt.uploadLicense.problemWithUploadedLicenseDescription', { - // eslint-disable-next-line max-len defaultMessage: 'Some functionality will be lost if you replace your {currentLicenseType} license with a {newLicenseType} license. Review the list of features below.', values: { diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts index abd658ff91db04..1da3c942830ca7 100644 --- a/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts +++ b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts @@ -5,7 +5,7 @@ */ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { PLUGIN } from '../../common/constants'; import { Breadcrumb } from './application/breadcrumbs'; diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/types.ts b/x-pack/legacy/plugins/license_management/server/np_ready/types.ts index a636481323e29f..0e66946ec1cc6c 100644 --- a/x-pack/legacy/plugins/license_management/server/np_ready/types.ts +++ b/x-pack/legacy/plugins/license_management/server/np_ready/types.ts @@ -5,7 +5,7 @@ */ import { IRouter } from 'src/core/server'; -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { ElasticsearchPlugin } from '../../../../../../src/legacy/core_plugins/elasticsearch'; export interface Dependencies { diff --git a/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.test.js b/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.test.js index 2c5e3ca3e4e36e..5feadeb55b96c9 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint max-len: 0 */ - import { emsRasterTileToEmsVectorTile } from './ems_raster_tile_to_ems_vector_tile'; describe('emsRasterTileToEmsVectorTile', () => { diff --git a/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.test.js b/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.test.js index e63ca0eecac5d5..39960e30320888 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint max-len: 0 */ - import { moveApplyGlobalQueryToSources } from './move_apply_global_query'; describe('moveApplyGlobalQueryToSources', () => { diff --git a/x-pack/legacy/plugins/maps/common/migrations/references.test.js b/x-pack/legacy/plugins/maps/common/migrations/references.test.js index 38eb9ff6f48f49..40f6fd72a48d7d 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/references.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/references.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint max-len: 0 */ - import { extractReferences, injectReferences } from './references'; import { ES_GEO_GRID, ES_SEARCH, ES_PEW_PEW } from '../constants'; diff --git a/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.test.js b/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.test.js index 3249d5092048c5..20524b7c7e6d09 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint max-len: 0 */ - import { topHitsTimeToSort } from './top_hits_time_to_sort'; describe('topHitsTimeToSort', () => { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 2c87469867e23c..83cebc9e835204 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -147,7 +147,7 @@ export class MBMapContainer extends React.Component { }); mbMap.on('load', () => { emptyImage = new Image(); - // eslint-disable-next-line max-len + emptyImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII='; emptyImage.crossOrigin = 'anonymous'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js index 3c3d3d503662eb..22b10880475394 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js @@ -17,7 +17,6 @@ export function getEmsUnavailableMessage() { } return i18n.translate('xpack.maps.source.ems.disabledDescription', { - // eslint-disable-next-line max-len defaultMessage: 'Access to Elastic Maps Service has been disabled. Ask your system administrator to set "map.includeElasticMapsService" in kibana.yml.', }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js index 8855de2110003d..1ea065c7e9e675 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js @@ -92,7 +92,7 @@ describe('getLinearGradient', () => { 'rgb(32,112,180)', 'rgb(7,47,107)', ]; - // eslint-disable-next-line max-len + expect(getLinearGradient(colorRamp)).toBe( 'linear-gradient(to right, rgb(247,250,255) 0%, rgb(221,234,247) 14%, rgb(197,218,238) 28%, rgb(157,201,224) 42%, rgb(106,173,213) 57%, rgb(65,145,197) 71%, rgb(32,112,180) 85%, rgb(7,47,107) 100%)' ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js index acccc668b08760..fd4794b385bd2c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js @@ -27,7 +27,7 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red'); - // eslint-disable-next-line max-len + expect(styledSvg.split('\n')[1]).toBe( '' ); @@ -37,7 +37,7 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white'); - // eslint-disable-next-line max-len + expect(styledSvg.split('\n')[1]).toBe( '' ); @@ -47,7 +47,7 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white', '2px'); - // eslint-disable-next-line max-len + expect(styledSvg.split('\n')[1]).toBe( '' ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index e2f32c109c0e32..d386c0ad4a5e0e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -80,7 +80,7 @@ export class VectorStyle extends AbstractStyle { this._descriptor.properties[VECTOR_STYLES.ICON_SIZE], VECTOR_STYLES.ICON_SIZE ); - // eslint-disable-next-line max-len + this._iconOrientationProperty = this._makeOrientationProperty( this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION], VECTOR_STYLES.ICON_ORIENTATION diff --git a/x-pack/legacy/plugins/maps/public/meta.js b/x-pack/legacy/plugins/maps/public/meta.js index d2f3e016176db0..c47c2847ffd149 100644 --- a/x-pack/legacy/plugins/maps/public/meta.js +++ b/x-pack/legacy/plugins/maps/public/meta.js @@ -42,7 +42,7 @@ export function getEMSClient() { false ); const proxyPath = proxyElasticMapsServiceInMaps ? relativeToAbsolute('..') : ''; - // eslint-disable-next-line max-len + const manifestServiceUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_CATALOGUE_PATH}`) : chrome.getInjected('emsManifestServiceUrl'); diff --git a/x-pack/legacy/plugins/maps/server/routes.js b/x-pack/legacy/plugins/maps/server/routes.js index ecb20ea0100ed0..e8c8c23ae2ca10 100644 --- a/x-pack/legacy/plugins/maps/server/routes.js +++ b/x-pack/legacy/plugins/maps/server/routes.js @@ -300,7 +300,7 @@ export function initRoutes(server, licenseUid) { const vectorStyle = await tmsService.getVectorStyleSheet(); const sourceManifest = vectorStyle.sources[request.query.sourceId]; - // eslint-disable-next-line max-len + const newUrl = `${GIS_API_PATH}/${EMS_TILES_VECTOR_TILE_PATH}?id=${request.query.id}&sourceId=${request.query.sourceId}&x={x}&y={y}&z={z}`; return { ...sourceManifest, diff --git a/x-pack/legacy/plugins/maps/server/sample_data/ecommerce_saved_objects.js b/x-pack/legacy/plugins/maps/server/sample_data/ecommerce_saved_objects.js index 05d56526865505..d5bcfa7cd1512c 100644 --- a/x-pack/legacy/plugins/maps/server/sample_data/ecommerce_saved_objects.js +++ b/x-pack/legacy/plugins/maps/server/sample_data/ecommerce_saved_objects.js @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable max-len */ import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/server/sample_data/flights_saved_objects.js b/x-pack/legacy/plugins/maps/server/sample_data/flights_saved_objects.js index 5e5a5cb0b458f9..aa3d5488764ad2 100644 --- a/x-pack/legacy/plugins/maps/server/sample_data/flights_saved_objects.js +++ b/x-pack/legacy/plugins/maps/server/sample_data/flights_saved_objects.js @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable max-len */ import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/server/sample_data/web_logs_saved_objects.js b/x-pack/legacy/plugins/maps/server/sample_data/web_logs_saved_objects.js index 65bfb90ca1f108..74039b11db727d 100644 --- a/x-pack/legacy/plugins/maps/server/sample_data/web_logs_saved_objects.js +++ b/x-pack/legacy/plugins/maps/server/sample_data/web_logs_saved_objects.js @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable max-len */ import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/ml/common/types/modules.ts b/x-pack/legacy/plugins/ml/common/types/modules.ts index 52259d8748a95b..cd6395500a804a 100644 --- a/x-pack/legacy/plugins/ml/common/types/modules.ts +++ b/x-pack/legacy/plugins/ml/common/types/modules.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectAttributes } from 'src/core/server/types'; +import { SavedObjectAttributes } from 'src/core/public'; import { Datafeed, Job } from '../../public/application/jobs/new_job/common/job_creator/configs'; export interface ModuleJob { diff --git a/x-pack/legacy/plugins/ml/common/util/__tests__/job_utils.js b/x-pack/legacy/plugins/ml/common/util/__tests__/job_utils.js index bf98e158a2e06f..60270fd4388465 100644 --- a/x-pack/legacy/plugins/ml/common/util/__tests__/job_utils.js +++ b/x-pack/legacy/plugins/ml/common/util/__tests__/job_utils.js @@ -137,7 +137,7 @@ describe('ML - job utils', () => { field_name: 'bytes', partition_field_name: 'clientip', detector_description: 'High bytes client IP', - }, // eslint-disable-line max-len + }, { function: 'freq_rare', by_field_name: 'uri', diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 514749ec1b08b7..62bb2de3fcb313 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -1301,7 +1301,6 @@ export class TimeSeriesExplorer extends React.Component { title={ { url_name: 'Show dashboard', time_range: '1h', url_value: - "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(filters:!(),query:(language:kuery,query:'airline:\"$airline$\"'))", // eslint-disable-line max-len + "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(filters:!(),query:(language:kuery,query:'airline:\"$airline$\"'))", }; const TEST_DISCOVER_URL: KibanaUrlConfig = { url_name: 'Raw data', time_range: 'auto', url_value: - "kibana#/discover?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'airline:\"$airline$\"'))", // eslint-disable-line max-len + "kibana#/discover?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'airline:\"$airline$\"'))", }; const TEST_DASHBOARD_LUCENE_URL: KibanaUrlConfig = { url_name: 'Show dashboard', time_range: '1h', url_value: - "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(filters:!(),query:(language:lucene,query:'airline:\"$airline$\"'))", // eslint-disable-line max-len + "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=(filters:!(),query:(language:lucene,query:'airline:\"$airline$\"'))", }; const TEST_OTHER_URL: UrlConfig = { @@ -121,7 +121,7 @@ describe('ML - custom URL utils', () => { test('replaces tokens as expected for a Kibana Dashboard type URL', () => { expect(replaceTokensInUrlValue(TEST_DASHBOARD_URL, 300, TEST_DOC, 'timestamp')).toBe( "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(filters:!(),query:(language:kuery,query:'airline:\"AAL\"'))" - ); // eslint-disable-line max-len + ); }); test('replaces tokens containing special characters as expected for a Kibana Dashboard type URL', () => { @@ -129,7 +129,7 @@ describe('ML - custom URL utils', () => { replaceTokensInUrlValue(TEST_DASHBOARD_URL, 300, TEST_RECORD_SPECIAL_CHARS, 'timestamp') ).toBe( "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(filters:!(),query:(language:kuery,query:'airline:\"%3C%3E%3A%3B%5B%7D%5C%22)\"'))" - ); // eslint-disable-line max-len + ); }); test('replaces tokens containing special characters as expected for a Kibana Dashboard type URL where query language is lucene', () => { @@ -142,13 +142,13 @@ describe('ML - custom URL utils', () => { ) ).toBe( "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(filters:!(),query:(language:lucene,query:'airline:\"%5C%3C%5C%3E%5C%3A%3B%5C%5B%5C%7D%5C%22%5C)\"'))" - ); // eslint-disable-line max-len + ); }); test('replaces tokens as expected for a Kibana Discover type URL', () => { expect(replaceTokensInUrlValue(TEST_DISCOVER_URL, 300, TEST_DOC, 'timestamp')).toBe( "kibana#/discover?_g=(time:(from:'2017-02-09T16:05:00.000Z',mode:absolute,to:'2017-02-09T16:20:00.000Z'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'airline:\"AAL\"'))" - ); // eslint-disable-line max-len + ); }); test('replaces token with multiple influencer values', () => { @@ -161,7 +161,7 @@ describe('ML - custom URL utils', () => { ) ).toBe( "kibana#/discover?_g=(time:(from:'2017-02-09T16:05:00.000Z',mode:absolute,to:'2017-02-09T16:20:00.000Z'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'(airline:\"AAL\" OR airline:\"AWE\")'))" - ); // eslint-disable-line max-len + ); }); test('removes tokens with no influencer values', () => { @@ -174,7 +174,7 @@ describe('ML - custom URL utils', () => { ) ).toBe( "kibana#/discover?_g=(time:(from:'2017-02-09T16:05:00.000Z',mode:absolute,to:'2017-02-09T16:20:00.000Z'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:''))" - ); // eslint-disable-line max-len + ); }); test('replaces tokens as expected for other type URL with tokens', () => { @@ -254,19 +254,19 @@ describe('ML - custom URL utils', () => { test('returns expected URL for a Kibana Dashboard type URL', () => { expect(getUrlForRecord(TEST_DASHBOARD_URL, TEST_RECORD)).toBe( "kibana#/dashboard/5f112420-9fc6-11e8-9130-150552a4bef3?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(filters:!(),query:(language:kuery,query:'airline:\"AAL\"'))" - ); // eslint-disable-line max-len + ); }); test('returns expected URL for a Kibana Discover type URL', () => { expect(getUrlForRecord(TEST_DISCOVER_URL, TEST_RECORD)).toBe( "kibana#/discover?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'airline:\"AAL\"'))" - ); // eslint-disable-line max-len + ); }); test('returns expected URL for a Kibana Discover type URL when record field contains special characters', () => { expect(getUrlForRecord(TEST_DISCOVER_URL, TEST_RECORD_SPECIAL_CHARS)).toBe( "kibana#/discover?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(index:bf6e5860-9404-11e8-8d4c-593f69c47267,query:(language:kuery,query:'airline:\"%3C%3E%3A%3B%5B%7D%5C%22)\"'))" - ); // eslint-disable-line max-len + ); }); test('replaces tokens with nesting', () => { @@ -274,7 +274,7 @@ describe('ML - custom URL utils', () => { url_name: 'Raw data', time_range: 'auto', url_value: - 'kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027$http.response.status_code$\u0027),type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),query:(language:kuery,query:\u0027\u0027))', // eslint-disable-line max-len + 'kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027$http.response.status_code$\u0027),type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),query:(language:kuery,query:\u0027\u0027))', }; const testRecord = { @@ -303,7 +303,7 @@ describe('ML - custom URL utils', () => { expect(getUrlForRecord(testUrlApache, testRecord)).toBe( "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027403\u0027),type:phrase,value:\u0027403\u0027),query:(match:(http.response.status_code:(query:\u0027403\u0027,type:phrase))))),query:(language:kuery,query:\u0027\u0027))" - ); // eslint-disable-line max-len + ); }); test('does not escape special characters for Lucene query language inside of the filter', () => { @@ -311,7 +311,7 @@ describe('ML - custom URL utils', () => { url_name: 'Lucene query with filters', time_range: 'auto', url_value: - "kibana#/dashboard/884c8780-0618-11ea-b671-c9c7e0ebf1f2?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'$earliest$',to:'$latest$'))&_a=(description:'',filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'7a0a6120-0612-11ea-b671-c9c7e0ebf1f2',key:'at@name',negate:!f,params:(query:'$at@name$'),type:phrase),query:(match_phrase:('at@name':'$at@name$')))),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,w:24,x:0,y:0),id:'19067710-0617-11ea-b671-c9c7e0ebf1f2',panelIndex:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,type:visualization,version:'8.0.0')),query:(language:lucene,query:''),timeRestore:!f,title:special-lucine,viewMode:view)", // eslint-disable-line max-len + "kibana#/dashboard/884c8780-0618-11ea-b671-c9c7e0ebf1f2?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'$earliest$',to:'$latest$'))&_a=(description:'',filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'7a0a6120-0612-11ea-b671-c9c7e0ebf1f2',key:'at@name',negate:!f,params:(query:'$at@name$'),type:phrase),query:(match_phrase:('at@name':'$at@name$')))),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,w:24,x:0,y:0),id:'19067710-0617-11ea-b671-c9c7e0ebf1f2',panelIndex:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,type:visualization,version:'8.0.0')),query:(language:lucene,query:''),timeRestore:!f,title:special-lucine,viewMode:view)", }; const testRecord = { @@ -342,7 +342,7 @@ describe('ML - custom URL utils', () => { expect(getUrlForRecord(testUrlLuceneFilters, testRecord)).toBe( "kibana#/dashboard/884c8780-0618-11ea-b671-c9c7e0ebf1f2?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'2017-02-09T15:10:00.000Z',to:'2017-02-09T17:15:00.000Z'))&_a=(description:'',filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'7a0a6120-0612-11ea-b671-c9c7e0ebf1f2',key:'at@name',negate:!f,params:(query:'contains%5C%20and%20a%20%2F'),type:phrase),query:(match_phrase:('at@name':'contains%5C%20and%20a%20%2F')))),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,w:24,x:0,y:0),id:'19067710-0617-11ea-b671-c9c7e0ebf1f2',panelIndex:f7ef89e3-62a6-42da-84b2-c815d8da8bb4,type:visualization,version:'8.0.0')),query:(language:lucene,query:''),timeRestore:!f,title:special-lucine,viewMode:view)" - ); // eslint-disable-line max-len + ); }); test('returns expected URL for other type URL', () => { diff --git a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js index 6157778a063637..5d38de4a6ba87c 100644 --- a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js +++ b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js @@ -694,7 +694,6 @@ export const elasticsearchJsPlugin = (Client, config, components) => { ml.fileStructure = ca({ urls: [ { - // eslint-disable-next-line max-len fmt: '/_ml/find_file_structure?&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', req: { diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts b/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts index e7c896bb36ed84..6b426169799a7b 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts +++ b/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts @@ -5,7 +5,7 @@ */ import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges'; -import { XPackMainPlugin } from '../../../../../../legacy/plugins/xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../../xpack_main/server/xpack_main'; import { callWithRequestType } from '../../../common/types/kibana'; import { isSecurityDisabled } from '../../lib/security_utils'; import { upgradeCheckProvider } from './upgrade'; diff --git a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts b/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts index 84d9961b0c6f0f..26fdff73b34606 100644 --- a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts +++ b/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { XPackMainPlugin } from '../../../../../legacy/plugins/xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; export function isSecurityDisabled(xpackMainPlugin: XPackMainPlugin): boolean; diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts index 727d05605614fb..c468c87d7abc8c 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts +++ b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts @@ -12,7 +12,7 @@ import { Logger, PluginInitializerContext, CoreSetup } from 'src/core/server'; import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { CloudSetup } from '../../../../../plugins/cloud/server'; -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { addLinksToSampleDatasets } from '../lib/sample_data_sets'; import { checkLicense } from '../lib/check_license'; // @ts-ignore: could not find declaration file for module diff --git a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js index 585486fe48a334..450484fdafbb38 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js @@ -53,7 +53,7 @@ const logs = { type: 'server', node: 'foobar2', message: - 'high disk watermark [90%] exceeded on [-pH5RhfsRl6FDeTPwD5vEw][Elastic-MBP.local][/Users/chris/Development/repos/kibana/.es/8.0.0/data/nodes/0] free: 29.5gb[6.3%], shards will be relocated away from this node', // eslint-disable-line max-len + 'high disk watermark [90%] exceeded on [-pH5RhfsRl6FDeTPwD5vEw][Elastic-MBP.local][/Users/chris/Development/repos/kibana/.es/8.0.0/data/nodes/0] free: 29.5gb[6.3%], shards will be relocated away from this node', }, { timestamp: '2019-03-18T12:49:24.414Z', @@ -88,7 +88,7 @@ const logs = { type: 'server', node: 'foobar', message: - 'high disk watermark [90%] exceeded on [-pH5RhfsRl6FDeTPwD5vEw][Elastic-MBP.local][/Users/chris/Development/repos/kibana/.es/8.0.0/data/nodes/0] free: 29.3gb[6.2%], shards will be relocated away from this node', // eslint-disable-line max-len + 'high disk watermark [90%] exceeded on [-pH5RhfsRl6FDeTPwD5vEw][Elastic-MBP.local][/Users/chris/Development/repos/kibana/.es/8.0.0/data/nodes/0] free: 29.3gb[6.2%], shards will be relocated away from this node', }, { timestamp: '2019-03-18T12:48:53.753Z', diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js index 94f8b110db4769..020122fac2e7f2 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js @@ -12,7 +12,7 @@ export function getPageData($injector) { const $route = $injector.get('$route'); const globalState = $injector.get('globalState'); const timeBounds = timefilter.getBounds(); - const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr/${$route.current.params.index}/shard/${$route.current.params.shardId}`; // eslint-disable-line max-len + const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr/${$route.current.params.index}/shard/${$route.current.params.shardId}`; return $http .post(url, { diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/map_nodes_metrics.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/map_nodes_metrics.test.js index 37171065107f4d..bcd59336588977 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/map_nodes_metrics.test.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/map_nodes_metrics.test.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint max-len: 0 */ import { mapNodesMetrics } from '../map_nodes_metrics'; describe('map nodes metrics', () => { diff --git a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js index 6bbae508e12618..d2385dc900bb28 100644 --- a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js +++ b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js @@ -15,7 +15,6 @@ import { init as initHttp } from '../../../public/app/services/http'; import { init as initNotification } from '../../../public/app/services/notification'; import { init as initUiMetric } from '../../../public/app/services/ui_metric'; import { init as initHttpRequests } from './http_requests'; -import { createUiStatsReporter } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; export const setupEnvironment = () => { chrome.breadcrumbs = { @@ -25,7 +24,7 @@ export const setupEnvironment = () => { initHttp(axios.create({ adapter: axiosXhrAdapter }), path => path); initBreadcrumb(() => {}, MANAGEMENT_BREADCRUMB); initNotification(toastNotifications, fatalError); - initUiMetric(createUiStatsReporter); + initUiMetric(() => () => {}); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js index d177238a037abc..c1ef7db2efc5cf 100644 --- a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js +++ b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js @@ -124,7 +124,7 @@ describe('Create Remote cluster', () => { form.setComboBoxValue('remoteClusterFormSeedsInput', `192.16${char}:3000`); expect(form.getErrorsMessages()).toContain( `Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.` - ); // eslint-disable-line max-len + ); }; [...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS] diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js index 4122f84a307606..a8ac99a48ca894 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js @@ -11,6 +11,8 @@ import nodeCrypto from '@elastic/node-crypto'; import { CancellationToken } from '../../../../common/cancellation_token'; import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; +// Reporting uses an unconventional directory structure so the linter marks this as a violation +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { StringFormat } from '../../../../../../../../src/plugins/data/server'; import { executeJobFactory } from '../execute_job'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js index 87c054c8606f19..5f6bc32f10d8be 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js @@ -7,6 +7,8 @@ import expect from '@kbn/expect'; import { FieldFormatsService } from '../../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; +// Reporting uses an unconventional directory structure so the linter marks this as a violation +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { BytesFormat, NumberFormat } from '../../../../../../../../../src/plugins/data/server'; import { fieldFormatMapFactory } from '../field_format_map'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 081800d1d7b2d3..ba29c3ef1ec3fb 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -18,11 +18,15 @@ import { import { getDataSource } from './get_data_source'; import { getFilters } from './get_filters'; import { JobParamsDiscoverCsv } from '../../../csv/types'; + import { esQuery, esFilters, IIndexPattern, Query, + // Reporting uses an unconventional directory structure so the linter marks this as a violation, server files should + // be moved under reporting/server/ + // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../../../../src/plugins/data/server'; const getEsQueryConfig = async (config: any) => { diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts index f8913a0dcea6b4..7874f67ef4c0c7 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; import { ExportTypesRegistry } from '../lib/export_types_registry'; /* diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 597e9cafdc2a20..c17b969d5d7fac 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -7,7 +7,7 @@ import { ResponseObject } from 'hapi'; import { EventEmitter } from 'events'; import { Legacy } from 'kibana'; -import { XPackMainPlugin } from '../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../xpack_main/server/xpack_main'; import { ElasticsearchPlugin, CallCluster, diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js index e5679973b6e700..39153cda1f99d3 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js @@ -17,6 +17,7 @@ import { tabToHumanizedMap, } from '../../components'; +jest.mock('ui/new_platform'); jest.mock('../../../services', () => { const services = require.requireActual('../../../services'); return { diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js index 5bb3a9722c7664..627091e0cf7492 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js @@ -8,6 +8,8 @@ import { registerTestBed } from '../../../../../../../test_utils'; import { rollupJobsStore } from '../../store'; import { JobList } from './job_list'; +jest.mock('ui/new_platform'); + jest.mock('ui/chrome', () => ({ addBasePath: () => {}, breadcrumbs: { set: () => {} }, diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js index 68fe55a701b6b3..c688343d5f7681 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js @@ -11,6 +11,7 @@ import { getJobs, jobCount } from '../../../../../fixtures'; import { rollupJobsStore } from '../../../store'; import { JobTable } from './job_table'; +jest.mock('ui/new_platform'); jest.mock('../../../services', () => { const services = require.requireActual('../../../services'); return { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts index eb7b6a2880576b..714b856b54c68c 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts @@ -1,4 +1,3 @@ -/* eslint quotes: 0 */ export const inputTimes = [ { type:"BooleanQuery", diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts index 7862aa386785bd..9b25f8bb36b0cf 100644 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts +++ b/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts @@ -6,7 +6,7 @@ import { ServerRoute } from 'hapi'; import { ElasticsearchPlugin, Request } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; export type RegisterRoute = (args: ServerRoute & { config: any }) => void; diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 848cb79bc1992a..a061424c62973f 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -12,7 +12,6 @@ import { initLoggedOutView } from './server/routes/views/logged_out'; import { AuditLogger } from '../../server/lib/audit_logger'; import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; import { KibanaRequest } from '../../../../src/core/server'; -import { createCSPRuleString } from '../../../../src/legacy/server/csp'; export const security = kibana => new kibana.Plugin({ @@ -22,28 +21,19 @@ export const security = kibana => require: ['kibana', 'elasticsearch', 'xpack_main'], config(Joi) { + const HANDLED_IN_NEW_PLATFORM = Joi.any().description( + 'This key is handled in the new platform security plugin ONLY' + ); return Joi.object({ enabled: Joi.boolean().default(true), - cookieName: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), - encryptionKey: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), + cookieName: HANDLED_IN_NEW_PLATFORM, + encryptionKey: HANDLED_IN_NEW_PLATFORM, session: Joi.object({ - idleTimeout: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), - lifespan: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), + idleTimeout: HANDLED_IN_NEW_PLATFORM, + lifespan: HANDLED_IN_NEW_PLATFORM, }).default(), - secureCookies: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), - loginAssistanceMessage: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), + secureCookies: HANDLED_IN_NEW_PLATFORM, + loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM, authorization: Joi.object({ legacyFallback: Joi.object({ enabled: Joi.boolean().default(true), // deprecated @@ -52,9 +42,7 @@ export const security = kibana => audit: Joi.object({ enabled: Joi.boolean().default(false), }).default(), - authc: Joi.any().description( - 'This key is handled in the new platform security plugin ONLY' - ), + authc: HANDLED_IN_NEW_PLATFORM, }).default(); }, @@ -112,8 +100,6 @@ export const security = kibana => secureCookies: securityPlugin.__legacyCompat.config.secureCookies, session: { tenant: server.newPlatform.setup.core.http.basePath.serverBasePath, - idleTimeout: securityPlugin.__legacyCompat.config.session.idleTimeout, - lifespan: securityPlugin.__legacyCompat.config.session.lifespan, }, enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled'), }; @@ -148,7 +134,6 @@ export const security = kibana => isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: createCSPRuleString(config.get('csp.rules')), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/legacy/plugins/security/public/lib/validate_user.ts b/x-pack/legacy/plugins/security/public/lib/validate_user.ts index 6fd10e77f8ca03..113aaacdcbf966 100644 --- a/x-pack/legacy/plugins/security/public/lib/validate_user.ts +++ b/x-pack/legacy/plugins/security/public/lib/validate_user.ts @@ -16,7 +16,7 @@ export interface UserValidationResult { error?: string; } -const validEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; // eslint-disable-line max-len +const validEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const validUsernameRegex = /[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*/; export class UserValidator { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx index cb60b773f92e0e..67c32c8393171f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx @@ -9,8 +9,11 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../plugins/features/public'; +// These modules should be moved into a common directory +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { Actions } from '../../../../../../../../plugins/security/server/authorization/actions'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { privilegesFactory } from '../../../../../../../../plugins/security/server/authorization/privileges'; import { RawKibanaPrivileges, Role } from '../../../../../common/model'; import { EditRolePage } from './edit_role_page'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx index c5bf910b007d00..7637d28dd42297 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx @@ -23,7 +23,7 @@ import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { toastNotifications } from 'ui/notify'; import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../plugins/features/public'; import { KibanaPrivileges, RawKibanaPrivileges, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx index 8425826235f0bf..a05dc687fce4ac 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx @@ -17,7 +17,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component } from 'react'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { AllowedPrivilege, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx index 199067b2e74697..97d61916926b69 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx @@ -8,7 +8,7 @@ import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role } from '../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../lib/kibana_privilege_calculator'; import { RoleValidator } from '../../../lib/validate_role'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 74d62b0c867580..1f29f774fd6cc0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonGroup, EuiButtonGroupProps, EuiComboBox, EuiSuperSelect } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { SimplePrivilegeSection } from './simple_privilege_section'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index d564179798ad87..7768dc769a32f7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -15,7 +15,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx index 3d4a0d89ed7a1f..ee121caa13a2af 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiInMemoryTable } from '@elastic/eui'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../..//lib/kibana_privilege_calculator'; import { PrivilegeMatrix } from './privilege_matrix'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index be49494efbe9a8..92dace65d466c4 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -24,7 +24,7 @@ import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; import { SpaceAvatar } from '../../../../../../../../../spaces/public/components'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { FeaturesPrivileges, Role } from '../../../../../../../../common/model'; import { CalculatedPrivilege } from '../../../../../../../lib/kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index a616d3537cee30..5abb87d23bb6e6 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -25,7 +25,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { AllowedPrivilege, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index cdb5521bd0c863..d324cf99c8418a 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -16,7 +16,7 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { isReservedRole } from '../../../../../../../lib/role_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 8b54f50c4beebc..09c612526918fe 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -15,7 +15,6 @@ import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { SpacesManager } from '../../../../../spaces/public/lib'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; @@ -79,7 +78,7 @@ const routeDefinition = action => ({ }, spaces(spacesEnabled) { if (spacesEnabled) { - return new SpacesManager().getSpaces(); + return kfetch({ method: 'get', pathname: '/api/spaces/space' }); } return []; }, diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index acea2d1cce468b..66e9bc700b3a16 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -19,6 +19,7 @@ const testWidth = 640; const usersViewing = ['elastic']; const mockUseKibanaCore = useKibanaCore as jest.Mock; +jest.mock('ui/new_platform'); jest.mock('../../../lib/compose/kibana_core'); mockUseKibanaCore.mockImplementation(() => ({ uiSettings: mockUiSettings, diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index fbeb1a2090cfde..d7061ba4efd9cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -15,6 +15,7 @@ import { HostsTableType } from '../../store/hosts/model'; import { RouteSpyState } from '../../utils/route/types'; import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; +jest.mock('ui/new_platform'); jest.mock('./breadcrumbs', () => ({ setBreadcrumbs: jest.fn(), })); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index e84e3066e4f695..00b1d4c066d4a2 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -16,6 +16,8 @@ import { CONSTANTS } from '../../url_state/constants'; import { TabNavigationComponent } from './'; import { TabNavigationProps } from './types'; +jest.mock('ui/new_platform'); + describe('Tab Navigation', () => { const pageName = SiemPageName.hosts; const hostName = 'siem-window'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 215d9da6eb7ff8..47a4dafa2d17f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -6,6 +6,7 @@ import { SignalSourceHit, SignalSearchResponse } from '../types'; import { Logger } from 'kibana/server'; +import { loggingServiceMock } from '../../../../../../../../../src/core/server/mocks'; import { RuleTypeParams, OutputRuleAlertRest } from '../../types'; export const sampleRuleAlertParams = ( @@ -279,12 +280,4 @@ export const sampleRule = (): Partial => { }; }; -export const mockLogger: Logger = { - log: jest.fn(), - trace: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), -}; +export const mockLogger: Logger = loggingServiceMock.createLogger(); diff --git a/x-pack/legacy/plugins/snapshot_restore/index.ts b/x-pack/legacy/plugins/snapshot_restore/index.ts index 0cc1043e25557b..19b67b41be2a68 100644 --- a/x-pack/legacy/plugins/snapshot_restore/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/index.ts @@ -7,8 +7,8 @@ import { Legacy } from 'kibana'; import { resolve } from 'path'; import { PLUGIN } from './common/constants'; -import { Plugin as SnapshotRestorePlugin } from './plugin'; -import { createShim } from './shim'; +import { Plugin as SnapshotRestorePlugin } from './server/plugin'; +import { createShim } from './server/shim'; export function snapshotRestore(kibana: any) { return new kibana.Plugin({ diff --git a/x-pack/legacy/plugins/snapshot_restore/plugin.ts b/x-pack/legacy/plugins/snapshot_restore/server/plugin.ts similarity index 79% rename from x-pack/legacy/plugins/snapshot_restore/plugin.ts rename to x-pack/legacy/plugins/snapshot_restore/server/plugin.ts index 35ef05f91be8e5..f9264ee1f25077 100644 --- a/x-pack/legacy/plugins/snapshot_restore/plugin.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/plugin.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { API_BASE_PATH } from './common/constants'; -import { registerRoutes } from './server/routes/api/register_routes'; +import { API_BASE_PATH } from '../common/constants'; +import { registerRoutes } from './routes/api/register_routes'; import { Core, Plugins } from './shim'; export class Plugin { diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts index 6c7ad0ae303875..9961801ecc6c78 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts @@ -13,7 +13,7 @@ import { // NOTE: now we import it from our "public" folder, but when the Authorisation lib // will move to the "es_ui_shared" plugin, it will be imported from its "static" folder import { Privileges } from '../../../public/app/lib/authorization'; -import { Plugins } from '../../../shim'; +import { Plugins } from '../../shim'; let xpackMainPlugin: any; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts index 38f9a2301af5a3..bbfc82b8a6de9a 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts @@ -10,7 +10,7 @@ import { } from '../../../../../server/lib/create_router/error_wrappers'; import { SlmPolicyEs, SlmPolicy, SlmPolicyPayload } from '../../../common/types'; import { deserializePolicy, serializePolicy } from '../../../common/lib'; -import { Plugins } from '../../../shim'; +import { Plugins } from '../../shim'; import { getManagedPolicyNames } from '../../lib'; let callWithInternalUser: any; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/register_routes.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/register_routes.ts index 11a6cad86640e4..713df194044d33 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/register_routes.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/register_routes.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Router } from '../../../../../server/lib/create_router'; -import { Plugins } from '../../../shim'; +import { Plugins } from '../../shim'; import { registerAppRoutes } from './app'; import { registerRepositoriesRoutes } from './repositories'; import { registerSnapshotsRoutes } from './snapshots'; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts index 13f44d2a1aeeb7..f6ac946ab07d5b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -17,7 +17,7 @@ import { SlmPolicyEs, } from '../../../common/types'; -import { Plugins } from '../../../shim'; +import { Plugins } from '../../shim'; import { deserializeRepositorySettings, serializeRepositorySettings, diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts index eed47b7343ec5b..042a2dfeaf6b53 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -10,7 +10,7 @@ import { } from '../../../../../server/lib/create_router/error_wrappers'; import { SnapshotDetails, SnapshotDetailsEs } from '../../../common/types'; import { deserializeSnapshotDetails } from '../../../common/lib'; -import { Plugins } from '../../../shim'; +import { Plugins } from '../../shim'; import { getManagedRepositoryName } from '../../lib'; let callWithInternalUser: any; diff --git a/x-pack/legacy/plugins/snapshot_restore/shim.ts b/x-pack/legacy/plugins/snapshot_restore/server/shim.ts similarity index 83% rename from x-pack/legacy/plugins/snapshot_restore/shim.ts rename to x-pack/legacy/plugins/snapshot_restore/server/shim.ts index ef8d65fca77d46..84c9ddf8e0bea6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/shim.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/shim.ts @@ -6,10 +6,10 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; -import { createRouter, Router } from '../../server/lib/create_router'; -import { registerLicenseChecker } from '../../server/lib/register_license_checker'; -import { elasticsearchJsPlugin } from './server/client/elasticsearch_slm'; -import { CloudSetup } from '../../../plugins/cloud/server'; +import { createRouter, Router } from '../../../server/lib/create_router'; +import { registerLicenseChecker } from '../../../server/lib/register_license_checker'; +import { elasticsearchJsPlugin } from './client/elasticsearch_slm'; +import { CloudSetup } from '../../../../plugins/cloud/server'; export interface Core { http: { createRouter(basePath: string): Router; diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 6ea06f47b90120..0083847cfb441b 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -8,7 +8,7 @@ import { resolve } from 'path'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import { Legacy } from 'kibana'; import { KibanaRequest } from '../../../../src/core/server'; -import { SpacesServiceSetup } from '../../../plugins/spaces/server/spaces_service/spaces_service'; +import { SpacesServiceSetup } from '../../../plugins/spaces/server'; import { SpacesPluginSetup } from '../../../plugins/spaces/server'; // @ts-ignore import { AuditLogger } from '../../server/lib/audit_logger'; @@ -48,7 +48,6 @@ export const spaces = (kibana: Record) => }, uiExports: { - chromeNavControls: ['plugins/spaces/views/nav_control'], styleSheetPaths: resolve(__dirname, 'public/index.scss'), managementSections: ['plugins/spaces/views/management'], apps: [ @@ -60,7 +59,7 @@ export const spaces = (kibana: Record) => hidden: true, }, ], - hacks: [], + hacks: ['plugins/spaces/legacy'], mappings, migrations: { space: { @@ -73,19 +72,21 @@ export const spaces = (kibana: Record) => hidden: true, }, }, - home: ['plugins/spaces/register_feature'], - injectDefaultVars(server: any) { + home: [], + injectDefaultVars(server: Server) { return { - spaces: [], - activeSpace: null, serverBasePath: server.config().get('server.basePath'), + activeSpace: null, }; }, async replaceInjectedVars( vars: Record, request: Legacy.Request, - server: Record + server: Server ) { + // NOTICE: use of `activeSpace` is deprecated and will not be made available in the New Platform. + // Known usages: + // - x-pack/legacy/plugins/infra/public/utils/use_kibana_space_id.ts const spacesPlugin = server.newPlatform.setup.plugins.spaces as SpacesPluginSetup; if (!spacesPlugin) { throw new Error('New Platform XPack Spaces plugin is not available.'); diff --git a/x-pack/legacy/plugins/spaces/public/__mocks__/ui_capabilities.ts b/x-pack/legacy/plugins/spaces/public/__mocks__/ui_capabilities.ts deleted file mode 100644 index 532f83b27a3698..00000000000000 --- a/x-pack/legacy/plugins/spaces/public/__mocks__/ui_capabilities.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -jest.mock('ui/capabilities', () => ({ - capabilities: { - get: jest.fn().mockReturnValue({ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { - manage: true, - }, - }), - }, -})); - -import { capabilities, UICapabilities } from 'ui/capabilities'; - -export function setMockCapabilities(mockCapabilities: UICapabilities) { - ((capabilities.get as unknown) as jest.Mock).mockReturnValue(mockCapabilities); -} diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx index 00e306fe3d691a..2dc6ae919c0187 100644 --- a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx @@ -4,37 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setMockCapabilities } from '../__mocks__/ui_capabilities'; import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { ManageSpacesButton } from './manage_spaces_button'; describe('ManageSpacesButton', () => { it('renders as expected', () => { - setMockCapabilities({ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { - manage: true, - }, - }); - - const component = ; + const component = ( + + ); expect(shallowWithIntl(component)).toMatchSnapshot(); }); it(`doesn't render if user profile forbids managing spaces`, () => { - setMockCapabilities({ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { - manage: false, - }, - }); - - const component = ; + const component = ( + + ); expect(shallowWithIntl(component)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx index ef7dba02f8fad1..91a0803c20bc9e 100644 --- a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx @@ -7,8 +7,8 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, CSSProperties } from 'react'; -import { capabilities } from 'ui/capabilities'; -import { MANAGE_SPACES_URL } from '../lib/constants'; +import { Capabilities } from 'src/core/public'; +import { getManageSpacesUrl } from '../lib/constants'; interface Props { isDisabled?: boolean; @@ -16,11 +16,12 @@ interface Props { size?: 's' | 'm'; style?: CSSProperties; onClick?: () => void; + capabilities: Capabilities; } export class ManageSpacesButton extends Component { public render() { - if (!capabilities.get().spaces.manage) { + if (!this.props.capabilities.spaces.manage) { return null; } @@ -44,6 +45,6 @@ export class ManageSpacesButton extends Component { if (this.props.onClick) { this.props.onClick(); } - window.location.replace(MANAGE_SPACES_URL); + window.location.replace(getManageSpacesUrl()); }; } diff --git a/x-pack/legacy/plugins/spaces/public/register_feature.ts b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts similarity index 81% rename from x-pack/legacy/plugins/spaces/public/register_feature.ts rename to x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts index c8ebfd8db5686f..1f41bb89d77076 100644 --- a/x-pack/legacy/plugins/spaces/public/register_feature.ts +++ b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -6,13 +6,12 @@ import { i18n } from '@kbn/i18n'; import { + FeatureCatalogueEntry, FeatureCatalogueCategory, - FeatureCatalogueRegistryProvider, - // @ts-ignore -} from 'ui/registry/feature_catalogue'; +} from '../../../../../src/plugins/home/public'; import { getSpacesFeatureDescription } from './lib/constants'; -FeatureCatalogueRegistryProvider.register(() => { +export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { return { id: 'spaces', title: i18n.translate('xpack.spaces.spacesTitle', { @@ -24,4 +23,4 @@ FeatureCatalogueRegistryProvider.register(() => { showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }; -}); +}; diff --git a/x-pack/legacy/plugins/spaces/public/index.ts b/x-pack/legacy/plugins/spaces/public/index.ts new file mode 100644 index 00000000000000..9233aae9fb12fb --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { SpacesPlugin } from './plugin'; + +export const plugin = () => { + return new SpacesPlugin(); +}; diff --git a/x-pack/legacy/plugins/spaces/public/legacy.ts b/x-pack/legacy/plugins/spaces/public/legacy.ts new file mode 100644 index 00000000000000..99419206093e93 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/legacy.ts @@ -0,0 +1,18 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +import { plugin } from '.'; +import { SpacesPlugin, PluginsSetup } from './plugin'; + +const spacesPlugin: SpacesPlugin = plugin(); + +const plugins: PluginsSetup = { + home: npSetup.plugins.home, +}; + +export const setup = spacesPlugin.setup(npSetup.core, plugins); +export const start = spacesPlugin.start(npStart.core); diff --git a/x-pack/legacy/plugins/spaces/public/lib/constants.ts b/x-pack/legacy/plugins/spaces/public/lib/constants.ts index 7752ce1b6bc78f..94799f6f2b5d8e 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/constants.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/constants.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; +import { npSetup } from 'ui/new_platform'; let spacesFeatureDescription: string; @@ -20,4 +20,5 @@ export const getSpacesFeatureDescription = () => { return spacesFeatureDescription; }; -export const MANAGE_SPACES_URL = chrome.addBasePath(`/app/kibana#/management/spaces/list`); +export const getManageSpacesUrl = () => + npSetup.core.http.basePath.prepend(`/app/kibana#/management/spaces/list`); diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index e0db5f360f0f6d..3b0fffa38e7856 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -11,7 +11,6 @@ import { SavedObjectsManagementRecord, } from '../../../../../../../src/legacy/core_plugins/management/public'; import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; -import { Space } from '../../../common/model/space'; import { SpacesManager } from '../spaces_manager'; export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagementAction { @@ -31,7 +30,7 @@ export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagem }, }; - constructor(private readonly spacesManager: SpacesManager, private readonly activeSpace: Space) { + constructor(private readonly spacesManager: SpacesManager) { super(); } @@ -44,7 +43,6 @@ export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagem onClose={this.onClose} savedObject={this.record} spacesManager={this.spacesManager} - activeSpace={this.activeSpace} toastNotifications={toastNotifications} /> ); diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts index d2576ca5c6c16b..9fcc5a89736cc3 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsImportRetry, SavedObjectsImportResponse } from 'src/core/server'; +import { SavedObjectsImportRetry, SavedObjectsImportResponse } from 'src/core/public'; export interface CopyOptions { includeRelated: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts index 4d7a9251228e8d..69c6f7a452fdd0 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts @@ -4,18 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { of, Observable } from 'rxjs'; +import { Space } from '../../common/model/space'; + function createSpacesManagerMock() { return { + onActiveSpaceChange$: (of(undefined) as unknown) as Observable, getSpaces: jest.fn().mockResolvedValue([]), getSpace: jest.fn().mockResolvedValue(undefined), + getActiveSpace: jest.fn().mockResolvedValue(undefined), createSpace: jest.fn().mockResolvedValue(undefined), updateSpace: jest.fn().mockResolvedValue(undefined), deleteSpace: jest.fn().mockResolvedValue(undefined), copySavedObjects: jest.fn().mockResolvedValue(undefined), resolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(undefined), redirectToSpaceSelector: jest.fn().mockResolvedValue(undefined), - requestRefresh: jest.fn(), - on: jest.fn(), }; } diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts index 67d34960ed98e3..4fff1ddba08b2b 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EventEmitter } from 'events'; -import { kfetch } from 'ui/kfetch'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { skipWhile } from 'rxjs/operators'; +import { HttpSetup } from 'src/core/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; @@ -12,43 +13,57 @@ import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/t import { ENTER_SPACE_PATH } from '../../common/constants'; import { addSpaceIdToPath } from '../../../../../plugins/spaces/common'; -export class SpacesManager extends EventEmitter { - constructor(private readonly serverBasePath: string) { - super(); +export class SpacesManager { + private activeSpace$: BehaviorSubject = new BehaviorSubject(null); + + public readonly onActiveSpaceChange$: Observable; + + constructor(private readonly serverBasePath: string, private readonly http: HttpSetup) { + this.onActiveSpaceChange$ = this.activeSpace$ + .asObservable() + .pipe(skipWhile((v: Space | null) => v == null)) as Observable; + + this.refreshActiveSpace(); } public async getSpaces(purpose?: GetSpacePurpose): Promise { - return await kfetch({ pathname: '/api/spaces/space', query: { purpose } }); + return await this.http.get('/api/spaces/space', { query: { purpose } }); } public async getSpace(id: string): Promise { - return await kfetch({ pathname: `/api/spaces/space/${encodeURIComponent(id)}` }); + return await this.http.get(`/api/spaces/space/${encodeURIComponent(id)}`); + } + + public getActiveSpace({ forceRefresh = false } = {}) { + if (!forceRefresh && this.activeSpace$.value) { + return Promise.resolve(this.activeSpace$.value); + } + return this.http.get('/internal/spaces/_active_space') as Promise; } public async createSpace(space: Space) { - return await kfetch({ - pathname: `/api/spaces/space`, - method: 'POST', + await this.http.post(`/api/spaces/space`, { body: JSON.stringify(space), }); } public async updateSpace(space: Space) { - return await kfetch({ - pathname: `/api/spaces/space/${encodeURIComponent(space.id)}`, + await this.http.put(`/api/spaces/space/${encodeURIComponent(space.id)}`, { query: { overwrite: true, }, - method: 'PUT', body: JSON.stringify(space), }); + + const activeSpaceId = (await this.getActiveSpace()).id; + + if (space.id === activeSpaceId) { + this.refreshActiveSpace(); + } } public async deleteSpace(space: Space) { - return await kfetch({ - pathname: `/api/spaces/space/${encodeURIComponent(space.id)}`, - method: 'DELETE', - }); + await this.http.delete(`/api/spaces/space/${encodeURIComponent(space.id)}`); } public async copySavedObjects( @@ -57,9 +72,7 @@ export class SpacesManager extends EventEmitter { includeReferences: boolean, overwrite: boolean ): Promise { - return await kfetch({ - pathname: `/api/spaces/_copy_saved_objects`, - method: 'POST', + return this.http.post('/api/spaces/_copy_saved_objects', { body: JSON.stringify({ objects, spaces, @@ -74,9 +87,7 @@ export class SpacesManager extends EventEmitter { retries: unknown, includeReferences: boolean ): Promise { - return await kfetch({ - pathname: `/api/spaces/_resolve_copy_saved_objects_errors`, - method: 'POST', + return this.http.post(`/api/spaces/_resolve_copy_saved_objects_errors`, { body: JSON.stringify({ objects, includeReferences, @@ -93,7 +104,8 @@ export class SpacesManager extends EventEmitter { window.location.href = `${this.serverBasePath}/spaces/space_selector`; } - public async requestRefresh() { - this.emit('request_refresh'); + private async refreshActiveSpace() { + const activeSpace = await this.getActiveSpace({ forceRefresh: true }); + this.activeSpace$.next(activeSpace); } } diff --git a/x-pack/legacy/plugins/spaces/public/plugin.tsx b/x-pack/legacy/plugins/spaces/public/plugin.tsx new file mode 100644 index 00000000000000..4e070c3cee3df5 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/plugin.tsx @@ -0,0 +1,40 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { HomePublicPluginSetup } from 'src/plugins/home/public'; +import { SpacesManager } from './lib'; +import { initSpacesNavControl } from './views/nav_control'; +import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; + +export interface SpacesPluginStart { + spacesManager: SpacesManager | null; +} + +export interface PluginsSetup { + home?: HomePublicPluginSetup; +} + +export class SpacesPlugin implements Plugin { + private spacesManager: SpacesManager | null = null; + + public async start(core: CoreStart) { + const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; + + this.spacesManager = new SpacesManager(serverBasePath, core.http); + initSpacesNavControl(this.spacesManager, core); + + return { + spacesManager: this.spacesManager, + }; + } + + public async setup(core: CoreSetup, plugins: PluginsSetup) { + if (plugins.home) { + plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + } + } +} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap index 82f94f2346ae7a..db9913ea7f0724 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap @@ -65,28 +65,6 @@ exports[`ConfirmDeleteModal renders as expected 1`] = ` value="" /> - - - - - ( - - My Space - - ) - , - } - } - /> - - diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/__snapshots__/advanced_settings_subtitle.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/__snapshots__/advanced_settings_subtitle.test.tsx.snap deleted file mode 100644 index 6c29a309c36b0b..00000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/__snapshots__/advanced_settings_subtitle.test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AdvancedSettingsSubtitle renders as expected 1`] = ` - - - - - My Space - , - } - } - /> -

- } - /> -
-`; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx index 43804b9ba44fcb..49f5233db44e2b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx @@ -4,16 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsSubtitle } from './advanced_settings_subtitle'; +import { EuiCallOut } from '@elastic/eui'; describe('AdvancedSettingsSubtitle', () => { - it('renders as expected', () => { + it('renders as expected', async () => { const space = { id: 'my-space', name: 'My Space', disabledFeatures: [], }; - expect(shallowWithIntl()).toMatchSnapshot(); + + const wrapper = mountWithIntl( + Promise.resolve(space)} /> + ); + + // Wait for active space to resolve before requesting the component to update + await Promise.resolve(); + await Promise.resolve(); + + wrapper.update(); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx index 901bce019012f3..433f8a8ccf0a20 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx @@ -6,30 +6,40 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { Fragment } from 'react'; +import React, { Fragment, useState, useEffect } from 'react'; import { Space } from '../../../../../common/model/space'; interface Props { - space: Space; + getActiveSpace: () => Promise; } -export const AdvancedSettingsSubtitle = (props: Props) => ( - - - - {props.space.name}, - }} - /> -

- } - /> -
-); +export const AdvancedSettingsSubtitle = (props: Props) => { + const [activeSpace, setActiveSpace] = useState(null); + + useEffect(() => { + props.getActiveSpace().then(space => setActiveSpace(space)); + }, [props]); + + if (!activeSpace) return null; + + return ( + + + + {activeSpace.name}, + }} + /> +

+ } + /> +
+ ); +}; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/__snapshots__/advanced_settings_title.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/__snapshots__/advanced_settings_title.test.tsx.snap deleted file mode 100644 index eba4a06a8e8aad..00000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/__snapshots__/advanced_settings_title.test.tsx.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AdvancedSettingsTitle renders as expected 1`] = ` - - - - - - -

- -

-
-
-
-`; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx index 3a91bcb6019ee8..7f2b6eee62c45e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx @@ -4,16 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsTitle } from './advanced_settings_title'; +import { SpaceAvatar } from '../../../../components'; describe('AdvancedSettingsTitle', () => { - it('renders as expected', () => { + it('renders without crashing', async () => { const space = { id: 'my-space', name: 'My Space', disabledFeatures: [], }; - expect(shallowWithIntl()).toMatchSnapshot(); + + const wrapper = mountWithIntl( + Promise.resolve(space)} /> + ); + + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); + expect(wrapper.find(SpaceAvatar)).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx index 9ba38a12f436ac..af6fa42cce07be 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx @@ -6,28 +6,38 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Space } from '../../../../../common/model/space'; import { SpaceAvatar } from '../../../../components'; interface Props { - space: Space; + getActiveSpace: () => Promise; } -export const AdvancedSettingsTitle = (props: Props) => ( - - - - - - -

- -

-
-
-
-); +export const AdvancedSettingsTitle = (props: Props) => { + const [activeSpace, setActiveSpace] = useState(null); + + useEffect(() => { + props.getActiveSpace().then(space => setActiveSpace(space)); + }, [props]); + + if (!activeSpace) return null; + + return ( + + + + + + +

+ +

+
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx index 3c3fa502a917d3..f0ab2c99ac2e20 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SpacesNavState } from '../../nav_control'; import { ConfirmDeleteModal } from './confirm_delete_modal'; import { spacesManagerMock } from '../../../lib/mocks'; import { SpacesManager } from '../../../lib'; @@ -20,11 +19,7 @@ describe('ConfirmDeleteModal', () => { }; const spacesManager = spacesManagerMock.create(); - - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; + spacesManager.getActiveSpace.mockResolvedValue(space); const onCancel = jest.fn(); const onConfirm = jest.fn(); @@ -34,7 +29,6 @@ describe('ConfirmDeleteModal', () => { { }; const spacesManager = spacesManagerMock.create(); - - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; + spacesManager.getActiveSpace.mockResolvedValue(space); const onCancel = jest.fn(); const onConfirm = jest.fn(); @@ -64,7 +54,6 @@ describe('ConfirmDeleteModal', () => { void; onConfirm: () => void; intl: InjectedIntl; @@ -42,6 +40,7 @@ interface State { confirmSpaceName: string; error: boolean | null; deleteInProgress: boolean; + isDeletingCurrentSpace: boolean; } class ConfirmDeleteModalUI extends Component { @@ -49,13 +48,23 @@ class ConfirmDeleteModalUI extends Component { confirmSpaceName: '', error: null, deleteInProgress: false, + isDeletingCurrentSpace: false, }; + public componentDidMount() { + isCurrentSpace(this.props.space, this.props.spacesManager).then(result => { + this.setState({ + isDeletingCurrentSpace: result, + }); + }); + } + public render() { - const { space, spacesNavState, onCancel, intl } = this.props; + const { space, onCancel, intl } = this.props; + const { isDeletingCurrentSpace } = this.state; let warning = null; - if (isDeletingCurrentSpace(space, spacesNavState)) { + if (isDeletingCurrentSpace) { const name = ( ({space.name}) @@ -186,7 +195,7 @@ class ConfirmDeleteModalUI extends Component { private onConfirm = async () => { if (this.state.confirmSpaceName === this.props.space.name) { - const needsRedirect = isDeletingCurrentSpace(this.props.space, this.props.spacesNavState); + const needsRedirect = this.state.isDeletingCurrentSpace; const spacesManager = this.props.spacesManager; this.setState({ @@ -210,8 +219,8 @@ class ConfirmDeleteModalUI extends Component { }; } -function isDeletingCurrentSpace(space: Space, spacesNavState: SpacesNavState) { - return space.id === spacesNavState.getActiveSpace().id; +async function isCurrentSpace(space: Space, spacesManager: SpacesManager) { + return space.id === (await spacesManager.getActiveSpace()).id; } export const ConfirmDeleteModal = injectI18n(ConfirmDeleteModalUI); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx index c30792b23e3ac8..590c0edc0073ba 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx @@ -33,6 +33,13 @@ const setup = async (opts: SetupOpts = {}) => { const onClose = jest.fn(); const mockSpacesManager = spacesManagerMock.create(); + + mockSpacesManager.getActiveSpace.mockResolvedValue({ + id: 'my-active-space', + name: 'my active space', + disabledFeatures: [], + }); + mockSpacesManager.getSpaces.mockResolvedValue( opts.mockSpaces || [ { @@ -79,11 +86,6 @@ const setup = async (opts: SetupOpts = {}) => { @@ -92,6 +94,7 @@ const setup = async (opts: SetupOpts = {}) => { if (!opts.returnBeforeSpacesLoad) { // Wait for spaces manager to complete and flyout to rerender await Promise.resolve(); + await Promise.resolve(); wrapper.update(); } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx index 1de5a10977f838..5a43e5878ab83f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx @@ -38,7 +38,6 @@ interface Props { onClose: () => void; savedObject: SavedObjectsManagementRecord; spacesManager: SpacesManager; - activeSpace: Space; toastNotifications: ToastNotifications; } @@ -57,12 +56,13 @@ export const CopySavedObjectsToSpaceFlyout = (props: Props) => { } ); useEffect(() => { - spacesManager - .getSpaces('copySavedObjectsIntoSpace') - .then(response => { + const getSpaces = spacesManager.getSpaces('copySavedObjectsIntoSpace'); + const getActiveSpace = spacesManager.getActiveSpace(); + Promise.all([getSpaces, getActiveSpace]) + .then(([allSpaces, activeSpace]) => { setSpacesState({ isLoading: false, - spaces: response, + spaces: allSpaces.filter(space => space.id !== activeSpace.id), }); }) .catch(e => { @@ -73,7 +73,6 @@ export const CopySavedObjectsToSpaceFlyout = (props: Props) => { }); }); }, [spacesManager, toastNotifications]); - const eligibleSpaces = spaces.filter(space => space.id !== props.activeSpace.id); const [copyInProgress, setCopyInProgress] = useState(false); const [conflictResolutionInProgress, setConflictResolutionInProgress] = useState(false); @@ -159,7 +158,7 @@ export const CopySavedObjectsToSpaceFlyout = (props: Props) => { } // Step 1a: assets loaded, but no spaces are available for copy. - if (eligibleSpaces.length === 0) { + if (spaces.length === 0) { return ( { // Step 2: Copy has not been initiated yet; User must fill out form to continue. if (!copyInProgress) { return ( - + ); } @@ -200,7 +195,7 @@ export const CopySavedObjectsToSpaceFlyout = (props: Props) => { copyInProgress={copyInProgress} conflictResolutionInProgress={conflictResolutionInProgress} copyResult={copyResult} - spaces={eligibleSpaces} + spaces={spaces} copyOptions={copyOptions} retries={retries} onRetriesChange={onRetriesChange} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx index 24296bf0fa7633..e7c7dfc5eb1b05 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SpacesNavState } from '../../nav_control'; import { DeleteSpacesButton } from './delete_spaces_button'; import { spacesManagerMock } from '../../../lib/mocks'; import { SpacesManager } from '../../../lib'; @@ -21,16 +20,10 @@ describe('DeleteSpacesButton', () => { it('renders as expected', () => { const spacesManager = spacesManagerMock.create(); - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; - const wrapper = shallowWithIntl( diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx index 7f3dd0aea485ec..216dd7c41f124b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React, { Component, Fragment } from 'react'; // @ts-ignore import { toastNotifications } from 'ui/notify'; @@ -18,7 +17,6 @@ interface Props { style?: 'button' | 'icon'; space: Space; spacesManager: SpacesManager; - spacesNavState: SpacesNavState; onDelete: () => void; intl: InjectedIntl; } @@ -81,12 +79,11 @@ class DeleteSpacesButtonUI extends Component { return null; } - const { spacesNavState, spacesManager } = this.props; + const { spacesManager } = this.props; return ( { this.setState({ @@ -99,7 +96,7 @@ class DeleteSpacesButtonUI extends Component { }; public deleteSpaces = async () => { - const { spacesManager, space, spacesNavState, intl } = this.props; + const { spacesManager, space, intl } = this.props; try { await spacesManager.deleteSpace(space); @@ -139,8 +136,6 @@ class DeleteSpacesButtonUI extends Component { if (this.props.onDelete) { this.props.onDelete(); } - - spacesNavState.refreshSpacesList(); }; } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx index 4485491f5cd892..8f82e6d413350f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx @@ -7,7 +7,7 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../../plugins/features/public'; import { Space } from '../../../../../common/model/space'; import { SectionPanel } from '../section_panel'; import { EnabledFeatures } from './enabled_features'; @@ -33,7 +33,7 @@ const space: Space = { disabledFeatures: ['feature-1', 'feature-2'], }; -const uiCapabilities = { +const capabilities = { navLinks: {}, management: {}, catalogue: {}, @@ -49,7 +49,7 @@ describe('EnabledFeatures', () => { @@ -64,7 +64,7 @@ describe('EnabledFeatures', () => { @@ -99,7 +99,7 @@ describe('EnabledFeatures', () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx index 1c1925a6a4ee0b..628be759b7c5ce 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx @@ -7,8 +7,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../../../../../../../plugins/features/server'; +import { Capabilities } from 'src/core/public'; +import { Feature } from '../../../../../../../../plugins/features/public'; import { Space } from '../../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; @@ -17,7 +17,7 @@ import { FeatureTable } from './feature_table'; interface Props { space: Partial; features: Feature[]; - uiCapabilities: UICapabilities; + capabilities: Capabilities; intl: InjectedIntl; onChange: (space: Partial) => void; } @@ -130,7 +130,7 @@ export class EnabledFeatures extends Component { defaultMessage="The feature is hidden in the UI, but is not disabled." />

- {this.props.uiCapabilities.spaces.manage && ( + {this.props.capabilities.spaces.manage && (

({ kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1' }]), })); -import '../../../__mocks__/ui_capabilities'; import '../../../__mocks__/xpack_info'; import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { SpacesNavState } from '../../nav_control'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { ManageSpacePage } from './manage_space_page'; import { SectionPanel } from './section_panel'; @@ -29,17 +27,18 @@ describe('ManageSpacePage', () => { it('allows a space to be created', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); - - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); const wrapper = mountWithIntl( ); @@ -75,17 +74,19 @@ describe('ManageSpacePage', () => { initials: 'AB', disabledFeatures: [], }); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; const wrapper = mountWithIntl( ); @@ -121,17 +122,19 @@ describe('ManageSpacePage', () => { initials: 'AB', disabledFeatures: [], }); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; const wrapper = mountWithIntl( ); @@ -176,17 +179,19 @@ describe('ManageSpacePage', () => { initials: 'AB', disabledFeatures: [], }); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); - const spacesNavState: SpacesNavState = { - getActiveSpace: () => space, - refreshSpacesList: jest.fn(), - }; const wrapper = mountWithIntl( ); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx index 7a3fea0d76a3b9..a5d60d1a731ba2 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx @@ -16,13 +16,12 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import _ from 'lodash'; -import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React, { Component, Fragment } from 'react'; -import { capabilities } from 'ui/capabilities'; import { Breadcrumb } from 'ui/chrome'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; -import { Feature } from '../../../../../../../plugins/features/server'; +import { Capabilities } from 'src/core/public'; +import { Feature } from '../../../../../../../plugins/features/public'; import { isReservedSpace } from '../../../../common'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib'; @@ -39,9 +38,9 @@ import { ReservedSpaceBadge } from './reserved_space_badge'; interface Props { spacesManager: SpacesManager; spaceId?: string; - spacesNavState: SpacesNavState; intl: InjectedIntl; setBreadcrumbs?: (breadcrumbs: Breadcrumb[]) => void; + capabilities: Capabilities; } interface State { @@ -73,7 +72,7 @@ class ManageSpacePageUI extends Component { } public async componentDidMount() { - if (!capabilities.get().spaces.manage) { + if (!this.props.capabilities.spaces.manage) { return; } @@ -139,7 +138,7 @@ class ManageSpacePageUI extends Component { ); public getForm = () => { - if (!capabilities.get().spaces.manage) { + if (!this.props.capabilities.spaces.manage) { return ; } @@ -173,7 +172,7 @@ class ManageSpacePageUI extends Component { @@ -269,7 +268,6 @@ class ManageSpacePageUI extends Component { data-test-subj="delete-space-button" space={this.state.space as Space} spacesManager={this.props.spacesManager} - spacesNavState={this.props.spacesNavState} onDelete={this.backToSpacesList} /> @@ -298,27 +296,30 @@ class ManageSpacePageUI extends Component { } if (this.editingExistingSpace()) { - const { spacesNavState } = this.props; + const { spacesManager } = this.props; const originalSpace: Space = this.state.originalSpace as Space; const space: Space = this.state.space as Space; - const editingActiveSpace = spacesNavState.getActiveSpace().id === originalSpace.id; + spacesManager.getActiveSpace().then(activeSpace => { + const editingActiveSpace = activeSpace.id === originalSpace.id; - const haveDisabledFeaturesChanged = - space.disabledFeatures.length !== originalSpace.disabledFeatures.length || - _.difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0; + const haveDisabledFeaturesChanged = + space.disabledFeatures.length !== originalSpace.disabledFeatures.length || + _.difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0; - if (editingActiveSpace && haveDisabledFeaturesChanged) { - this.setState({ - showAlteringActiveSpaceDialog: true, - }); + if (editingActiveSpace && haveDisabledFeaturesChanged) { + this.setState({ + showAlteringActiveSpaceDialog: true, + }); - return; - } + return; + } + this.performSave(); + }); + } else { + this.performSave(); } - - this.performSave(); }; private performSave = (requireRefresh = false) => { @@ -358,7 +359,6 @@ class ManageSpacePageUI extends Component { action .then(() => { - this.props.spacesNavState.refreshSpacesList(); toastNotifications.addSuccess( intl.formatMessage( { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx index f659154c910f19..bf33273c614d65 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx @@ -15,16 +15,16 @@ import { // @ts-ignore import routes from 'ui/routes'; import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; -import { SpacesManager } from '../../lib'; import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; import { AdvancedSettingsTitle } from './components/advanced_settings_title'; +import { start as spacesNPStart } from '../../legacy'; import { CopyToSpaceSavedObjectsManagementAction } from '../../lib/copy_saved_objects_to_space'; const MANAGE_SPACES_KEY = 'spaces'; routes.defaults(/\/management/, { resolve: { - spacesManagementSection(activeSpace: any, serverBasePath: string) { + spacesManagementSection() { function getKibanaSection() { return management.getSection('kibana'); } @@ -48,21 +48,24 @@ routes.defaults(/\/management/, { } // Customize Saved Objects Management - const action = new CopyToSpaceSavedObjectsManagementAction( - new SpacesManager(serverBasePath), - activeSpace.space - ); - // This route resolve function executes any time the management screen is loaded, and we want to ensure - // that this action is only registered once. - if (!managementSetup.savedObjects.registry.has(action.id)) { - managementSetup.savedObjects.registry.register(action); - } + spacesNPStart.then(({ spacesManager }) => { + const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager!); + // This route resolve function executes any time the management screen is loaded, and we want to ensure + // that this action is only registered once. + if (!managementSetup.savedObjects.registry.has(action.id)) { + managementSetup.savedObjects.registry.register(action); + } + }); + + const getActiveSpace = async () => { + const { spacesManager } = await spacesNPStart; + return spacesManager!.getActiveSpace(); + }; - // Customize Advanced Settings - const PageTitle = () => ; + const PageTitle = () => ; registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); - const SubTitle = () => ; + const SubTitle = () => ; registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts index 3420a4ccd7278d..8621ec5614368c 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts +++ b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../plugins/features/public'; import { getEnabledFeatures } from './feature_utils'; const buildFeatures = () => diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts index 0ff428c7117841..ef46a539677442 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts +++ b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../../plugins/features/server'; +import { Feature } from '../../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx b/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx index 1f0afc706c3f0e..d8fd0298df2fc3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx @@ -5,31 +5,36 @@ */ // @ts-ignore import template from 'plugins/spaces/views/management/template.html'; -import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; // @ts-ignore import routes from 'ui/routes'; -import { SpacesManager } from '../../lib/spaces_manager'; +import { npStart } from 'ui/new_platform'; import { ManageSpacePage } from './edit_space'; import { getCreateBreadcrumbs, getEditBreadcrumbs, getListBreadcrumbs } from './lib'; import { SpacesGridPage } from './spaces_grid'; + +import { start as spacesNPStart } from '../../legacy'; + const reactRootNodeId = 'manageSpacesReactRoot'; routes.when('/management/spaces/list', { template, k7Breadcrumbs: getListBreadcrumbs, requireUICapability: 'management.kibana.spaces', - controller($scope: any, spacesNavState: SpacesNavState, serverBasePath: string) { + controller($scope: any) { $scope.$$postDigest(async () => { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager(serverBasePath); + const { spacesManager } = await spacesNPStart; render( - + , domNode ); @@ -48,15 +53,18 @@ routes.when('/management/spaces/create', { template, k7Breadcrumbs: getCreateBreadcrumbs, requireUICapability: 'management.kibana.spaces', - controller($scope: any, spacesNavState: SpacesNavState, serverBasePath: string) { + controller($scope: any) { $scope.$$postDigest(async () => { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager(serverBasePath); + const { spacesManager } = await spacesNPStart; render( - + , domNode ); @@ -79,29 +87,21 @@ routes.when('/management/spaces/edit/:spaceId', { template, k7Breadcrumbs: () => getEditBreadcrumbs(), requireUICapability: 'management.kibana.spaces', - controller( - $scope: any, - $route: any, - chrome: any, - spacesNavState: SpacesNavState, - serverBasePath: string - ) { + controller($scope: any, $route: any) { $scope.$$postDigest(async () => { const domNode = document.getElementById(reactRootNodeId); const { spaceId } = $route.current.params; - const spacesManager = new SpacesManager(serverBasePath); + const { spacesManager } = await spacesNPStart; render( { - chrome.breadcrumbs.set(breadcrumbs); - }} + spacesManager={spacesManager!} + setBreadcrumbs={npStart.core.chrome.setBreadcrumbs} + capabilities={npStart.core.application.capabilities} /> , domNode diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx index bd7c61a018c9f7..9fa03b1a9b74ae 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx @@ -19,18 +19,16 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { capabilities } from 'ui/capabilities'; import { kfetch } from 'ui/kfetch'; -// @ts-ignore import { toastNotifications } from 'ui/notify'; -import { Feature } from '../../../../../../../plugins/features/server'; +import { Capabilities } from 'src/core/public'; +import { Feature } from '../../../../../../../plugins/features/public'; import { isReservedSpace } from '../../../../common'; import { DEFAULT_SPACE_ID } from '../../../../common/constants'; import { Space } from '../../../../common/model/space'; import { SpaceAvatar } from '../../../components'; import { getSpacesFeatureDescription } from '../../../lib/constants'; import { SpacesManager } from '../../../lib/spaces_manager'; -import { SpacesNavState } from '../../nav_control'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; import { SecureSpaceMessage } from '../components/secure_space_message'; import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; @@ -38,8 +36,8 @@ import { getEnabledFeatures } from '../lib/feature_utils'; interface Props { spacesManager: SpacesManager; - spacesNavState: SpacesNavState; intl: InjectedIntl; + capabilities: Capabilities; } interface State { @@ -65,7 +63,7 @@ class SpacesGridPageUI extends Component { } public componentDidMount() { - if (capabilities.get().spaces.manage) { + if (this.props.capabilities.spaces.manage) { this.loadGrid(); } } @@ -83,7 +81,7 @@ class SpacesGridPageUI extends Component { public getPageContent() { const { intl } = this.props; - if (!capabilities.get().spaces.manage) { + if (!this.props.capabilities.spaces.manage) { return ; } @@ -159,12 +157,11 @@ class SpacesGridPageUI extends Component { return null; } - const { spacesNavState, spacesManager } = this.props; + const { spacesManager } = this.props; return ( { this.setState({ @@ -178,7 +175,7 @@ class SpacesGridPageUI extends Component { public deleteSpace = async () => { const { intl } = this.props; - const { spacesManager, spacesNavState } = this.props; + const { spacesManager } = this.props; const space = this.state.selectedSpace; @@ -221,8 +218,6 @@ class SpacesGridPageUI extends Component { ); toastNotifications.addSuccess(message); - - spacesNavState.refreshSpacesList(); }; public loadGrid = async () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx index 346cb8b7074fa7..4add607707b248 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx @@ -6,14 +6,12 @@ jest.mock('ui/kfetch', () => ({ kfetch: () => Promise.resolve([]), })); -import '../../../__mocks__/ui_capabilities'; import '../../../__mocks__/xpack_info'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { SpaceAvatar } from '../../../components'; import { spacesManagerMock } from '../../../lib/mocks'; import { SpacesManager } from '../../../lib'; -import { SpacesNavState } from '../../nav_control'; import { SpacesGridPage } from './spaces_grid_page'; const spaces = [ @@ -38,11 +36,6 @@ const spaces = [ }, ]; -const spacesNavState: SpacesNavState = { - getActiveSpace: () => spaces[0], - refreshSpacesList: jest.fn(), -}; - const spacesManager = spacesManagerMock.create(); spacesManager.getSpaces = jest.fn().mockResolvedValue(spaces); @@ -52,8 +45,13 @@ describe('SpacesGridPage', () => { shallowWithIntl( ) ).toMatchSnapshot(); @@ -63,8 +61,13 @@ describe('SpacesGridPage', () => { const wrapper = mountWithIntl( ); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 4e829bd481152f..5cad4e794cfda6 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -4,25 +4,18 @@ exports[`NavControlPopover renders without crashing 1`] = ` - } - linkTitle="foo" - spaceSelectorShown={false} - toggleSpaceSelector={[Function]} - /> + + + } closePopover={[Function]} data-test-subj="spacesNavSelector" @@ -36,6 +29,16 @@ exports[`NavControlPopover renders without crashing 1`] = ` withTitle={true} > diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap index 8cce2a376746af..079dab701cc1dd 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap @@ -19,6 +19,16 @@ exports[`SpacesDescription renders without crashing 1`] = ` key="manageSpacesButton" > { it('renders without crashing', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow( + + ) + ).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx index c9bc0891df8c82..043fc656a571e8 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx @@ -6,11 +6,13 @@ import { EuiContextMenuPanel, EuiText } from '@elastic/eui'; import React, { FC } from 'react'; +import { Capabilities } from 'src/core/public'; import { ManageSpacesButton } from '../../../components'; import { getSpacesFeatureDescription } from '../../../lib/constants'; interface Props { onManageSpacesClick: () => void; + capabilities: Capabilities; } export const SpacesDescription: FC = (props: Props) => { @@ -29,6 +31,7 @@ export const SpacesDescription: FC = (props: Props) => { size="s" style={{ width: `100%` }} onClick={props.onManageSpacesClick} + capabilities={props.capabilities} />

diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx deleted file mode 100644 index d5c693df58b284..00000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - // @ts-ignore - EuiHeaderSectionItemButton, -} from '@elastic/eui'; -import React from 'react'; -import { ButtonProps } from '../types'; - -export const SpacesHeaderNavButton: React.FC = props => ( - - {props.linkIcon} - -); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx index 76a47ca738627e..9a26f6802abdf5 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiContextMenuItem, EuiContextMenuPanel, EuiFieldSearch, EuiText } from '@elastic/eui'; +import { + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFieldSearch, + EuiText, + EuiLoadingContent, +} from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import React, { Component } from 'react'; +import React, { Component, ReactElement } from 'react'; +import { Capabilities } from 'src/core/public'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../common/constants'; import { Space } from '../../../../common/model/space'; import { ManageSpacesButton, SpaceAvatar } from '../../../components'; interface Props { spaces: Space[]; + isLoading: boolean; onSelectSpace: (space: Space) => void; onManageSpacesClick: () => void; intl: InjectedIntl; + capabilities: Capabilities; } interface State { @@ -30,10 +39,12 @@ class SpacesMenuUI extends Component { }; public render() { - const { intl } = this.props; + const { intl, isLoading } = this.props; const { searchTerm } = this.state; - const items = this.getVisibleSpaces(searchTerm).map(this.renderSpaceMenuItem); + const items = isLoading + ? [1, 2, 3].map(this.renderPlaceholderMenuItem) + : this.getVisibleSpaces(searchTerm).map(this.renderSpaceMenuItem); const panelProps = { className: 'spcMenu', @@ -76,7 +87,7 @@ class SpacesMenuUI extends Component { return filteredSpaces; }; - private renderSpacesListPanel = (items: JSX.Element[], searchTerm: string) => { + private renderSpacesListPanel = (items: ReactElement[], searchTerm: string) => { if (items.length === 0) { return ( @@ -151,6 +162,7 @@ class SpacesMenuUI extends Component { className="spcMenu__manageButton" size="s" onClick={this.props.onManageSpacesClick} + capabilities={this.props.capabilities} /> ); }; @@ -175,6 +187,14 @@ class SpacesMenuUI extends Component { ); }; + + private renderPlaceholderMenuItem = (key: string | number): JSX.Element => { + return ( + + + + ); + }; } export const SpacesMenu = injectI18n(SpacesMenuUI); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts b/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts index 541c79a8fd4a36..649aeee9eab9ed 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './nav_control'; - -export { SpacesNavState } from './nav_control'; +export { initSpacesNavControl } from './nav_control'; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx index bac95bbf22099e..0df077e0d2da04 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx @@ -5,69 +5,34 @@ */ import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; -// @ts-ignore -import template from 'plugins/spaces/views/nav_control/nav_control.html'; -import { NavControlPopover } from 'plugins/spaces/views/nav_control/nav_control_popover'; -// @ts-ignore -import { Path } from 'plugins/xpack_main/services/path'; import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { - chromeHeaderNavControlsRegistry, - NavControlSide, -} from 'ui/registry/chrome_header_nav_controls'; -// @ts-ignore -import { Space } from '../../../common/model/space'; -import { SpacesHeaderNavButton } from './components/spaces_header_nav_button'; - -const module = uiModules.get('spaces_nav', ['kibana']); - -export interface SpacesNavState { - getActiveSpace: () => Space; - refreshSpacesList: () => void; -} - -let spacesManager: SpacesManager; - -module.service('spacesNavState', (activeSpace: any) => { - return { - getActiveSpace: () => { - return activeSpace.space; - }, - refreshSpacesList: () => { - if (spacesManager) { - spacesManager.requestRefresh(); +import { CoreStart } from 'src/core/public'; +import { NavControlPopover } from './nav_control_popover'; + +export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreStart) { + const I18nContext = core.i18n.Context; + core.chrome.navControls.registerLeft({ + order: 1000, + mount(targetDomElement: HTMLElement) { + if (core.http.anonymousPaths.isAnonymous(window.location.pathname)) { + return () => null; } - }, - } as SpacesNavState; -}); - -chromeHeaderNavControlsRegistry.register((chrome: any, activeSpace: any) => ({ - name: 'spaces', - order: 1000, - side: NavControlSide.Left, - render(el: HTMLElement) { - if (Path.isUnauthenticated()) { - return; - } - - const serverBasePath = chrome.getInjected('serverBasePath'); - spacesManager = new SpacesManager(serverBasePath); - - ReactDOM.render( - - - , - el - ); - }, -})); + ReactDOM.render( + + + , + targetDomElement + ); + + return () => { + ReactDOM.unmountComponentAtNode(targetDomElement); + }; + }, + }); +} diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx index c0d04342a69c87..a04f28242f984a 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx @@ -4,40 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount, shallow } from 'enzyme'; +import * as Rx from 'rxjs'; +import { shallow } from 'enzyme'; import React from 'react'; import { SpaceAvatar } from '../../components'; import { spacesManagerMock } from '../../lib/mocks'; import { SpacesManager } from '../../lib'; -import { SpacesHeaderNavButton } from './components/spaces_header_nav_button'; import { NavControlPopover } from './nav_control_popover'; +import { EuiHeaderSectionItemButton } from '@elastic/eui'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; describe('NavControlPopover', () => { it('renders without crashing', () => { - const activeSpace = { - space: { id: '', name: 'foo', disabledFeatures: [] }, - valid: true, - }; - const spacesManager = spacesManagerMock.create(); const wrapper = shallow( ); expect(wrapper).toMatchSnapshot(); }); it('renders a SpaceAvatar with the active space', async () => { - const activeSpace = { - space: { id: 'foo-space', name: 'foo', disabledFeatures: [] }, - valid: true, - }; - const spacesManager = spacesManagerMock.create(); spacesManager.getSpaces = jest.fn().mockResolvedValue([ { @@ -51,23 +42,27 @@ describe('NavControlPopover', () => { disabledFeatures: [], }, ]); + spacesManager.onActiveSpaceChange$ = Rx.of({ + id: 'foo-space', + name: 'foo', + disabledFeatures: [], + }); - const wrapper = mount( + const wrapper = mountWithIntl( ); - return new Promise(resolve => { - setTimeout(() => { - expect(wrapper.state().spaces).toHaveLength(2); - wrapper.update(); - expect(wrapper.find(SpaceAvatar)).toHaveLength(1); - resolve(); - }, 20); - }); + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + // Wait for `getSpaces` promise to resolve + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); + + expect(wrapper.find(SpaceAvatar)).toHaveLength(3); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx index f0e365c27b8e74..98ce64715f3253 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx @@ -4,24 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiAvatar, EuiPopover, PopoverAnchorPosition } from '@elastic/eui'; +import { + EuiPopover, + PopoverAnchorPosition, + EuiLoadingSpinner, + EuiHeaderSectionItemButton, +} from '@elastic/eui'; import React, { Component } from 'react'; +import { Capabilities } from 'src/core/public'; +import { Subscription } from 'rxjs'; import { Space } from '../../../common/model/space'; import { SpaceAvatar } from '../../components'; import { SpacesManager } from '../../lib/spaces_manager'; import { SpacesDescription } from './components/spaces_description'; import { SpacesMenu } from './components/spaces_menu'; -import { ButtonProps } from './types'; interface Props { spacesManager: SpacesManager; - activeSpace: { - valid: boolean; - error?: string; - space: Space; - }; anchorPosition: PopoverAnchorPosition; - buttonClass: React.ComponentType; + capabilities: Capabilities; } interface State { @@ -32,23 +33,31 @@ interface State { } export class NavControlPopover extends Component { + private activeSpace$?: Subscription; + constructor(props: Props) { super(props); this.state = { showSpaceSelector: false, loading: false, - activeSpace: props.activeSpace.space, + activeSpace: null, spaces: [], }; } - public componentDidMount() { - this.loadSpaces(); + public componentWillMount() { + this.activeSpace$ = this.props.spacesManager.onActiveSpaceChange$.subscribe({ + next: activeSpace => { + this.setState({ + activeSpace, + }); + }, + }); + } - if (this.props.spacesManager) { - this.props.spacesManager.on('request_refresh', () => { - this.loadSpaces(); - }); + public componentWillUnmount() { + if (this.activeSpace$) { + this.activeSpace$.unsubscribe(); } } @@ -59,20 +68,26 @@ export class NavControlPopover extends Component { } let element: React.ReactNode; - if (this.state.spaces.length < 2) { - element = ; + if (!this.state.loading && this.state.spaces.length < 2) { + element = ( + + ); } else { element = ( ); } return ( - // @ts-ignore repositionOnScroll doesn't exist on EuiPopover { } private async loadSpaces() { - const { spacesManager, activeSpace } = this.props; + const { spacesManager } = this.props; + + if (this.state.loading) { + return; + } this.setState({ loading: true, @@ -99,16 +118,8 @@ export class NavControlPopover extends Component { const spaces = await spacesManager.getSpaces(); - // Update the active space definition, if it changed since the last load operation - let activeSpaceEntry: Space | null = activeSpace.space; - - if (activeSpace.valid) { - activeSpaceEntry = spaces.find(space => space.id === this.props.activeSpace.space.id) || null; - } - this.setState({ spaces, - activeSpace: activeSpaceEntry, loading: false, }); } @@ -117,10 +128,7 @@ export class NavControlPopover extends Component { const { activeSpace } = this.state; if (!activeSpace) { - return this.getButton( - , - 'error' - ); + return this.getButton(, 'loading'); } return this.getButton( @@ -130,14 +138,17 @@ export class NavControlPopover extends Component { }; private getButton = (linkIcon: JSX.Element, linkTitle: string) => { - const Button = this.props.buttonClass; return ( -