diff --git a/.i18nrc.json b/.i18nrc.json index d3ad522126e6e..d0d8beb6f5337 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -6,6 +6,7 @@ "dashboardEmbeddableContainer": "src/plugins/dashboard_embeddable_container", "data": ["src/legacy/core_plugins/data", "src/plugins/data"], "embeddableApi": "src/plugins/embeddable", + "share": "src/plugins/share", "esUi": "src/plugins/es_ui_shared", "expressions": "src/plugins/expressions", "inputControl": "src/legacy/core_plugins/input_control_vis", diff --git a/TYPESCRIPT.md b/TYPESCRIPT.md index 4b8bcdb76c69c..7be9a5e4f3b17 100644 --- a/TYPESCRIPT.md +++ b/TYPESCRIPT.md @@ -47,13 +47,13 @@ Since `@elastic/eui` already ships with a module declaration, any local addition // file `typings/@elastic/eui/index.d.ts` import { CommonProps } from '@elastic/eui'; -import { SFC } from 'react'; +import { FC } from 'react'; declare module '@elastic/eui' { export type EuiNewComponentProps = CommonProps & { additionalProp: string; }; - export const EuiNewComponent: SFC; + export const EuiNewComponent: FC; } ``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md index c6920fc30d4ee..fdf56012e4729 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md @@ -15,11 +15,6 @@ export interface ChromeNavControl | Property | Type | Description | | --- | --- | --- | +| [mount](./kibana-plugin-public.chromenavcontrol.mount.md) | MountPoint | | | [order](./kibana-plugin-public.chromenavcontrol.order.md) | number | | -## Methods - -| Method | Description | -| --- | --- | -| [mount(targetDomElement)](./kibana-plugin-public.chromenavcontrol.mount.md) | | - diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md index 6ce5d7f0d5c4d..3e1f5a1f78f89 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md @@ -2,21 +2,10 @@ [Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) > [mount](./kibana-plugin-public.chromenavcontrol.mount.md) -## ChromeNavControl.mount() method +## ChromeNavControl.mount property Signature: ```typescript -mount(targetDomElement: HTMLElement): () => void; +mount: MountPoint; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| targetDomElement | HTMLElement | | - -Returns: - -`() => void` - diff --git a/docs/development/core/public/kibana-plugin-public.mountpoint.md b/docs/development/core/public/kibana-plugin-public.mountpoint.md index 58f407904a576..928d22f00ed00 100644 --- a/docs/development/core/public/kibana-plugin-public.mountpoint.md +++ b/docs/development/core/public/kibana-plugin-public.mountpoint.md @@ -9,5 +9,5 @@ A function that should mount DOM content inside the provided container element a Signature: ```typescript -export declare type MountPoint = (element: HTMLElement) => UnmountCallback; +export declare type MountPoint = (element: T) => UnmountCallback; ``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.md b/docs/development/core/public/kibana-plugin-public.overlaystart.md index 6bcf0a581df80..8b6f11bd819f8 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.md @@ -16,6 +16,6 @@ export interface OverlayStart | Property | Type | Description | | --- | --- | --- | | [banners](./kibana-plugin-public.overlaystart.banners.md) | OverlayBannersStart | [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | -| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | (flyoutChildren: React.ReactNode, flyoutProps?: {
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | -| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | (modalChildren: React.ReactNode, modalProps?: {
className?: string;
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | +| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | OverlayFlyoutStart['open'] | | +| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | OverlayModalStart['open'] | | diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md index 6d015d6a34382..ad3351fb4d098 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md @@ -4,11 +4,9 @@ ## OverlayStart.openFlyout property + Signature: ```typescript -openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; +openFlyout: OverlayFlyoutStart['open']; ``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md index a4569e178f17d..2c983d6151f4c 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md @@ -4,12 +4,9 @@ ## OverlayStart.openModal property + Signature: ```typescript -openModal: (modalChildren: React.ReactNode, modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; +openModal: OverlayModalStart['open']; ``` diff --git a/docs/user/reporting/development/index.asciidoc b/docs/user/reporting/development/index.asciidoc index 2a9abae34f042..a64e540da0c70 100644 --- a/docs/user/reporting/development/index.asciidoc +++ b/docs/user/reporting/development/index.asciidoc @@ -14,9 +14,7 @@ However, these docs will be kept up-to-date to reflect the current implementatio [float] [[reporting-nav-bar-extensions]] === Share menu extensions -X-Pack uses the `ShareContextMenuExtensionsRegistryProvider` to register actions in the share menu. - -This integration will likely be changing in the near future as we move towards a unified actions abstraction across {kib}. +X-Pack uses the `share` plugin of the Kibana platform to register actions in the share menu. [float] === Generate job URL diff --git a/package.json b/package.json index b0a74a4a5e04f..ac06753d7d046 100644 --- a/package.json +++ b/package.json @@ -347,8 +347,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "^2.8.0", + "@typescript-eslint/parser": "^2.8.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index c67629f058d5a..71517bc10404d 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "^2.8.0", + "@typescript-eslint/parser": "^2.8.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 757616f36180b..17dd32b766ab7 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -6,7 +6,8 @@ const semver = require('semver'); const PKG = require('../../package.json'); -const eslintConfigPrettierTypescriptEslintRules = require('eslint-config-prettier/@typescript-eslint').rules; +const eslintConfigPrettierTypescriptEslintRules = require('eslint-config-prettier/@typescript-eslint') + .rules; module.exports = { overrides: [ @@ -14,12 +15,7 @@ module.exports = { files: ['**/*.{ts,tsx}'], parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint', - 'ban', - 'import', - 'prefer-object-spread', - ], + plugins: ['@typescript-eslint', 'ban', 'import', 'prefer-object-spread'], settings: { 'import/resolver': { @@ -43,7 +39,7 @@ module.exports = { sourceType: 'module', ecmaVersion: 2018, ecmaFeatures: { - jsx: true + jsx: true, }, // NOTE: That is to avoid a known performance issue related with the `ts.Program` used by // typescript eslint. As we are not using rules that need types information, we can safely @@ -52,7 +48,7 @@ module.exports = { // https://github.com/typescript-eslint/typescript-eslint/issues/389 // https://github.com/typescript-eslint/typescript-eslint/issues/243 // https://github.com/typescript-eslint/typescript-eslint/pull/361 - project: undefined + project: undefined, }, // NOTE: we can't override the extends option here to apply @@ -69,41 +65,59 @@ module.exports = { // // Old recommended tslint rules '@typescript-eslint/adjacent-overload-signatures': 'error', - '@typescript-eslint/array-type': ['error', { default: 'array-simple', readonly: 'array-simple' }], - '@typescript-eslint/ban-types': 'error', - 'camelcase': 'off', - '@typescript-eslint/camelcase': ['error', { - 'properties': 'never', - 'ignoreDestructuring': true, - 'allow': ['^[A-Z0-9_]+$'] - }], + '@typescript-eslint/array-type': [ + 'error', + { default: 'array-simple', readonly: 'array-simple' }, + ], + '@typescript-eslint/ban-types': [ + 'error', + { + types: { SFC: null, 'React.SFC': null }, + }, + ], + camelcase: 'off', + '@typescript-eslint/camelcase': [ + 'error', + { + properties: 'never', + ignoreDestructuring: true, + allow: ['^[A-Z0-9_]+$'], + }, + ], '@typescript-eslint/class-name-casing': 'error', - '@typescript-eslint/explicit-member-accessibility': ['error', + '@typescript-eslint/explicit-member-accessibility': [ + 'error', { accessibility: 'off', overrides: { accessors: 'explicit', constructors: 'no-public', - parameterProperties: 'explicit' - } - } + parameterProperties: 'explicit', + }, + }, ], - 'indent': 'off', - '@typescript-eslint/indent': [ 'error', 2, { SwitchCase: 1 } ], + 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', { - 'default': ['public-static-field', 'static-field', 'instance-field'] - }], + '@typescript-eslint/member-ordering': [ + 'error', + { + default: ['public-static-field', 'static-field', 'instance-field'], + }, + ], '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', - '@typescript-eslint/triple-slash-reference': ['error', { - path: 'never', - types: 'never', - lib: 'never' - }], + '@typescript-eslint/triple-slash-reference': [ + 'error', + { + path: 'never', + types: 'never', + lib: 'never', + }, + ], '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/unified-signatures': 'error', @@ -111,20 +125,19 @@ module.exports = { 'arrow-parens': 'error', 'comma-dangle': ['error', 'always-multiline'], 'constructor-super': 'error', - 'curly': 'error', + curly: 'error', 'dot-notation': 'error', 'eol-last': 'error', - 'eqeqeq': ['error', 'always', {'null': 'ignore'}], + eqeqeq: ['error', 'always', { null: 'ignore' }], 'guard-for-in': 'error', - 'import/order': ['error', { - 'groups': [ - ['external', 'builtin'], - 'internal', - ['parent', 'sibling', 'index'], - ], - }], + 'import/order': [ + 'error', + { + groups: [['external', 'builtin'], 'internal', ['parent', 'sibling', 'index']], + }, + ], 'max-classes-per-file': ['error', 1], - 'max-len': [ 'error', { code: 120, ignoreComments: true, ignoreUrls: true } ], + 'max-len': ['error', { code: 120, ignoreComments: true, ignoreUrls: true }], 'new-parens': 'error', 'no-bitwise': 'error', 'no-caller': 'error', @@ -146,34 +159,40 @@ module.exports = { 'no-var': 'error', 'object-curly-spacing': 'error', 'object-shorthand': 'error', - 'one-var': [ 'error', 'never' ], + 'one-var': ['error', 'never'], 'prefer-const': 'error', - 'quotes': ['error', 'double', { 'avoidEscape': true }], + 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": ["/"] - }], + radix: 'error', + semi: 'error', + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], + 'spaced-comment': [ + 'error', + 'always', + { + exceptions: ['/'], + }, + ], 'use-isnan': 'error', // Old tslint yml override or defined rules 'ban/ban': [ 2, - {'name': ['describe', 'only'], 'message': 'No exclusive suites.'}, - {'name': ['it', 'only'], 'message': 'No exclusive tests.'}, - {'name': ['test', 'only'], 'message': 'No exclusive tests.'}, - + { name: ['describe', 'only'], message: 'No exclusive suites.' }, + { name: ['it', 'only'], message: 'No exclusive tests.' }, + { name: ['test', 'only'], message: 'No exclusive tests.' }, ], 'import/no-default-export': 'error', }, eslintConfigPrettierTypescriptEslintRules - ) + ), }, - ] + ], }; diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js deleted file mode 100644 index 6c149b6b8a98d..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { convertExistsFilter } from '../exists'; - -describe('filter to kuery migration', function () { - - describe('exists filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const result = convertExistsFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'exists'); - expect(result.arguments[0].value).to.be('foo'); - }); - - it('should throw an exception if the given filter is not of type "exists"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertExistsFilter).withArgs(filter).to.throwException( - /Expected filter of type "exists", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js deleted file mode 100644 index 1e5656f85eb89..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { filterToKueryAST } from '../filter_to_kuery'; - -describe('filter to kuery migration', function () { - - describe('filterToKueryAST', function () { - - it('should hand off conversion of known filter types to the appropriate converter', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const result = filterToKueryAST(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'exists'); - }); - - it('should thrown an error when an unknown filter type is encountered', function () { - const filter = { - meta: { - type: 'foo', - } - }; - - expect(filterToKueryAST).withArgs(filter).to.throwException(/Couldn't convert that filter to a kuery/); - }); - - it('should wrap the AST node of negated filters in a "not" function', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const negatedFilter = _.set(_.cloneDeep(filter), 'meta.negate', true); - - const result = filterToKueryAST(filter); - const negatedResult = filterToKueryAST(negatedFilter); - - expect(negatedResult).to.have.property('type', 'function'); - expect(negatedResult).to.have.property('function', 'not'); - expect(negatedResult.arguments[0]).to.eql(result); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js deleted file mode 100644 index e4cb6d30bbf48..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { convertGeoBoundingBox } from '../geo_bounding_box'; - -describe('filter to kuery migration', function () { - - describe('geo_bounding_box filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'geo_bounding_box', - key: 'foo', - params: { - topLeft: { - lat: 10, - lon: 20, - }, - bottomRight: { - lat: 30, - lon: 40, - }, - }, - } - }; - const result = convertGeoBoundingBox(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'geoBoundingBox'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - const argByName = _.mapKeys(args, 'name'); - expect(argByName.topLeft.value.value).to.be('10, 20'); - expect(argByName.bottomRight.value.value).to.be('30, 40'); - }); - - it('should throw an exception if the given filter is not of type "geo_bounding_box"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertGeoBoundingBox).withArgs(filter).to.throwException( - /Expected filter of type "geo_bounding_box", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js deleted file mode 100644 index e1b2a09edaba3..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { convertGeoPolygon } from '../geo_polygon'; - -describe('filter to kuery migration', function () { - - describe('geo_polygon filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'geo_polygon', - key: 'foo', - params: { - points: [ - { - lat: 10, - lon: 20, - }, - { - lat: 30, - lon: 40, - }, - ] - } - } - }; - const result = convertGeoPolygon(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'geoPolygon'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - expect(args[0].value).to.be('10, 20'); - expect(args[1].value).to.be('30, 40'); - }); - - it('should throw an exception if the given filter is not of type "geo_polygon"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertGeoPolygon).withArgs(filter).to.throwException( - /Expected filter of type "geo_polygon", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js deleted file mode 100644 index b2a7c097a3f95..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js +++ /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 expect from '@kbn/expect'; -import { convertPhraseFilter } from '../phrase'; - -describe('filter to kuery migration', function () { - - describe('phrase filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'phrase', - key: 'foo', - params: { - query: 'bar' - }, - } - }; - const result = convertPhraseFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'is'); - - const { arguments: [ { value: fieldName }, { value: value } ] } = result; - expect(fieldName).to.be('foo'); - expect(value).to.be('bar'); - }); - - it('should throw an exception if the given filter is not of type "phrase"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertPhraseFilter).withArgs(filter).to.throwException( - /Expected filter of type "phrase", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js deleted file mode 100644 index 2cad37cc0ad3a..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { convertRangeFilter } from '../range'; - -describe('filter to kuery migration', function () { - - describe('range filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'range', - key: 'foo', - params: { - gt: 1000, - lt: 8000, - }, - } - }; - const result = convertRangeFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'range'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - const argByName = _.mapKeys(args, 'name'); - expect(argByName.gt.value.value).to.be(1000); - expect(argByName.lt.value.value).to.be(8000); - }); - - it('should throw an exception if the given filter is not of type "range"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertRangeFilter).withArgs(filter).to.throwException( - /Expected filter of type "range", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js deleted file mode 100644 index 18c53938d6c36..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js +++ /dev/null @@ -1,54 +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 { nodeTypes } from '../node_types'; -import { convertPhraseFilter } from './phrase'; -import { convertRangeFilter } from './range'; -import { convertExistsFilter } from './exists'; -import { convertGeoBoundingBox } from './geo_bounding_box'; -import { convertGeoPolygon } from './geo_polygon'; - -const conversionChain = [ - convertPhraseFilter, - convertRangeFilter, - convertExistsFilter, - convertGeoBoundingBox, - convertGeoPolygon, -]; - -export function filterToKueryAST(filter) { - const { negate } = filter.meta; - - const node = conversionChain.reduce((acc, converter) => { - if (acc !== null) return acc; - - try { - return converter(filter); - } - catch (ex) { - return null; - } - }, null); - - if (!node) { - throw new Error(`Couldn't convert that filter to a kuery`); - } - - return negate ? nodeTypes.function.buildNode('not', node) : node; -} diff --git a/packages/kbn-es-query/src/kuery/filter_migration/range.js b/packages/kbn-es-query/src/kuery/filter_migration/range.js deleted file mode 100644 index 438ab979e1395..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/range.js +++ /dev/null @@ -1,29 +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 { nodeTypes } from '../node_types'; - -export function convertRangeFilter(filter) { - if (filter.meta.type !== 'range') { - throw new Error(`Expected filter of type "range", got "${filter.meta.type}"`); - } - - const { key, params } = filter.meta; - return nodeTypes.function.buildNode('range', key, params); -} diff --git a/packages/kbn-es-query/src/kuery/index.js b/packages/kbn-es-query/src/kuery/index.js index 08fa9829d4a56..e0cacada7f274 100644 --- a/packages/kbn-es-query/src/kuery/index.js +++ b/packages/kbn-es-query/src/kuery/index.js @@ -18,6 +18,5 @@ */ export * from './ast'; -export * from './filter_migration'; export { nodeTypes } from './node_types'; export * from './errors'; diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 366a5b65fbb99..6989c2159dce3 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1130,7 +1130,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. | +| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. | | `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | | `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | | @@ -1142,6 +1142,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. | | `ui/registry/vis_types` | `visualizations.types` | -- | | `ui/vis` | `visualizations.types` | -- | +| `ui/share` | `share` | `showShareContextMenu` is now called `toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider` is now called `register` | | `ui/vis/vis_factory` | `visualizations.types` | -- | | `ui/vis/vis_filters` | `visualizations.filters` | -- | | `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 0088ef68aaeb8..7f9c75595a4ce 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -20,11 +20,12 @@ import { sortBy } from 'lodash'; import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; +import { MountPoint } from '../../types'; /** @public */ export interface ChromeNavControl { order?: number; - mount(targetDomElement: HTMLElement): () => void; + mount: MountPoint; } /** diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx index 90e7907b0068c..76413a0ea0317 100644 --- a/src/core/public/chrome/ui/header/header_extension.tsx +++ b/src/core/public/chrome/ui/header/header_extension.tsx @@ -18,9 +18,10 @@ */ import React from 'react'; +import { MountPoint } from '../../../types'; interface Props { - extension?: (el: HTMLDivElement) => () => void; + extension?: MountPoint; } export class HeaderExtension extends React.Component { diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 79fa32040b14c..22e315f9e1b03 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -19,7 +19,7 @@ import angular from 'angular'; import { InternalCoreSetup, InternalCoreStart } from '../core_system'; -import { LegacyCoreSetup, LegacyCoreStart } from '../'; +import { LegacyCoreSetup, LegacyCoreStart, MountPoint } from '../'; /** @internal */ export interface LegacyPlatformParams { @@ -40,7 +40,7 @@ interface StartDeps { } interface BootstrapModule { - bootstrap: (targetDomElement: HTMLElement) => void; + bootstrap: MountPoint; } /** diff --git a/src/core/public/notifications/toasts/error_toast.test.tsx b/src/core/public/notifications/toasts/error_toast.test.tsx index b72b2de85340a..b497be526093d 100644 --- a/src/core/public/notifications/toasts/error_toast.test.tsx +++ b/src/core/public/notifications/toasts/error_toast.test.tsx @@ -40,6 +40,7 @@ function render(props: ErrorToastProps = {}) { error={props.error || new Error('error message')} title={props.title || 'An error occured'} toastMessage={props.toastMessage || 'This is the toast message'} + i18nContext={() => ({ children }) => {children}} /> ); } diff --git a/src/core/public/notifications/toasts/error_toast.tsx b/src/core/public/notifications/toasts/error_toast.tsx index 10bc51559644b..6b53719839b0f 100644 --- a/src/core/public/notifications/toasts/error_toast.tsx +++ b/src/core/public/notifications/toasts/error_toast.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; +import ReactDOM from 'react-dom'; import { EuiButton, @@ -32,12 +33,14 @@ import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { OverlayStart } from '../../overlays'; +import { I18nStart } from '../../i18n'; interface ErrorToastProps { title: string; error: Error; toastMessage: string; openModal: OverlayStart['openModal']; + i18nContext: () => I18nStart['Context']; } /** @@ -50,33 +53,48 @@ function showErrorDialog({ title, error, openModal, -}: Pick) { + i18nContext, +}: Pick) { + const I18nContext = i18nContext(); const modal = openModal( - - - {title} - - - - {error.stack && ( - - - - {error.stack} - - - )} - - - modal.close()} fill> - - - - + mount( + + + + {title} + + + + {error.stack && ( + + + + {error.stack} + + + )} + + + modal.close()} fill> + + + + + + ) ); } -export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToastProps) { +export function ErrorToast({ + title, + error, + toastMessage, + openModal, + i18nContext, +}: ErrorToastProps) { return (

{toastMessage}

@@ -84,7 +102,7 @@ export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToast showErrorDialog({ title, error, openModal })} + onClick={() => showErrorDialog({ title, error, openModal, i18nContext })} > ); } + +const mount = (component: React.ReactElement) => (container: HTMLElement) => { + ReactDOM.render(component, container); + return () => ReactDOM.unmountComponentAtNode(container); +}; diff --git a/src/core/public/notifications/toasts/toasts_api.test.ts b/src/core/public/notifications/toasts/toasts_api.test.ts index f99a28617aa5c..a0e419e989657 100644 --- a/src/core/public/notifications/toasts/toasts_api.test.ts +++ b/src/core/public/notifications/toasts/toasts_api.test.ts @@ -51,10 +51,13 @@ function uiSettingsMock() { function toastDeps() { return { uiSettings: uiSettingsMock(), - i18n: i18nServiceMock.createStartContract(), }; } +function startDeps() { + return { overlays: {} as any, i18n: i18nServiceMock.createStartContract() }; +} + describe('#get$()', () => { it('returns observable that emits NEW toast list when something added or removed', () => { const toasts = new ToastsApi(toastDeps()); @@ -188,6 +191,7 @@ describe('#addDanger()', () => { describe('#addError', () => { it('adds an error toast', async () => { const toasts = new ToastsApi(toastDeps()); + toasts.start(startDeps()); const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' }); expect(toast).toHaveProperty('color', 'danger'); expect(toast).toHaveProperty('title', 'Something went wrong'); @@ -195,6 +199,7 @@ describe('#addError', () => { it('returns the created toast', async () => { const toasts = new ToastsApi(toastDeps()); + toasts.start(startDeps()); const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' }); const currentToasts = await getCurrentToasts(toasts); expect(currentToasts[0]).toBe(toast); diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx index b49bafda5b26e..a21b727b02d73 100644 --- a/src/core/public/notifications/toasts/toasts_api.tsx +++ b/src/core/public/notifications/toasts/toasts_api.tsx @@ -26,6 +26,7 @@ import { MountPoint } from '../../types'; import { mountReactNode } from '../../utils'; import { UiSettingsClientContract } from '../../ui_settings'; import { OverlayStart } from '../../overlays'; +import { I18nStart } from '../../i18n'; /** * Allowed fields for {@link ToastInput}. @@ -96,14 +97,16 @@ export class ToastsApi implements IToasts { private uiSettings: UiSettingsClientContract; private overlays?: OverlayStart; + private i18n?: I18nStart; constructor(deps: { uiSettings: UiSettingsClientContract }) { this.uiSettings = deps.uiSettings; } /** @internal */ - public registerOverlays(overlays: OverlayStart) { + public start({ overlays, i18n }: { overlays: OverlayStart; i18n: I18nStart }) { this.overlays = overlays; + this.i18n = i18n; } /** Observable of the toast messages to show to the user. */ @@ -206,6 +209,7 @@ export class ToastsApi implements IToasts { error={error} title={options.title} toastMessage={message} + i18nContext={() => this.i18n!.Context} /> ), }); diff --git a/src/core/public/notifications/toasts/toasts_service.tsx b/src/core/public/notifications/toasts/toasts_service.tsx index 47d8c14f9af8b..81d23afc4f4d3 100644 --- a/src/core/public/notifications/toasts/toasts_service.tsx +++ b/src/core/public/notifications/toasts/toasts_service.tsx @@ -58,7 +58,7 @@ export class ToastsService { } public start({ i18n, overlays, targetDomElement }: StartDeps) { - this.api!.registerOverlays(overlays); + this.api!.start({ overlays, i18n }); this.targetDomElement = targetDomElement; render( diff --git a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap b/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap deleted file mode 100644 index 0c19c6312a672..0000000000000 --- a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = ` -Array [ - Array [ -
, - ], -] -`; - -exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = ` -Array [ - Array [ - - - - Flyout content - - - , -
, - ], -] -`; - -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` -Array [ - Array [ - - - - Flyout content 1 - - - , -
, - ], - Array [ - - - - Flyout content 2 - - - , -
, - ], -] -`; diff --git a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap b/src/core/public/overlays/__snapshots__/modal.test.tsx.snap deleted file mode 100644 index a4e6f5d6f72b8..0000000000000 --- a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap +++ /dev/null @@ -1,64 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = ` -Array [ - Array [ -
, - ], -] -`; - -exports[`ModalService openModal() renders a modal to the DOM 1`] = ` -Array [ - Array [ - - - - - Modal content - - - - , -
, - ], -] -`; - -exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` -Array [ - Array [ - - - - - Modal content 1 - - - - , -
, - ], - Array [ - - - - - Flyout content 2 - - - - , -
, - ], -] -`; diff --git a/src/core/public/overlays/_index.scss b/src/core/public/overlays/_index.scss index fe86883aedcf9..368dc9b644ff9 100644 --- a/src/core/public/overlays/_index.scss +++ b/src/core/public/overlays/_index.scss @@ -1 +1,2 @@ @import './banners/index'; +@import './mount_wrapper'; diff --git a/src/core/public/overlays/_mount_wrapper.scss b/src/core/public/overlays/_mount_wrapper.scss new file mode 100644 index 0000000000000..aafcc4bbe87db --- /dev/null +++ b/src/core/public/overlays/_mount_wrapper.scss @@ -0,0 +1,5 @@ +.kbnOverlayMountWrapper { + display: flex; + flex-direction: column; + height: 100%; +} diff --git a/src/core/public/overlays/banners/banners_service.tsx b/src/core/public/overlays/banners/banners_service.tsx index 31d49b5952e87..a26a7c71bc61f 100644 --- a/src/core/public/overlays/banners/banners_service.tsx +++ b/src/core/public/overlays/banners/banners_service.tsx @@ -97,9 +97,7 @@ export class OverlayBannersService { if (!banners$.value.has(id)) { return false; } - banners$.next(banners$.value.remove(id)); - return true; }, @@ -107,10 +105,8 @@ export class OverlayBannersService { if (!id || !banners$.value.has(id)) { return this.add(mount, priority); } - const nextId = genId(); const nextBanner = { id: nextId, mount, priority }; - banners$.next(banners$.value.remove(id).add(nextId, nextBanner)); return nextId; }, diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap new file mode 100644 index 0000000000000..94c11f0185427 --- /dev/null +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = ` +Array [ + Array [ +
, + ], +] +`; + +exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = ` +Array [ + Array [ + + + + + , +
, + ], +] +`; + +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; + +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` +Array [ + Array [ + + + + + , +
, + ], + Array [ + + + + + , +
, + ], +] +`; + +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; diff --git a/src/core/public/overlays/flyout/flyout_service.mock.ts b/src/core/public/overlays/flyout/flyout_service.mock.ts new file mode 100644 index 0000000000000..91544500713d6 --- /dev/null +++ b/src/core/public/overlays/flyout/flyout_service.mock.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FlyoutService, OverlayFlyoutStart } from './flyout_service'; + +const createStartContractMock = () => { + const startContract: jest.Mocked = { + open: jest.fn().mockReturnValue({ + close: jest.fn(), + onClose: Promise.resolve(), + }), + }; + return startContract; +}; + +const createMock = () => { + const mocked: jest.Mocked> = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; +}; + +export const overlayFlyoutServiceMock = { + create: createMock, + createStartContract: createStartContractMock, +}; diff --git a/src/core/public/overlays/flyout.test.tsx b/src/core/public/overlays/flyout/flyout_service.test.tsx similarity index 65% rename from src/core/public/overlays/flyout.test.tsx rename to src/core/public/overlays/flyout/flyout_service.test.tsx index afe37fc50776b..25ba94f577993 100644 --- a/src/core/public/overlays/flyout.test.tsx +++ b/src/core/public/overlays/flyout/flyout_service.test.tsx @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks'; +import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks'; -import React from 'react'; -import { i18nServiceMock } from '../i18n/i18n_service.mock'; -import { FlyoutRef, FlyoutService } from './flyout'; +import { mount } from 'enzyme'; +import { i18nServiceMock } from '../../i18n/i18n_service.mock'; +import { FlyoutService, OverlayFlyoutStart } from './flyout_service'; +import { OverlayRef } from '../types'; const i18nMock = i18nServiceMock.createStartContract(); @@ -29,35 +30,50 @@ beforeEach(() => { mockReactDomUnmount.mockClear(); }); +const mountText = (text: string) => (container: HTMLElement) => { + const content = document.createElement('span'); + content.textContent = text; + container.append(content); + return () => {}; +}; + +const getServiceStart = () => { + const service = new FlyoutService(); + return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') }); +}; + describe('FlyoutService', () => { + let flyouts: OverlayFlyoutStart; + beforeEach(() => { + flyouts = getServiceStart(); + }); + describe('openFlyout()', () => { it('renders a flyout to the DOM', () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); expect(mockReactDomRender).not.toHaveBeenCalled(); - flyoutService.openFlyout(i18nMock, Flyout content); + flyouts.open(mountText('Flyout content')); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); }); describe('with a currently active flyout', () => { - let target: HTMLElement; - let flyoutService: FlyoutService; - let ref1: FlyoutRef; + let ref1: OverlayRef; beforeEach(() => { - target = document.createElement('div'); - flyoutService = new FlyoutService(target); - ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1); + ref1 = flyouts.open(mountText('Flyout content')); }); it('replaces the current flyout with a new one', () => { - flyoutService.openFlyout(i18nMock, Flyout content 2); + flyouts.open(mountText('Flyout content 2')); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + const modalContent = mount(mockReactDomRender.mock.calls[1][0]); + expect(modalContent.html()).toMatchSnapshot(); expect(() => ref1.close()).not.toThrowError(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); it('resolves onClose on the previous ref', async () => { const onCloseComplete = jest.fn(); ref1.onClose.then(onCloseComplete); - flyoutService.openFlyout(i18nMock, Flyout content 2); + flyouts.open(mountText('Flyout content 2')); await ref1.onClose; expect(onCloseComplete).toBeCalledTimes(1); }); @@ -65,9 +81,7 @@ describe('FlyoutService', () => { }); describe('FlyoutRef#close()', () => { it('resolves the onClose Promise', async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref = flyoutService.openFlyout(i18nMock, Flyout content); + const ref = flyouts.open(mountText('Flyout content')); const onCloseComplete = jest.fn(); ref.onClose.then(onCloseComplete); @@ -76,9 +90,7 @@ describe('FlyoutService', () => { expect(onCloseComplete).toHaveBeenCalledTimes(1); }); it('can be called multiple times on the same FlyoutRef', async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref = flyoutService.openFlyout(i18nMock, Flyout content); + const ref = flyouts.open(mountText('Flyout content')); expect(mockReactDomUnmount).not.toHaveBeenCalled(); await ref.close(); expect(mockReactDomUnmount.mock.calls).toMatchSnapshot(); @@ -86,10 +98,8 @@ describe('FlyoutService', () => { expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); it("on a stale FlyoutRef doesn't affect the active flyout", async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1); - const ref2 = flyoutService.openFlyout(i18nMock, Flyout content 2); + const ref1 = flyouts.open(mountText('Flyout content 1')); + const ref2 = flyouts.open(mountText('Flyout content 2')); const onCloseComplete = jest.fn(); ref2.onClose.then(onCloseComplete); mockReactDomUnmount.mockClear(); diff --git a/src/core/public/overlays/flyout.tsx b/src/core/public/overlays/flyout/flyout_service.tsx similarity index 57% rename from src/core/public/overlays/flyout.tsx rename to src/core/public/overlays/flyout/flyout_service.tsx index c8ba9c6b284d3..b609b2ce1d741 100644 --- a/src/core/public/overlays/flyout.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -23,8 +23,10 @@ import { EuiFlyout } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; -import { I18nStart } from '../i18n'; -import { OverlayRef } from './overlay_service'; +import { I18nStart } from '../../i18n'; +import { MountPoint } from '../../types'; +import { OverlayRef } from '../types'; +import { MountWrapper } from '../../utils'; /** * A FlyoutRef is a reference to an opened flyout panel. It offers methods to @@ -37,7 +39,7 @@ import { OverlayRef } from './overlay_service'; * * @public */ -export class FlyoutRef implements OverlayRef { +class FlyoutRef implements OverlayRef { /** * An Promise that will resolve once this flyout is closed. * @@ -66,55 +68,77 @@ export class FlyoutRef implements OverlayRef { } } +/** + * APIs to open and manage fly-out dialogs. + * + * @public + */ +export interface OverlayFlyoutStart { + /** + * Opens a flyout panel with the given mount point inside. You can use + * `close()` on the returned FlyoutRef to close the flyout. + * + * @param mount {@link MountPoint} - Mounts the children inside a flyout panel + * @param options {@link OverlayFlyoutOpenOptions} - options for the flyout + * @return {@link OverlayRef} A reference to the opened flyout panel. + */ + open(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef; +} + +/** + * @public + */ +export interface OverlayFlyoutOpenOptions { + className?: string; + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; +} + +interface StartDeps { + i18n: I18nStart; + targetDomElement: Element; +} + /** @internal */ export class FlyoutService { private activeFlyout: FlyoutRef | null = null; + private targetDomElement: Element | null = null; - constructor(private readonly targetDomElement: Element) {} + public start({ i18n, targetDomElement }: StartDeps): OverlayFlyoutStart { + this.targetDomElement = targetDomElement; - /** - * Opens a flyout panel with the given component inside. You can use - * `close()` on the returned FlyoutRef to close the flyout. - * - * @param flyoutChildren - Mounts the children inside a flyout panel - * @return {FlyoutRef} A reference to the opened flyout panel. - */ - public openFlyout = ( - i18n: I18nStart, - flyoutChildren: React.ReactNode, - flyoutProps: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } = {} - ): FlyoutRef => { - // If there is an active flyout session close it before opening a new one. - if (this.activeFlyout) { - this.activeFlyout.close(); - this.cleanupDom(); - } + return { + open: (mount: MountPoint, options: OverlayFlyoutOpenOptions = {}): OverlayRef => { + // If there is an active flyout session close it before opening a new one. + if (this.activeFlyout) { + this.activeFlyout.close(); + this.cleanupDom(); + } - const flyout = new FlyoutRef(); + const flyout = new FlyoutRef(); - // If a flyout gets closed through it's FlyoutRef, remove it from the dom - flyout.onClose.then(() => { - if (this.activeFlyout === flyout) { - this.cleanupDom(); - } - }); + // If a flyout gets closed through it's FlyoutRef, remove it from the dom + flyout.onClose.then(() => { + if (this.activeFlyout === flyout) { + this.cleanupDom(); + } + }); - this.activeFlyout = flyout; + this.activeFlyout = flyout; - render( - - flyout.close()}> - {flyoutChildren} - - , - this.targetDomElement - ); + render( + + flyout.close()}> + + + , + this.targetDomElement + ); - return flyout; - }; + return flyout; + }, + }; + } /** * Using React.Render to re-render into a target DOM element will replace @@ -124,8 +148,10 @@ export class FlyoutService { * depend on unmounting for cleanup behaviour. */ private cleanupDom(): void { - unmountComponentAtNode(this.targetDomElement); - this.targetDomElement.innerHTML = ''; + if (this.targetDomElement != null) { + unmountComponentAtNode(this.targetDomElement); + this.targetDomElement.innerHTML = ''; + } this.activeFlyout = null; } } diff --git a/src/legacy/ui/public/share/index.ts b/src/core/public/overlays/flyout/index.ts similarity index 84% rename from src/legacy/ui/public/share/index.ts rename to src/core/public/overlays/flyout/index.ts index 3a1264541cdea..b01cc3af5fa38 100644 --- a/src/legacy/ui/public/share/index.ts +++ b/src/core/public/overlays/flyout/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { showShareContextMenu } from './show_share_context_menu'; -export { ShareContextMenuExtensionsRegistryProvider } from './share_action_registry'; +export { FlyoutService, OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout_service'; diff --git a/src/core/public/overlays/index.ts b/src/core/public/overlays/index.ts index ff03e5dffb2ca..417486f78f719 100644 --- a/src/core/public/overlays/index.ts +++ b/src/core/public/overlays/index.ts @@ -17,5 +17,8 @@ * under the License. */ +export { OverlayRef } from './types'; export { OverlayBannersStart } from './banners'; -export { OverlayService, OverlayStart, OverlayRef } from './overlay_service'; +export { OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout'; +export { OverlayModalStart, OverlayModalOpenOptions } from './modal'; +export { OverlayService, OverlayStart } from './overlay_service'; diff --git a/src/core/public/overlays/modal.tsx b/src/core/public/overlays/modal.tsx deleted file mode 100644 index 6f94788b84d71..0000000000000 --- a/src/core/public/overlays/modal.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable max-classes-per-file */ - -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { Subject } from 'rxjs'; -import { I18nStart } from '../i18n'; -import { OverlayRef } from './overlay_service'; - -/** - * A ModalRef is a reference to an opened modal. It offers methods to - * close the modal. - * - * @public - */ -export class ModalRef implements OverlayRef { - public readonly onClose: Promise; - - private closeSubject = new Subject(); - - constructor() { - this.onClose = this.closeSubject.toPromise(); - } - - /** - * Closes the referenced modal if it's still open which in turn will - * resolve the `onClose` Promise. If the modal had already been - * closed this method does nothing. - */ - public close(): Promise { - if (!this.closeSubject.closed) { - this.closeSubject.next(); - this.closeSubject.complete(); - } - return this.onClose; - } -} - -/** @internal */ -export class ModalService { - private activeModal: ModalRef | null = null; - - constructor(private readonly targetDomElement: Element) {} - - /** - * Opens a flyout panel with the given component inside. You can use - * `close()` on the returned FlyoutRef to close the flyout. - * - * @param flyoutChildren - Mounts the children inside a flyout panel - * @return {FlyoutRef} A reference to the opened flyout panel. - */ - public openModal = ( - i18n: I18nStart, - modalChildren: React.ReactNode, - modalProps: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } = {} - ): ModalRef => { - // If there is an active flyout session close it before opening a new one. - if (this.activeModal) { - this.activeModal.close(); - this.cleanupDom(); - } - - const modal = new ModalRef(); - - // If a modal gets closed through it's ModalRef, remove it from the dom - modal.onClose.then(() => { - if (this.activeModal === modal) { - this.cleanupDom(); - } - }); - - this.activeModal = modal; - - render( - - - modal.close()}> - {modalChildren} - - - , - this.targetDomElement - ); - - return modal; - }; - - /** - * Using React.Render to re-render into a target DOM element will replace - * the content of the target but won't call unmountComponent on any - * components inside the target or any of their children. So we properly - * cleanup the DOM here to prevent subtle bugs in child components which - * depend on unmounting for cleanup behaviour. - */ - private cleanupDom(): void { - unmountComponentAtNode(this.targetDomElement); - this.targetDomElement.innerHTML = ''; - this.activeModal = null; - } -} diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap new file mode 100644 index 0000000000000..3928c54f90179 --- /dev/null +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = ` +Array [ + Array [ +
, + ], +] +`; + +exports[`ModalService openModal() renders a modal to the DOM 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], +] +`; + +exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; + +exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], + Array [ + + + + + + + , +
, + ], +] +`; diff --git a/src/core/public/overlays/modal/index.ts b/src/core/public/overlays/modal/index.ts new file mode 100644 index 0000000000000..9ef4492af3a3a --- /dev/null +++ b/src/core/public/overlays/modal/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 { ModalService, OverlayModalStart, OverlayModalOpenOptions } from './modal_service'; diff --git a/packages/kbn-es-query/src/kuery/filter_migration/exists.js b/src/core/public/overlays/modal/modal_service.mock.ts similarity index 56% rename from packages/kbn-es-query/src/kuery/filter_migration/exists.js rename to src/core/public/overlays/modal/modal_service.mock.ts index ce6403d681b56..726209b8f277c 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/exists.js +++ b/src/core/public/overlays/modal/modal_service.mock.ts @@ -17,13 +17,27 @@ * under the License. */ -import { nodeTypes } from '../node_types'; +import { ModalService, OverlayModalStart } from './modal_service'; -export function convertExistsFilter(filter) { - if (filter.meta.type !== 'exists') { - throw new Error(`Expected filter of type "exists", got "${filter.meta.type}"`); - } +const createStartContractMock = () => { + const startContract: jest.Mocked = { + open: jest.fn().mockReturnValue({ + close: jest.fn(), + onClose: Promise.resolve(), + }), + }; + return startContract; +}; - const { key } = filter.meta; - return nodeTypes.function.buildNode('exists', key); -} +const createMock = () => { + const mocked: jest.Mocked> = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; +}; + +export const overlayModalServiceMock = { + create: createMock, + createStartContract: createStartContractMock, +}; diff --git a/src/core/public/overlays/modal.test.tsx b/src/core/public/overlays/modal/modal_service.test.tsx similarity index 65% rename from src/core/public/overlays/modal.test.tsx rename to src/core/public/overlays/modal/modal_service.test.tsx index ee8bba5d61112..582c2697aef30 100644 --- a/src/core/public/overlays/modal.test.tsx +++ b/src/core/public/overlays/modal/modal_service.test.tsx @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks'; +import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks'; import React from 'react'; -import { i18nServiceMock } from '../i18n/i18n_service.mock'; -import { ModalService, ModalRef } from './modal'; +import { mount } from 'enzyme'; +import { i18nServiceMock } from '../../i18n/i18n_service.mock'; +import { ModalService, OverlayModalStart } from './modal_service'; +import { mountReactNode } from '../../utils'; +import { OverlayRef } from '../types'; const i18nMock = i18nServiceMock.createStartContract(); @@ -29,45 +32,59 @@ beforeEach(() => { mockReactDomUnmount.mockClear(); }); +const getServiceStart = () => { + const service = new ModalService(); + return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') }); +}; + describe('ModalService', () => { + let modals: OverlayModalStart; + beforeEach(() => { + modals = getServiceStart(); + }); + describe('openModal()', () => { it('renders a modal to the DOM', () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); expect(mockReactDomRender).not.toHaveBeenCalled(); - modalService.openModal(i18nMock, Modal content); + modals.open(container => { + const content = document.createElement('span'); + content.textContent = 'Modal content'; + container.append(content); + return () => {}; + }); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); }); + describe('with a currently active modal', () => { - let target: HTMLElement; - let modalService: ModalService; - let ref1: ModalRef; + let ref1: OverlayRef; + beforeEach(() => { - target = document.createElement('div'); - modalService = new ModalService(target); - ref1 = modalService.openModal(i18nMock, Modal content 1); + ref1 = modals.open(mountReactNode(Modal content 1)); }); + it('replaces the current modal with a new one', () => { - modalService.openModal(i18nMock, Flyout content 2); + modals.open(mountReactNode(Flyout content 2)); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); expect(() => ref1.close()).not.toThrowError(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); + it('resolves onClose on the previous ref', async () => { const onCloseComplete = jest.fn(); ref1.onClose.then(onCloseComplete); - modalService.openModal(i18nMock, Flyout content 2); + modals.open(mountReactNode(Flyout content 2)); await ref1.onClose; expect(onCloseComplete).toBeCalledTimes(1); }); }); }); + describe('ModalRef#close()', () => { it('resolves the onClose Promise', async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref = modalService.openModal(i18nMock, Flyout content); + const ref = modals.open(mountReactNode(Flyout content)); const onCloseComplete = jest.fn(); ref.onClose.then(onCloseComplete); @@ -75,21 +92,19 @@ describe('ModalService', () => { await ref.close(); expect(onCloseComplete).toHaveBeenCalledTimes(1); }); + it('can be called multiple times on the same ModalRef', async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref = modalService.openModal(i18nMock, Flyout content); + const ref = modals.open(mountReactNode(Flyout content)); expect(mockReactDomUnmount).not.toHaveBeenCalled(); await ref.close(); expect(mockReactDomUnmount.mock.calls).toMatchSnapshot(); await ref.close(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); + it("on a stale ModalRef doesn't affect the active flyout", async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref1 = modalService.openModal(i18nMock, Modal content 1); - const ref2 = modalService.openModal(i18nMock, Modal content 2); + const ref1 = modals.open(mountReactNode(Modal content 1)); + const ref2 = modals.open(mountReactNode(Modal content 2)); const onCloseComplete = jest.fn(); ref2.onClose.then(onCloseComplete); mockReactDomUnmount.mockClear(); diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx new file mode 100644 index 0000000000000..cb77c2ec4c88c --- /dev/null +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -0,0 +1,148 @@ +/* + * 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 */ + +import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Subject } from 'rxjs'; +import { I18nStart } from '../../i18n'; +import { MountPoint } from '../../types'; +import { OverlayRef } from '../types'; +import { MountWrapper } from '../../utils'; + +/** + * A ModalRef is a reference to an opened modal. It offers methods to + * close the modal. + * + * @public + */ +class ModalRef implements OverlayRef { + public readonly onClose: Promise; + + private closeSubject = new Subject(); + + constructor() { + this.onClose = this.closeSubject.toPromise(); + } + + /** + * Closes the referenced modal if it's still open which in turn will + * resolve the `onClose` Promise. If the modal had already been + * closed this method does nothing. + */ + public close(): Promise { + if (!this.closeSubject.closed) { + this.closeSubject.next(); + this.closeSubject.complete(); + } + return this.onClose; + } +} + +/** + * APIs to open and manage modal dialogs. + * + * @public + */ +export interface OverlayModalStart { + /** + * Opens a modal panel with the given mount point inside. You can use + * `close()` on the returned OverlayRef to close the modal. + * + * @param mount {@link MountPoint} - Mounts the children inside the modal + * @param options {@link OverlayModalOpenOptions} - options for the modal + * @return {@link OverlayRef} A reference to the opened modal. + */ + open(mount: MountPoint, options?: OverlayModalOpenOptions): OverlayRef; +} + +/** + * @public + */ +export interface OverlayModalOpenOptions { + className?: string; + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; +} + +interface StartDeps { + i18n: I18nStart; + targetDomElement: Element; +} + +/** @internal */ +export class ModalService { + private activeModal: ModalRef | null = null; + private targetDomElement: Element | null = null; + + public start({ i18n, targetDomElement }: StartDeps): OverlayModalStart { + this.targetDomElement = targetDomElement; + + return { + open: (mount: MountPoint, options: OverlayModalOpenOptions = {}): OverlayRef => { + // If there is an active flyout session close it before opening a new one. + if (this.activeModal) { + this.activeModal.close(); + this.cleanupDom(); + } + + const modal = new ModalRef(); + + // If a modal gets closed through it's ModalRef, remove it from the dom + modal.onClose.then(() => { + if (this.activeModal === modal) { + this.cleanupDom(); + } + }); + + this.activeModal = modal; + + render( + + + modal.close()}> + + + + , + targetDomElement + ); + + return modal; + }, + }; + } + + /** + * Using React.Render to re-render into a target DOM element will replace + * the content of the target but won't call unmountComponent on any + * components inside the target or any of their children. So we properly + * cleanup the DOM here to prevent subtle bugs in child components which + * depend on unmounting for cleanup behaviour. + */ + private cleanupDom(): void { + if (this.targetDomElement != null) { + unmountComponentAtNode(this.targetDomElement); + this.targetDomElement.innerHTML = ''; + } + this.activeModal = null; + } +} diff --git a/src/core/public/overlays/flyout.test.mocks.ts b/src/core/public/overlays/overlay.test.mocks.ts similarity index 88% rename from src/core/public/overlays/flyout.test.mocks.ts rename to src/core/public/overlays/overlay.test.mocks.ts index 35a046e960077..563f414a0ae99 100644 --- a/src/core/public/overlays/flyout.test.mocks.ts +++ b/src/core/public/overlays/overlay.test.mocks.ts @@ -19,7 +19,9 @@ export const mockReactDomRender = jest.fn(); export const mockReactDomUnmount = jest.fn(); +export const mockReactDomCreatePortal = jest.fn().mockImplementation(component => component); jest.doMock('react-dom', () => ({ render: mockReactDomRender, + createPortal: mockReactDomCreatePortal, unmountComponentAtNode: mockReactDomUnmount, })); diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts index 39abc6f765b97..2937ec89bfc74 100644 --- a/src/core/public/overlays/overlay_service.mock.ts +++ b/src/core/public/overlays/overlay_service.mock.ts @@ -18,17 +18,15 @@ */ import { OverlayService, OverlayStart } from './overlay_service'; import { overlayBannersServiceMock } from './banners/banners_service.mock'; +import { overlayFlyoutServiceMock } from './flyout/flyout_service.mock'; +import { overlayModalServiceMock } from './modal/modal_service.mock'; const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { - openFlyout: jest.fn(), - openModal: jest.fn(), + openFlyout: overlayFlyoutServiceMock.createStartContract().open, + openModal: overlayModalServiceMock.createStartContract().open, banners: overlayBannersServiceMock.createStartContract(), }; - startContract.openModal.mockReturnValue({ - close: jest.fn(), - onClose: Promise.resolve(), - }); return startContract; }; diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts index 1a72bb5dbe435..82fe753d6f283 100644 --- a/src/core/public/overlays/overlay_service.ts +++ b/src/core/public/overlays/overlay_service.ts @@ -17,34 +17,11 @@ * under the License. */ -import React from 'react'; - -import { FlyoutService } from './flyout'; -import { ModalService } from './modal'; import { I18nStart } from '../i18n'; -import { OverlayBannersStart, OverlayBannersService } from './banners'; import { UiSettingsClientContract } from '../ui_settings'; - -/** - * Returned by {@link OverlayStart} methods for closing a mounted overlay. - * @public - */ -export interface OverlayRef { - /** - * A Promise that will resolve once this overlay is closed. - * - * Overlays can close from user interaction, calling `close()` on the overlay - * reference or another overlay replacing yours via `openModal` or `openFlyout`. - */ - onClose: Promise; - - /** - * Closes the referenced overlay if it's still open which in turn will - * resolve the `onClose` Promise. If the overlay had already been - * closed this method does nothing. - */ - close(): Promise; -} +import { OverlayBannersStart, OverlayBannersService } from './banners'; +import { FlyoutService, OverlayFlyoutStart } from './flyout'; +import { ModalService, OverlayModalStart } from './modal'; interface StartDeps { i18n: I18nStart; @@ -54,19 +31,25 @@ interface StartDeps { /** @internal */ export class OverlayService { + private bannersService = new OverlayBannersService(); + private modalService = new ModalService(); + private flyoutService = new FlyoutService(); + public start({ i18n, targetDomElement, uiSettings }: StartDeps): OverlayStart { const flyoutElement = document.createElement('div'); - const modalElement = document.createElement('div'); targetDomElement.appendChild(flyoutElement); + const flyouts = this.flyoutService.start({ i18n, targetDomElement: flyoutElement }); + + const banners = this.bannersService.start({ i18n, uiSettings }); + + const modalElement = document.createElement('div'); targetDomElement.appendChild(modalElement); - const flyoutService = new FlyoutService(flyoutElement); - const modalService = new ModalService(modalElement); - const bannersService = new OverlayBannersService(); + const modals = this.modalService.start({ i18n, targetDomElement: modalElement }); return { - banners: bannersService.start({ i18n, uiSettings }), - openFlyout: flyoutService.openFlyout.bind(flyoutService, i18n), - openModal: modalService.openModal.bind(modalService, i18n), + banners, + openFlyout: flyouts.open.bind(flyouts), + openModal: modals.open.bind(modals), }; } } @@ -75,19 +58,8 @@ export class OverlayService { export interface OverlayStart { /** {@link OverlayBannersStart} */ banners: OverlayBannersStart; - openFlyout: ( - flyoutChildren: React.ReactNode, - flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } - ) => OverlayRef; - openModal: ( - modalChildren: React.ReactNode, - modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } - ) => OverlayRef; + /** {@link OverlayFlyoutStart#open} */ + openFlyout: OverlayFlyoutStart['open']; + /** {@link OverlayModalStart#open} */ + openModal: OverlayModalStart['open']; } diff --git a/packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js b/src/core/public/overlays/types.ts similarity index 56% rename from packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js rename to src/core/public/overlays/types.ts index deaf258418ed4..d5bd01c672d1f 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js +++ b/src/core/public/overlays/types.ts @@ -17,15 +17,23 @@ * under the License. */ -import _ from 'lodash'; -import { nodeTypes } from '../node_types'; - -export function convertGeoBoundingBox(filter) { - if (filter.meta.type !== 'geo_bounding_box') { - throw new Error(`Expected filter of type "geo_bounding_box", got "${filter.meta.type}"`); - } +/** + * Returned by {@link OverlayStart} methods for closing a mounted overlay. + * @public + */ +export interface OverlayRef { + /** + * A Promise that will resolve once this overlay is closed. + * + * Overlays can close from user interaction, calling `close()` on the overlay + * reference or another overlay replacing yours via `openModal` or `openFlyout`. + */ + onClose: Promise; - const { key, params } = filter.meta; - const camelParams = _.mapKeys(params, (value, key) => _.camelCase(key)); - return nodeTypes.function.buildNode('geoBoundingBox', key, camelParams); + /** + * Closes the referenced overlay if it's still open which in turn will + * resolve the `onClose` Promise. If the overlay had already been + * closed this method does nothing. + */ + close(): Promise; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1e97d8e066d09..7abbbcd32fbb8 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -124,7 +124,7 @@ export type ChromeHelpExtension = (element: HTMLDivElement) => () => void; // @public (undocumented) export interface ChromeNavControl { // (undocumented) - mount(targetDomElement: HTMLElement): () => void; + mount: MountPoint; // (undocumented) order?: number; } @@ -620,7 +620,7 @@ export interface LegacyNavLink { } // @public -export type MountPoint = (element: HTMLElement) => UnmountCallback; +export type MountPoint = (element: T) => UnmountCallback; // @public (undocumented) export interface NotificationsSetup { @@ -657,17 +657,14 @@ export interface OverlayRef { export interface OverlayStart { // (undocumented) banners: OverlayBannersStart; + // Warning: (ae-forgotten-export) The symbol "OverlayFlyoutStart" needs to be exported by the entry point index.d.ts + // // (undocumented) - openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; + openFlyout: OverlayFlyoutStart['open']; + // Warning: (ae-forgotten-export) The symbol "OverlayModalStart" needs to be exported by the entry point index.d.ts + // // (undocumented) - openModal: (modalChildren: React.ReactNode, modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; + openModal: OverlayModalStart['open']; } // @public (undocumented) @@ -941,9 +938,12 @@ export class ToastsApi implements IToasts { addSuccess(toastOrTitle: ToastInput): Toast; addWarning(toastOrTitle: ToastInput): Toast; get$(): Rx.Observable; - // @internal (undocumented) - registerOverlays(overlays: OverlayStart): void; remove(toastOrId: Toast | string): void; + // @internal (undocumented) + start({ overlays, i18n }: { + overlays: OverlayStart; + i18n: I18nStart; + }): void; } // @public (undocumented) diff --git a/src/core/public/types.ts b/src/core/public/types.ts index 4b12d5bc6da51..5abbb3c55813a 100644 --- a/src/core/public/types.ts +++ b/src/core/public/types.ts @@ -26,7 +26,7 @@ * * @public */ -export type MountPoint = (element: HTMLElement) => UnmountCallback; +export type MountPoint = (element: T) => UnmountCallback; /** * A function that will unmount the element previously mounted by diff --git a/src/core/public/utils/mount.test.tsx b/src/core/public/utils/mount.test.tsx new file mode 100644 index 0000000000000..1cacb7d6a796c --- /dev/null +++ b/src/core/public/utils/mount.test.tsx @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { MountWrapper, mountReactNode } from './mount'; + +describe('MountWrapper', () => { + it('renders an html element in react tree', () => { + const mountPoint = (container: HTMLElement) => { + const el = document.createElement('p'); + el.textContent = 'hello'; + el.className = 'bar'; + container.append(el); + return () => {}; + }; + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"

hello

"` + ); + }); + + it('updates the react tree when the mounted element changes', () => { + const el = document.createElement('p'); + el.textContent = 'initial'; + + const mountPoint = (container: HTMLElement) => { + container.append(el); + return () => {}; + }; + + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"

initial

"` + ); + + el.textContent = 'changed'; + container.update(); + expect(container.html()).toMatchInlineSnapshot( + `"

changed

"` + ); + }); + + it('can render a detached react component', () => { + const mountPoint = mountReactNode(detached); + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"
detached
"` + ); + }); + + it('accepts a className prop to override default className', () => { + const mountPoint = mountReactNode(detached); + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"
detached
"` + ); + }); +}); diff --git a/src/core/public/utils/mount.tsx b/src/core/public/utils/mount.tsx index dbd7d5da435a6..0fee67a6e7fbc 100644 --- a/src/core/public/utils/mount.tsx +++ b/src/core/public/utils/mount.tsx @@ -22,23 +22,26 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { MountPoint } from '../types'; +const defaultWrapperClass = 'kbnMountWrapper'; + /** * MountWrapper is a react component to mount a {@link MountPoint} inside a react tree. */ -export const MountWrapper: React.FunctionComponent<{ mount: MountPoint }> = ({ mount }) => { +export const MountWrapper: React.FunctionComponent<{ mount: MountPoint; className?: string }> = ({ + mount, + className = defaultWrapperClass, +}) => { const element = useRef(null); useEffect(() => mount(element.current!), [mount]); - return
; + return
; }; /** - * Mount converter for react components. + * Mount converter for react node. * - * @param component to get a mount for + * @param node to get a mount for */ -export const mountReactNode = (component: React.ReactNode): MountPoint => ( - element: HTMLElement -) => { - render({component}, element); +export const mountReactNode = (node: React.ReactNode): MountPoint => (element: HTMLElement) => { + render({node}, element); return () => unmountComponentAtNode(element); }; diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index abe9ec6d6e873..39ec1f78b65f0 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; +import { toMountPoint } from '../../../../../../plugins/kibana_react/public'; import { IAction, createAction, @@ -79,17 +80,19 @@ export function createFilterAction( const filterSelectionPromise: Promise = new Promise(resolve => { const overlay = overlays.openModal( - applyFiltersPopover( - filters, - indexPatterns, - () => { - overlay.close(); - resolve([]); - }, - (filterSelection: esFilters.Filter[]) => { - overlay.close(); - resolve(filterSelection); - } + toMountPoint( + applyFiltersPopover( + filters, + indexPatterns, + () => { + overlay.close(); + resolve([]); + }, + (filterSelection: esFilters.Filter[]) => { + overlay.close(); + resolve(filterSelection); + } + ) ), { 'data-test-subj': 'test', diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx index fee043764d8d7..39d4a80cdf540 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx @@ -19,7 +19,7 @@ import { EuiBadge, useInnerText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { FilterLabel } from '../filter_editor/lib/filter_label'; import { esFilters } from '../../../../../../../plugins/data/public'; @@ -29,7 +29,7 @@ interface Props { [propName: string]: any; } -export const FilterView: SFC = ({ +export const FilterView: FC = ({ filter, iconOnClick, onClick, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index d82b89339b0d0..457d8972876ae 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -35,7 +35,6 @@ import { docTitle } from 'ui/doc_title/doc_title'; import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { timefilter } from 'ui/timefilter'; @@ -55,6 +54,7 @@ import { SaveOptions } from 'ui/saved_objects/saved_object'; import { capabilities } from 'ui/capabilities'; import { Subscription } from 'rxjs'; import { npStart } from 'ui/new_platform'; +import { unhashUrl } from 'ui/state_management/state_hashing'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { Query } from '../../../../../plugins/data/public'; import { start as data } from '../../../data/public/legacy'; @@ -131,7 +131,6 @@ export class DashboardAppController { }) { const queryFilter = Private(FilterBarQueryFilterProvider); const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); let lastReloadRequestTime = 0; @@ -758,14 +757,13 @@ export class DashboardAppController { }); }; navActions[TopNavIds.SHARE] = anchorElement => { - showShareContextMenu({ + npStart.plugins.share.toggleShareContextMenu({ anchorElement, allowEmbed: true, allowShortUrl: !dashboardConfig.getHideWriteControls(), - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: dash.id, objectType: 'dashboard', - shareContextMenuExtensions: shareContextMenuExtensions.raw, sharingData: { title: dash.title, }, 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 8ee23bfb005a2..20a05e17d16d6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -50,7 +50,7 @@ import { migrateLegacyQuery, RequestAdapter, showSaveModal, - showShareContextMenu, + unhashUrl, stateMonitorFactory, subscribeWithScope, tabifyAggResponse, @@ -63,7 +63,7 @@ const { chrome, docTitle, FilterBarQueryFilterProvider, - ShareContextMenuExtensionsRegistryProvider, + share, StateProvider, timefilter, toastNotifications, @@ -190,7 +190,6 @@ function discoverController( ) { const responseHandler = vislibSeriesResponseHandlerProvider().handler; const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const queryFilter = Private(FilterBarQueryFilterProvider); @@ -323,14 +322,13 @@ function discoverController( testId: 'shareTopNavButton', run: async (anchorElement) => { const sharingData = await this.getSharingData(); - showShareContextMenu({ + share.toggleShareContextMenu({ anchorElement, allowEmbed: false, allowShortUrl: uiCapabilities.discover.createShortUrl, - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: savedSearch.id, objectType: 'search', - shareContextMenuExtensions, sharingData: { ...sharingData, title: savedSearch.title, diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index d0eb115e32676..61d7933464e7f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -36,7 +36,6 @@ import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { timefilter } from 'ui/timefilter'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; // @ts-ignore import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { wrapInI18nContext } from 'ui/i18n'; @@ -58,6 +57,7 @@ const services = { uiSettings: npStart.core.uiSettings, uiActions: npStart.plugins.uiActions, embeddable: npStart.plugins.embeddable, + share: npStart.plugins.share, // legacy docTitle, docViewsRegistry, @@ -68,7 +68,6 @@ const services = { SavedObjectRegistryProvider, SavedObjectProvider, SearchSource, - ShareContextMenuExtensionsRegistryProvider, StateProvider, timefilter, uiModules, @@ -99,7 +98,6 @@ export { RequestAdapter } from 'ui/inspector/adapters'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; export { FieldList } from 'ui/index_patterns'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { showShareContextMenu } from 'ui/share'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; // @ts-ignore @@ -110,6 +108,7 @@ export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; export { tabifyAggResponse } from 'ui/agg_response/tabify'; // @ts-ignore export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; +export { unhashUrl } from 'ui/state_management/state_hashing'; // EXPORT types export { Vis } from 'ui/vis'; diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/traefik.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/traefik.svg new file mode 100644 index 0000000000000..8ee3448f5626e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/traefik.svg @@ -0,0 +1,342 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 58a0075e94b99..619903e93c127 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -42,10 +42,10 @@ import { KibanaParsedUrl, migrateLegacyQuery, SavedObjectSaveModal, - showShareContextMenu, showSaveModal, stateMonitorFactory, subscribeWithScope, + unhashUrl, } from '../kibana_services'; const { @@ -56,12 +56,12 @@ const { docTitle, FilterBarQueryFilterProvider, getBasePath, - ShareContextMenuExtensionsRegistryProvider, toastNotifications, timefilter, uiModules, uiRoutes, visualizations, + share, } = getServices(); const { savedQueryService } = data.search.services; @@ -159,7 +159,6 @@ function VisEditor( ) { const queryFilter = Private(FilterBarQueryFilterProvider); const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; @@ -239,14 +238,13 @@ function VisEditor( run: (anchorElement) => { const hasUnappliedChanges = vis.dirty; const hasUnsavedChanges = $appStatus.dirty; - showShareContextMenu({ + share.toggleShareContextMenu({ anchorElement, allowEmbed: true, allowShortUrl: capabilities.visualize.createShortUrl, - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: savedVis.id, objectType: 'visualization', - shareContextMenuExtensions, sharingData: { title: savedVis.title, }, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 5c6d06b5eaeb6..3be49971cf4c9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -36,7 +36,6 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { uiModules } from 'ui/modules'; import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { timefilter } from 'ui/timefilter'; // Saved objects @@ -62,6 +61,7 @@ const services = { toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, + share: npStart.plugins.share, data, embeddables, visualizations, @@ -77,7 +77,6 @@ const services = { SavedObjectProvider, SavedObjectRegistryProvider, SavedObjectsClientProvider, - ShareContextMenuExtensionsRegistryProvider, timefilter, uiModules, uiRoutes, @@ -99,13 +98,13 @@ export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; // @ts-ignore export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { showShareContextMenu } from 'ui/share'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { unhashUrl } from 'ui/state_management/state_hashing'; export { Container, Embeddable, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index f1f65acf131ce..155d730ec3ede 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -78,6 +78,7 @@ import { couchdbMetricsSpecProvider } from './couchdb_metrics'; import { emsBoundariesSpecProvider } from './ems'; import { consulMetricsSpecProvider } from './consul_metrics'; import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; +import { traefikMetricsSpecProvider } from './traefik_metrics'; export function registerTutorials(server) { server.newPlatform.setup.plugins.home.tutorials.registerTutorial(systemLogsSpecProvider); @@ -142,4 +143,5 @@ export function registerTutorials(server) { server.registerTutorial(emsBoundariesSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(consulMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cockroachdbMetricsSpecProvider); + server.newPlatform.setup.plugins.home.tutorials.registerTutorial(traefikMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js new file mode 100644 index 0000000000000..d034ec82cd351 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js @@ -0,0 +1,55 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function traefikMetricsSpecProvider(server, context) { + const moduleName = 'traefik'; + return { + id: 'traefikMetrics', + name: i18n.translate('kbn.server.tutorials.traefikMetrics.nameTitle', { + defaultMessage: 'Traefik metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.traefikMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from Traefik.', + }), + longDescription: i18n.translate('kbn.server.tutorials.traefikMetrics.longDescription', { + defaultMessage: 'The `traefik` Metricbeat module fetches monitoring metrics from Traefik. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-traefik.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/traefik.svg', + artifacts: { + dashboards: [], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-traefik.html' + } + }, + completionTimeMinutes: 10, + // previewImagePath: '/plugins/kibana/home/tutorial_resources/traefik_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx index ff46b6ec34a86..10fb58783481d 100644 --- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx +++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx @@ -35,18 +35,18 @@ import { injectUICapabilities } from './inject_ui_capabilities'; import { UICapabilitiesProvider } from './ui_capabilities_provider'; describe('injectUICapabilities', () => { - it('provides UICapabilities to SFCs', () => { - interface SFCProps { + it('provides UICapabilities to FCs', () => { + interface FCProps { uiCapabilities: UICapabilities; } - const MySFC = injectUICapabilities(({ uiCapabilities }: SFCProps) => { + const MyFC = injectUICapabilities(({ uiCapabilities }: FCProps) => { return {uiCapabilities.uiCapability2.nestedProp}; }); const wrapper = mount( - + ); diff --git a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx index 76f1dd8016313..dbc7cd03e27a3 100644 --- a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx +++ b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx @@ -35,18 +35,18 @@ import { injectUICapabilities } from './inject_ui_capabilities'; import { UICapabilitiesProvider } from './ui_capabilities_provider'; describe('injectUICapabilities', () => { - it('provides UICapabilities to SFCs', () => { - interface SFCProps { + it('provides UICapabilities to FCs', () => { + interface FCProps { uiCapabilities: UICapabilities; } - const MySFC = injectUICapabilities(({ uiCapabilities }: SFCProps) => { + const MyFC = injectUICapabilities(({ uiCapabilities }: FCProps) => { return {uiCapabilities.uiCapability2.nestedProp}; }); const wrapper = mount( - + ); diff --git a/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx b/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx index 5e53477b8ec04..b02344ce6dd72 100644 --- a/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx +++ b/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx @@ -22,6 +22,7 @@ import { npStart } from 'ui/new_platform'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiTextAlign } from '@elastic/eui'; +import { toMountPoint } from '../../../../../../plugins/kibana_react/public'; import { ShardFailureModal } from './shard_failure_modal'; import { ResponseWithShardFailure, Request } from './shard_failure_types'; @@ -34,12 +35,14 @@ interface Props { export function ShardFailureOpenModalButton({ request, response, title }: Props) { function onClick() { const modal = npStart.core.overlays.openModal( - modal.close()} - />, + toMountPoint( + modal.close()} + /> + ), { className: 'shardFailureModal', } 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 e62ceeb2f3102..5c269c7b019aa 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 @@ -62,6 +62,9 @@ export const npSetup = { } }, }, + share: { + register: () => {}, + }, devTools: { register: () => {}, }, @@ -162,6 +165,9 @@ export const npStart = { }, }, }, + share: { + toggleShareContextMenu: () => {}, + }, inspector: { isAvailable: () => false, open: () => ({ diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 9ee5d8580a90b..6e71d36877895 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -33,6 +33,7 @@ import { FeatureCatalogueSetup, FeatureCatalogueStart, } from '../../../../plugins/feature_catalogue/public'; +import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; export interface PluginsSetup { data: ReturnType; @@ -41,6 +42,7 @@ export interface PluginsSetup { feature_catalogue: FeatureCatalogueSetup; inspector: InspectorSetup; uiActions: IUiActionsSetup; + share: SharePluginSetup; devTools: DevToolsSetup; } @@ -52,6 +54,7 @@ export interface PluginsStart { feature_catalogue: FeatureCatalogueStart; inspector: InspectorStart; uiActions: IUiActionsStart; + share: SharePluginStart; devTools: DevToolsStart; } diff --git a/src/legacy/ui/public/share/_index.scss b/src/legacy/ui/public/share/_index.scss index 192091fb04e3c..85168c9ea80f7 100644 --- a/src/legacy/ui/public/share/_index.scss +++ b/src/legacy/ui/public/share/_index.scss @@ -1 +1 @@ -@import './components/index'; +@import './share_context_menu'; diff --git a/src/legacy/ui/public/share/_share_context_menu.scss b/src/legacy/ui/public/share/_share_context_menu.scss new file mode 100644 index 0000000000000..a05164a6bb0d1 --- /dev/null +++ b/src/legacy/ui/public/share/_share_context_menu.scss @@ -0,0 +1,3 @@ +.kbnShareContextMenu__finalPanel { + padding: $euiSize; +} diff --git a/src/legacy/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap b/src/legacy/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap deleted file mode 100644 index df50f1d4a78ba..0000000000000 --- a/src/legacy/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap +++ /dev/null @@ -1,121 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`shareContextMenuExtensions should sort ascending on sort order first and then ascending on name 1`] = ` -, - "id": 1, - "title": "Permalink", - }, - Object { - "content":
- panel content -
, - "id": 2, - "title": "AAA panel", - }, - Object { - "content":
- panel content -
, - "id": 3, - "title": "ZZZ panel", - }, - Object { - "id": 4, - "items": Array [ - Object { - "data-test-subj": "sharePanel-Permalinks", - "icon": "link", - "name": "Permalinks", - "panel": 1, - }, - Object { - "data-test-subj": "sharePanel-ZZZpanel", - "name": "ZZZ panel", - "panel": 3, - }, - Object { - "data-test-subj": "sharePanel-AAApanel", - "name": "AAA panel", - "panel": 2, - }, - ], - "title": "Share this dashboard", - }, - ] - } -/> -`; - -exports[`should only render permalink panel when there are no other panels 1`] = ` -, - "id": 1, - "title": "Permalink", - }, - ] - } -/> -`; - -exports[`should render context menu panel when there are more than one panel 1`] = ` -, - "id": 1, - "title": "Permalink", - }, - Object { - "content": , - "id": 2, - "title": "Embed Code", - }, - Object { - "id": 3, - "items": Array [ - Object { - "data-test-subj": "sharePanel-Embedcode", - "icon": "console", - "name": "Embed code", - "panel": 2, - }, - Object { - "data-test-subj": "sharePanel-Permalinks", - "icon": "link", - "name": "Permalinks", - "panel": 1, - }, - ], - "title": "Share this dashboard", - }, - ] - } -/> -`; diff --git a/src/legacy/ui/public/share/components/__snapshots__/url_panel_content.test.js.snap b/src/legacy/ui/public/share/components/__snapshots__/url_panel_content.test.js.snap deleted file mode 100644 index 645b8c662c417..0000000000000 --- a/src/legacy/ui/public/share/components/__snapshots__/url_panel_content.test.js.snap +++ /dev/null @@ -1,431 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render 1`] = ` - - - } - label={ - - } - labelType="label" - > - - - - - - - } - position="bottom" - /> - - , - }, - Object { - "data-test-subj": "exportAsSavedObject", - "disabled": true, - "id": "savedObject", - "label": - - - - - - } - position="bottom" - /> - - , - }, - ] - } - /> - - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - - - - - - -`; - -exports[`should enable saved object export option when objectId is provided 1`] = ` - - - } - labelType="label" - > - - - - - - - } - position="bottom" - /> - - , - }, - Object { - "data-test-subj": "exportAsSavedObject", - "disabled": false, - "id": "savedObject", - "label": - - - - - - } - position="bottom" - /> - - , - }, - ] - } - /> - - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - - - - - - -`; - -exports[`should hide short url section when allowShortUrl is false 1`] = ` - - - } - labelType="label" - > - - - - - - - } - position="bottom" - /> - - , - }, - Object { - "data-test-subj": "exportAsSavedObject", - "disabled": false, - "id": "savedObject", - "label": - - - - - - } - position="bottom" - /> - - , - }, - ] - } - /> - - - - - - -`; diff --git a/src/legacy/ui/public/share/components/_index.scss b/src/legacy/ui/public/share/components/_index.scss deleted file mode 100644 index 85168c9ea80f7..0000000000000 --- a/src/legacy/ui/public/share/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './share_context_menu'; diff --git a/src/legacy/ui/public/share/components/_share_context_menu.scss b/src/legacy/ui/public/share/components/_share_context_menu.scss deleted file mode 100644 index d28e7846d813e..0000000000000 --- a/src/legacy/ui/public/share/components/_share_context_menu.scss +++ /dev/null @@ -1,8 +0,0 @@ -.kbnShareContextMenu__finalPanel { - padding: $euiSize; -} - -.kbnShareContextMenu__copyAnchor, -.kbnShareContextMenu__copyButton { - width: 100%; -} diff --git a/src/legacy/ui/public/share/components/share_context_menu.test.js b/src/legacy/ui/public/share/components/share_context_menu.test.js deleted file mode 100644 index 5b583420f85e9..0000000000000 --- a/src/legacy/ui/public/share/components/share_context_menu.test.js +++ /dev/null @@ -1,92 +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. - */ - -jest.mock('../lib/url_shortener', () => ({})); - -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; - -import { - ShareContextMenu, -} from './share_context_menu'; - -test('should render context menu panel when there are more than one panel', () => { - const component = shallowWithIntl( {}} - />); - expect(component).toMatchSnapshot(); -}); - -test('should only render permalink panel when there are no other panels', () => { - const component = shallowWithIntl( {}} - />); - expect(component).toMatchSnapshot(); -}); - -describe('shareContextMenuExtensions', () => { - const shareContextMenuExtensions = [ - { - getShareActions: () => { - return [ - { - panel: { - title: 'AAA panel', - content: (
panel content
), - }, - shareMenuItem: { - name: 'AAA panel', - sortOrder: 5, - } - } - ]; - } - }, - { - getShareActions: () => { - return [ - { - panel: { - title: 'ZZZ panel', - content: (
panel content
), - }, - shareMenuItem: { - name: 'ZZZ panel', - sortOrder: 0, - } - } - ]; - } - } - ]; - - test('should sort ascending on sort order first and then ascending on name', () => { - const component = shallowWithIntl( {}} - shareContextMenuExtensions={shareContextMenuExtensions} - />); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/legacy/ui/public/share/lib/url_shortener.test.js b/src/legacy/ui/public/share/lib/url_shortener.test.js deleted file mode 100644 index 859873bd4989f..0000000000000 --- a/src/legacy/ui/public/share/lib/url_shortener.test.js +++ /dev/null @@ -1,133 +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. - */ -jest.mock('ui/kfetch', () => ({})); - -jest.mock('../../chrome', () => ({})); - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import { shortenUrl } from './url_shortener'; - -describe('Url shortener', () => { - const shareId = 'id123'; - - let kfetchStub; - beforeEach(() => { - kfetchStub = sinon.stub(); - require('ui/kfetch').kfetch = async (...args) => { - return kfetchStub(...args); - }; - }); - - describe('Shorten without base path', () => { - beforeAll(() => { - require('../../chrome').getBasePath = () => { - return ''; - }; - }); - - it('should shorten urls with a port', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana#123"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl('http://localhost:5601/app/kibana#123'); - expect(shortUrl).to.be(`http://localhost:5601/goto/${shareId}`); - }); - - it('should shorten urls without a port', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana#123"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl('http://localhost/app/kibana#123'); - expect(shortUrl).to.be(`http://localhost/goto/${shareId}`); - }); - }); - - describe('Shorten with base path', () => { - const basePath = '/foo'; - - beforeAll(() => { - require('../../chrome').getBasePath = () => { - return basePath; - }; - }); - - it('should shorten urls with a port', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana#123"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`); - expect(shortUrl).to.be(`http://localhost:5601${basePath}/goto/${shareId}`); - }); - - it('should shorten urls without a port', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana#123"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana#123`); - expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`); - }); - - it('should shorten urls with a query string', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana?foo#123"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`); - expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`); - }); - - it('should shorten urls without a hash', async () => { - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana"}' - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana`); - expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`); - }); - - it('should shorten urls with a query string in the hash', async () => { - const relativeUrl = "/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"; //eslint-disable-line max-len, quotes - kfetchStub.withArgs({ - method: 'POST', - pathname: `/api/shorten_url`, - body: '{"url":"/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"}' //eslint-disable-line max-len, quotes - }).returns(Promise.resolve({ urlId: shareId })); - - const shortUrl = await shortenUrl(`http://localhost${basePath}${relativeUrl}`); - expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`); - }); - }); -}); diff --git a/src/legacy/ui/public/share/share_action.ts b/src/legacy/ui/public/share/share_action.ts deleted file mode 100644 index 5e524944d2094..0000000000000 --- a/src/legacy/ui/public/share/share_action.ts +++ /dev/null @@ -1,63 +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. - */ - -/* - * 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 mayexport - * 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 { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; - -export interface ShareActionProps { - objectType: string; - objectId?: string; - getUnhashableStates: () => object[]; - sharingData: any; - isDirty: boolean; - onClose: () => void; -} - -export interface ShareContextMenuPanelItem extends EuiContextMenuPanelItemDescriptor { - sortOrder: number; -} - -export interface ShareAction { - shareMenuItem: ShareContextMenuPanelItem; - panel: EuiContextMenuPanelDescriptor; -} - -export interface ShareActionProvider { - readonly id: string; - - getShareActions: (actionProps: ShareActionProps) => ShareAction[]; -} diff --git a/src/legacy/ui/public/share/show_share_context_menu.tsx b/src/legacy/ui/public/share/show_share_context_menu.tsx deleted file mode 100644 index 1b3da0c6dc060..0000000000000 --- a/src/legacy/ui/public/share/show_share_context_menu.tsx +++ /dev/null @@ -1,94 +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 React from 'react'; -import ReactDOM from 'react-dom'; - -import { EuiWrappingPopover } from '@elastic/eui'; -import { I18nContext } from 'ui/i18n'; -import { ShareContextMenu } from './components/share_context_menu'; -import { ShareActionProvider } from './share_action'; - -let isOpen = false; - -const container = document.createElement('div'); - -const onClose = () => { - ReactDOM.unmountComponentAtNode(container); - isOpen = false; -}; - -interface ShowProps { - anchorElement: any; - allowEmbed: boolean; - allowShortUrl: boolean; - getUnhashableStates: () => object[]; - objectId?: string; - objectType: string; - shareContextMenuExtensions?: ShareActionProvider[]; - sharingData: any; - isDirty: boolean; -} - -export function showShareContextMenu({ - anchorElement, - allowEmbed, - allowShortUrl, - getUnhashableStates, - objectId, - objectType, - shareContextMenuExtensions, - sharingData, - isDirty, -}: ShowProps) { - if (isOpen) { - onClose(); - return; - } - - isOpen = true; - - document.body.appendChild(container); - const element = ( - - - - - - ); - ReactDOM.render(element, container); -} diff --git a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx index cb354375e7a68..b30733760bbdf 100644 --- a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx +++ b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; import { CoreStart } from '../../../../core/public'; +import { toMountPoint } from '../../../../plugins/kibana_react/public'; import { ReplacePanelFlyout } from './replace_panel_flyout'; import { IEmbeddable, @@ -44,18 +45,20 @@ export async function openReplacePanelFlyout(options: { getEmbeddableFactories, } = options; const flyoutSession = core.overlays.openFlyout( - { - if (flyoutSession) { - flyoutSession.close(); - } - }} - panelToRemove={panelToRemove} - savedObjectsFinder={savedObjectFinder} - notifications={notifications} - getEmbeddableFactories={getEmbeddableFactories} - />, + toMountPoint( + { + if (flyoutSession) { + flyoutSession.close(); + } + }} + panelToRemove={panelToRemove} + savedObjectsFinder={savedObjectFinder} + notifications={notifications} + getEmbeddableFactories={getEmbeddableFactories} + /> + ), { 'data-test-subj': 'replacePanelFlyout', } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 8a8a2f44b7fd8..33be01529c631 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -25,7 +25,8 @@ import { TGetActionsCompatibleWithTrigger, IAction, } from '../ui_actions'; -import { CoreStart } from '../../../../../core/public'; +import { CoreStart, OverlayStart } from '../../../../../core/public'; +import { toMountPoint } from '../../../../kibana_react/public'; import { Start as InspectorStartContract } from '../inspector'; import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from '../triggers'; @@ -200,17 +201,19 @@ export class EmbeddablePanel extends React.Component { embeddable: this.props.embeddable, }); - const createGetUserData = (overlays: CoreStart['overlays']) => + const createGetUserData = (overlays: OverlayStart) => async function getUserData(context: { embeddable: IEmbeddable }) { return new Promise<{ title: string | undefined }>(resolve => { const session = overlays.openModal( - { - session.close(); - resolve({ title }); - }} - />, + toMountPoint( + { + session.close(); + resolve({ title }); + }} + /> + ), { 'data-test-subj': 'customizePanel', } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts index 3ca3a0864d9f1..9ecc4686c21b6 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts @@ -18,8 +18,7 @@ */ import { i18n } from '@kbn/i18n'; import { IAction } from 'src/plugins/ui_actions/public'; -import { NotificationsStart } from 'src/core/public'; -import { KibanaReactOverlays } from 'src/plugins/kibana_react/public'; +import { NotificationsStart, OverlayStart } from 'src/core/public'; import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types'; import { openAddPanelFlyout } from './open_add_panel_flyout'; import { IContainer } from '../../../../containers'; @@ -37,7 +36,7 @@ export class AddPanelAction implements IAction { constructor( private readonly getFactory: GetEmbeddableFactory, private readonly getAllFactories: GetEmbeddableFactories, - private readonly overlays: KibanaReactOverlays, + private readonly overlays: OverlayStart, private readonly notifications: NotificationsStart, private readonly SavedObjectFinder: React.ComponentType ) {} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index bfa4f6e31d84e..481693501066c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -17,8 +17,8 @@ * under the License. */ import React from 'react'; -import { NotificationsStart } from 'src/core/public'; -import { KibanaReactOverlays } from 'src/plugins/kibana_react/public'; +import { NotificationsStart, OverlayStart } from 'src/core/public'; +import { toMountPoint } from '../../../../../../../kibana_react/public'; import { IContainer } from '../../../../containers'; import { AddPanelFlyout } from './add_panel_flyout'; import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types'; @@ -27,7 +27,7 @@ export async function openAddPanelFlyout(options: { embeddable: IContainer; getFactory: GetEmbeddableFactory; getAllFactories: GetEmbeddableFactories; - overlays: KibanaReactOverlays; + overlays: OverlayStart; notifications: NotificationsStart; SavedObjectFinder: React.ComponentType; }) { @@ -40,18 +40,20 @@ export async function openAddPanelFlyout(options: { SavedObjectFinder, } = options; const flyoutSession = overlays.openFlyout( - { - if (flyoutSession) { - flyoutSession.close(); - } - }} - getFactory={getFactory} - getAllFactories={getAllFactories} - notifications={notifications} - SavedObjectFinder={SavedObjectFinder} - />, + toMountPoint( + { + if (flyoutSession) { + flyoutSession.close(); + } + }} + getFactory={getFactory} + getAllFactories={getAllFactories} + notifications={notifications} + SavedObjectFinder={SavedObjectFinder} + /> + ), { 'data-test-subj': 'addPanelFlyout', } diff --git a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx index fc20a99987484..502269d7ac193 100644 --- a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { EuiFlyoutBody } from '@elastic/eui'; import { createAction, IncompatibleActionError } from '../../ui_actions'; import { CoreStart } from '../../../../../../core/public'; +import { toMountPoint } from '../../../../../kibana_react/public'; import { Embeddable, EmbeddableInput } from '../../embeddables'; import { GetMessageModal } from './get_message_modal'; import { FullNameEmbeddableOutput, hasFullNameOutput } from './say_hello_action'; @@ -38,7 +39,7 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) { const greeting = `Hello, ${context.embeddable.getOutput().fullName}`; const content = message ? `${greeting}. ${message}` : greeting; - overlays.openFlyout({content}); + overlays.openFlyout(toMountPoint({content})); }; return createAction({ @@ -51,13 +52,15 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) { } const modal = overlays.openModal( - modal.close()} - onDone={message => { - modal.close(); - sendMessage(context, message); - }} - /> + toMountPoint( + modal.close()} + onDone={message => { + modal.close(); + sendMessage(context, message); + }} + /> + ) ); }, }); diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx index 962cddfa3735f..d1eea5d67fb41 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; import { CoreStart } from 'src/core/public'; +import { toMountPoint } from '../../../../../../kibana_react/public'; import { EmbeddableFactory } from '../../../embeddables'; import { Container } from '../../../containers'; import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable'; @@ -54,16 +55,18 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory> { return new Promise(resolve => { const modalSession = this.overlays.openModal( - { - modalSession.close(); - resolve(undefined); - }} - onCreate={(input: { firstName: string; lastName?: string }) => { - modalSession.close(); - resolve(input); - }} - />, + toMountPoint( + { + modalSession.close(); + resolve(undefined); + }} + onCreate={(input: { firstName: string; lastName?: string }) => { + modalSession.close(); + resolve(input); + }} + /> + ), { 'data-test-subj': 'createContactCardEmbeddable', } diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx index 00714c5a8205c..cc9f2404d802f 100644 --- a/src/plugins/inspector/public/plugin.tsx +++ b/src/plugins/inspector/public/plugin.tsx @@ -20,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import * as React from 'react'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { toMountPoint } from '../../kibana_react/public'; import { InspectorViewRegistry } from './view_registry'; import { Adapters, InspectorOptions, InspectorSession } from './types'; import { InspectorPanel } from './ui/inspector_panel'; @@ -99,7 +100,7 @@ export class InspectorPublicPlugin implements Plugin { } return core.overlays.openFlyout( - , + toMountPoint(), { 'data-test-subj': 'inspectorPanel', closeButtonAriaLabel: closeButtonLabel, diff --git a/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx b/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx index b10cbbc87be7c..c4981c72f1a78 100644 --- a/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx +++ b/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx @@ -48,13 +48,11 @@ test('can open flyout with React element', () => { overlays.openFlyout(
foo
); expect(coreOverlays.openFlyout).toHaveBeenCalledTimes(1); - expect(coreOverlays.openFlyout.mock.calls[0][0]).toMatchInlineSnapshot(` - -
- foo -
-
- `); + + const container = document.createElement('div'); + const mount = coreOverlays.openFlyout.mock.calls[0][0]; + mount(container); + expect(container.innerHTML).toMatchInlineSnapshot(`"
foo
"`); }); test('can open modal with React element', () => { @@ -68,13 +66,10 @@ test('can open modal with React element', () => { overlays.openModal(
bar
); expect(coreOverlays.openModal).toHaveBeenCalledTimes(1); - expect(coreOverlays.openModal.mock.calls[0][0]).toMatchInlineSnapshot(` - -
- bar -
-
- `); + const container = document.createElement('div'); + const mount = coreOverlays.openModal.mock.calls[0][0]; + mount(container); + expect(container.innerHTML).toMatchInlineSnapshot(`"
bar
"`); }); test('passes through flyout options when opening flyout', () => { diff --git a/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx b/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx index a62c0970cf525..6d7b34128f3fe 100644 --- a/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx +++ b/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { KibanaServices } from '../context/types'; import { KibanaReactOverlays } from './types'; +import { toMountPoint } from '../util'; export const createReactOverlays = (services: KibanaServices): KibanaReactOverlays => { const checkCoreService = () => { @@ -30,12 +31,12 @@ export const createReactOverlays = (services: KibanaServices): KibanaReactOverla const openFlyout: KibanaReactOverlays['openFlyout'] = (node, options?) => { checkCoreService(); - return services.overlays!.openFlyout(<>{node}, options); + return services.overlays!.openFlyout(toMountPoint(<>{node}), options); }; const openModal: KibanaReactOverlays['openModal'] = (node, options?) => { checkCoreService(); - return services.overlays!.openModal(<>{node}, options); + return services.overlays!.openModal(toMountPoint(<>{node}), options); }; const overlays: KibanaReactOverlays = { diff --git a/src/plugins/kibana_react/public/overlays/types.ts b/src/plugins/kibana_react/public/overlays/types.ts index 0108d8eaba6cc..9822e80376d94 100644 --- a/src/plugins/kibana_react/public/overlays/types.ts +++ b/src/plugins/kibana_react/public/overlays/types.ts @@ -27,6 +27,6 @@ export interface KibanaReactOverlays { ) => ReturnType; openModal: ( node: React.ReactNode, - options?: Parameters['1'] + options?: Parameters['1'] ) => ReturnType; } diff --git a/src/plugins/share/README.md b/src/plugins/share/README.md new file mode 100644 index 0000000000000..7ecf23134cf24 --- /dev/null +++ b/src/plugins/share/README.md @@ -0,0 +1,24 @@ +# Share plugin + +Replaces the legacy `ui/share` module for registering share context menus. + +## Example registration + +```ts +// For legacy plugins +import { npSetup } from 'ui/new_platform'; +npSetup.plugins.share.register(/* same details here */); + +// For new plugins: first add 'share' to the list of `optionalPlugins` +// in your kibana.json file. Then access the plugin directly in `setup`: + +class MyPlugin { + setup(core, plugins) { + if (plugins.share) { + plugins.share.register(/* same details here. */); + } + } +} +``` + +Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json new file mode 100644 index 0000000000000..bbe393a76c5da --- /dev/null +++ b/src/plugins/share/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "share", + "version": "kibana", + "server": false, + "ui": true +} diff --git a/src/plugins/share/public/components/__snapshots__/share_context_menu.test.tsx.snap b/src/plugins/share/public/components/__snapshots__/share_context_menu.test.tsx.snap new file mode 100644 index 0000000000000..fc3fa3e72b9c0 --- /dev/null +++ b/src/plugins/share/public/components/__snapshots__/share_context_menu.test.tsx.snap @@ -0,0 +1,135 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`shareContextMenuExtensions should sort ascending on sort order first and then ascending on name 1`] = ` + + , + "id": 1, + "title": "Permalink", + }, + Object { + "content":
+ panel content +
, + "id": 2, + "title": "AAA panel", + }, + Object { + "content":
+ panel content +
, + "id": 3, + "title": "ZZZ panel", + }, + Object { + "id": 4, + "items": Array [ + Object { + "data-test-subj": "sharePanel-Permalinks", + "icon": "link", + "name": "Permalinks", + "panel": 1, + }, + Object { + "data-test-subj": "sharePanel-ZZZpanel", + "name": "ZZZ panel", + "panel": 3, + }, + Object { + "data-test-subj": "sharePanel-AAApanel", + "name": "AAA panel", + "panel": 2, + }, + ], + "title": "Share this dashboard", + }, + ] + } + /> +
+`; + +exports[`should only render permalink panel when there are no other panels 1`] = ` + + , + "id": 1, + "title": "Permalink", + }, + ] + } + /> + +`; + +exports[`should render context menu panel when there are more than one panel 1`] = ` + + , + "id": 1, + "title": "Permalink", + }, + Object { + "content": , + "id": 2, + "title": "Embed Code", + }, + Object { + "id": 3, + "items": Array [ + Object { + "data-test-subj": "sharePanel-Embedcode", + "icon": "console", + "name": "Embed code", + "panel": 2, + }, + Object { + "data-test-subj": "sharePanel-Permalinks", + "icon": "link", + "name": "Permalinks", + "panel": 1, + }, + ], + "title": "Share this dashboard", + }, + ] + } + /> + +`; diff --git a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap new file mode 100644 index 0000000000000..c10ca55130880 --- /dev/null +++ b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap @@ -0,0 +1,437 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render 1`] = ` + + + + } + label={ + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": true, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + + +`; + +exports[`should enable saved object export option when objectId is provided 1`] = ` + + + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": false, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + + +`; + +exports[`should hide short url section when allowShortUrl is false 1`] = ` + + + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": false, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + + +`; diff --git a/src/plugins/share/public/components/share_context_menu.test.tsx b/src/plugins/share/public/components/share_context_menu.test.tsx new file mode 100644 index 0000000000000..7fb0449ead502 --- /dev/null +++ b/src/plugins/share/public/components/share_context_menu.test.tsx @@ -0,0 +1,87 @@ +/* + * 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 { ShareMenuItem } from '../types'; + +jest.mock('../lib/url_shortener', () => ({})); + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ShareContextMenu } from './share_context_menu'; + +const defaultProps = { + allowEmbed: true, + allowShortUrl: false, + shareMenuItems: [], + sharingData: null, + isDirty: false, + onClose: () => {}, + basePath: '', + post: () => Promise.resolve(), + objectType: 'dashboard', +}; + +test('should render context menu panel when there are more than one panel', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); +}); + +test('should only render permalink panel when there are no other panels', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); +}); + +describe('shareContextMenuExtensions', () => { + const shareContextMenuItems: ShareMenuItem[] = [ + { + panel: { + id: '1', + title: 'AAA panel', + content:
panel content
, + }, + shareMenuItem: { + name: 'AAA panel', + sortOrder: 5, + }, + }, + { + panel: { + id: '2', + title: 'ZZZ panel', + content:
panel content
, + }, + shareMenuItem: { + name: 'ZZZ panel', + sortOrder: 0, + }, + }, + ]; + + test('should sort ascending on sort order first and then ascending on name', () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/ui/public/share/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx similarity index 63% rename from src/legacy/ui/public/share/components/share_context_menu.tsx rename to src/plugins/share/public/components/share_context_menu.tsx index 5d5c80f10e1d5..8676a07bc10c4 100644 --- a/src/legacy/ui/public/share/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -19,47 +19,50 @@ import React, { Component } from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiContextMenu } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { ShareAction, ShareActionProvider, ShareContextMenuPanelItem } from 'ui/share/share_action'; +import { HttpStart } from 'kibana/public'; + import { UrlPanelContent } from './url_panel_content'; +import { ShareMenuItem, ShareContextMenuPanelItem } from '../types'; interface Props { allowEmbed: boolean; allowShortUrl: boolean; objectId?: string; objectType: string; - getUnhashableStates: () => object[]; - shareContextMenuExtensions?: ShareActionProvider[]; + shareableUrl?: string; + shareMenuItems: ShareMenuItem[]; sharingData: any; - isDirty: boolean; onClose: () => void; - intl: InjectedIntl; + basePath: string; + post: HttpStart['post']; } -class ShareContextMenuUI extends Component { +export class ShareContextMenu extends Component { public render() { const { panels, initialPanelId } = this.getPanels(); return ( - + + + ); } private getPanels = () => { const panels: EuiContextMenuPanelDescriptor[] = []; const menuItems: ShareContextMenuPanelItem[] = []; - const { intl } = this.props; const permalinkPanel = { id: panels.length + 1, - title: intl.formatMessage({ - id: 'common.ui.share.contextMenu.permalinkPanelTitle', + title: i18n.translate('share.contextMenu.permalinkPanelTitle', { defaultMessage: 'Permalink', }), content: ( @@ -67,13 +70,14 @@ class ShareContextMenuUI extends Component { allowShortUrl={this.props.allowShortUrl} objectId={this.props.objectId} objectType={this.props.objectType} - getUnhashableStates={this.props.getUnhashableStates} + basePath={this.props.basePath} + post={this.props.post} + shareableUrl={this.props.shareableUrl} /> ), }; menuItems.push({ - name: intl.formatMessage({ - id: 'common.ui.share.contextMenu.permalinksLabel', + name: i18n.translate('share.contextMenu.permalinksLabel', { defaultMessage: 'Permalinks', }), icon: 'link', @@ -85,8 +89,7 @@ class ShareContextMenuUI extends Component { if (this.props.allowEmbed) { const embedPanel = { id: panels.length + 1, - title: intl.formatMessage({ - id: 'common.ui.share.contextMenu.embedCodePanelTitle', + title: i18n.translate('share.contextMenu.embedCodePanelTitle', { defaultMessage: 'Embed Code', }), content: ( @@ -95,14 +98,15 @@ class ShareContextMenuUI extends Component { isEmbedded objectId={this.props.objectId} objectType={this.props.objectType} - getUnhashableStates={this.props.getUnhashableStates} + basePath={this.props.basePath} + post={this.props.post} + shareableUrl={this.props.shareableUrl} /> ), }; panels.push(embedPanel); menuItems.push({ - name: intl.formatMessage({ - id: 'common.ui.share.contextMenu.embedCodeLabel', + name: i18n.translate('share.contextMenu.embedCodeLabel', { defaultMessage: 'Embed code', }), icon: 'console', @@ -111,51 +115,27 @@ class ShareContextMenuUI extends Component { }); } - if (this.props.shareContextMenuExtensions) { - const { - objectType, - objectId, - getUnhashableStates, - sharingData, - isDirty, - onClose, - } = this.props; - this.props.shareContextMenuExtensions.forEach((provider: ShareActionProvider) => { - provider - .getShareActions({ - objectType, - objectId, - getUnhashableStates, - sharingData, - isDirty, - onClose, - }) - .forEach(({ shareMenuItem, panel }: ShareAction) => { - const panelId = panels.length + 1; - panels.push({ - ...panel, - id: panelId, - }); - menuItems.push({ - ...shareMenuItem, - panel: panelId, - }); - }); + this.props.shareMenuItems.forEach(({ shareMenuItem, panel }) => { + const panelId = panels.length + 1; + panels.push({ + ...panel, + id: panelId, }); - } + menuItems.push({ + ...shareMenuItem, + panel: panelId, + }); + }); if (menuItems.length > 1) { const topLevelMenuPanel = { id: panels.length + 1, - title: intl.formatMessage( - { - id: 'common.ui.share.contextMenuTitle', - defaultMessage: 'Share this {objectType}', - }, - { + title: i18n.translate('share.contextMenuTitle', { + defaultMessage: 'Share this {objectType}', + values: { objectType: this.props.objectType, - } - ), + }, + }), items: menuItems // Sorts ascending on sort order first and then ascending on name .sort((a, b) => { @@ -186,5 +166,3 @@ class ShareContextMenuUI extends Component { return { panels, initialPanelId }; }; } - -export const ShareContextMenu = injectI18n(ShareContextMenuUI); diff --git a/src/legacy/ui/public/share/components/url_panel_content.test.js b/src/plugins/share/public/components/url_panel_content.test.tsx similarity index 64% rename from src/legacy/ui/public/share/components/url_panel_content.test.js rename to src/plugins/share/public/components/url_panel_content.test.tsx index 8dd94202fe726..9da1a23641ab8 100644 --- a/src/legacy/ui/public/share/components/url_panel_content.test.js +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -20,37 +20,30 @@ jest.mock('../lib/url_shortener', () => ({})); import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; -import { - UrlPanelContent, -} from './url_panel_content'; +import { UrlPanelContent } from './url_panel_content'; + +const defaultProps = { + allowShortUrl: true, + objectType: 'dashboard', + basePath: '', + post: () => Promise.resolve(), +}; test('render', () => { - const component = shallowWithIntl( {}} - />); + const component = shallow(); expect(component).toMatchSnapshot(); }); test('should enable saved object export option when objectId is provided', () => { - const component = shallowWithIntl( {}} - />); + const component = shallow(); expect(component).toMatchSnapshot(); }); test('should hide short url section when allowShortUrl is false', () => { - const component = shallowWithIntl( {}} - />); + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); diff --git a/src/legacy/ui/public/share/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx similarity index 72% rename from src/legacy/ui/public/share/components/url_panel_content.tsx rename to src/plugins/share/public/components/url_panel_content.tsx index 159767a9eeb29..d0d4ce55dc1ac 100644 --- a/src/legacy/ui/public/share/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -31,24 +31,25 @@ import { EuiLoadingSpinner, EuiRadioGroup, EuiSwitch, + EuiSwitchEvent, } from '@elastic/eui'; import { format as formatUrl, parse as parseUrl } from 'url'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { unhashUrl } from '../../state_management/state_hashing'; -import { shortenUrl } from '../lib/url_shortener'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { HttpStart } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; -// TODO: Remove once EuiIconTip supports "content" prop -const FixedEuiIconTip = EuiIconTip as React.FC; +import { shortenUrl } from '../lib/url_shortener'; interface Props { allowShortUrl: boolean; isEmbedded?: boolean; objectId?: string; objectType: string; - getUnhashableStates: () => object[]; - intl: InjectedIntl; + shareableUrl?: string; + basePath: string; + post: HttpStart['post']; } enum ExportUrlAsType { @@ -64,7 +65,7 @@ interface State { shortUrlErrorMsg?: string; } -class UrlPanelContentUI extends Component { +export class UrlPanelContent extends Component { private mounted?: boolean; private shortUrlCache?: string; @@ -96,41 +97,41 @@ class UrlPanelContentUI extends Component { public render() { return ( - - {this.renderExportAsRadioGroup()} - - {this.renderShortUrlSwitch()} - - - - - {(copy: () => void) => ( - - {this.props.isEmbedded ? ( - - ) : ( - - )} - - )} - - + + + {this.renderExportAsRadioGroup()} + + {this.renderShortUrlSwitch()} + + + + + {(copy: () => void) => ( + + {this.props.isEmbedded ? ( + + ) : ( + + )} + + )} + + + ); } @@ -155,11 +156,9 @@ class UrlPanelContentUI extends Component { return; } - const url = window.location.href; - // Replace hashes with original RISON values. - const unhashedUrl = unhashUrl(url, this.props.getUnhashableStates()); + const url = this.getSnapshotUrl(); - const parsedUrl = parseUrl(unhashedUrl); + const parsedUrl = parseUrl(url); if (!parsedUrl || !parsedUrl.hash) { return; } @@ -184,9 +183,7 @@ class UrlPanelContentUI extends Component { }; private getSnapshotUrl = () => { - const url = window.location.href; - // Replace hashes with original RISON values. - return unhashUrl(url, this.props.getUnhashableStates()); + return this.props.shareableUrl || window.location.href; }; private makeUrlEmbeddable = (url: string) => { @@ -233,8 +230,7 @@ class UrlPanelContentUI extends Component { ); }; - // TODO: switch evt type to ChangeEvent once https://github.com/elastic/eui/issues/1134 is resolved - private handleShortUrlChange = async (evt: any) => { + private handleShortUrlChange = async (evt: EuiSwitchEvent) => { const isChecked = evt.target.checked; if (!isChecked || this.shortUrlCache !== undefined) { @@ -249,7 +245,10 @@ class UrlPanelContentUI extends Component { }); try { - const shortUrl = await shortenUrl(this.getSnapshotUrl()); + const shortUrl = await shortenUrl(this.getSnapshotUrl(), { + basePath: this.props.basePath, + post: this.props.post, + }); if (this.mounted) { this.shortUrlCache = shortUrl; this.setState( @@ -267,15 +266,12 @@ class UrlPanelContentUI extends Component { { useShortUrl: false, isCreatingShortUrl: false, - shortUrlErrorMsg: this.props.intl.formatMessage( - { - id: 'common.ui.share.urlPanel.unableCreateShortUrlErrorMessage', - defaultMessage: 'Unable to create short URL. Error: {errorMessage}', - }, - { + shortUrlErrorMsg: i18n.translate('share.urlPanel.unableCreateShortUrlErrorMessage', { + defaultMessage: 'Unable to create short URL. Error: {errorMessage}', + values: { errorMessage: fetchError.message, - } - ), + }, + }), }, this.setUrl ); @@ -288,12 +284,9 @@ class UrlPanelContentUI extends Component { { id: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT, label: this.renderWithIconTip( + , , - { id: ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT, disabled: this.isNotSaved(), label: this.renderWithIconTip( + , , - @@ -325,7 +315,7 @@ class UrlPanelContentUI extends Component { {child} - + ); @@ -334,7 +324,7 @@ class UrlPanelContentUI extends Component { private renderExportAsRadioGroup = () => { const generateLinkAsHelp = this.isNotSaved() ? ( @@ -345,7 +335,7 @@ class UrlPanelContentUI extends Component { } @@ -368,7 +358,7 @@ class UrlPanelContentUI extends Component { return; } const shortUrlLabel = ( - + ); const switchLabel = this.state.isCreatingShortUrl ? ( @@ -387,7 +377,7 @@ class UrlPanelContentUI extends Component { ); const tipContent = ( { + const shareId = 'id123'; + + let postStub: jest.Mock; + beforeEach(() => { + postStub = jest.fn(() => Promise.resolve({ urlId: shareId })); + }); + + describe('Shorten without base path', () => { + it('should shorten urls with a port', async () => { + const shortUrl = await shortenUrl('http://localhost:5601/app/kibana#123', { + basePath: '', + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost:5601/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana#123"}', + }); + }); + + it('should shorten urls without a port', async () => { + const shortUrl = await shortenUrl('http://localhost/app/kibana#123', { + basePath: '', + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana#123"}', + }); + }); + }); + + describe('Shorten with base path', () => { + const basePath = '/foo'; + + it('should shorten urls with a port', async () => { + const shortUrl = await shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`, { + basePath, + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost:5601${basePath}/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana#123"}', + }); + }); + + it('should shorten urls without a port', async () => { + const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana#123`, { + basePath, + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana#123"}', + }); + }); + + it('should shorten urls with a query string', async () => { + const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`, { + basePath, + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana?foo#123"}', + }); + }); + + it('should shorten urls without a hash', async () => { + const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana`, { + basePath, + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: '{"url":"/app/kibana"}', + }); + }); + + it('should shorten urls with a query string in the hash', async () => { + const relativeUrl = + '/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))'; + const shortUrl = await shortenUrl(`http://localhost${basePath}${relativeUrl}`, { + basePath, + post: postStub, + }); + expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`); + expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, { + body: + '{"url":"/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"}', + }); + }); + }); +}); diff --git a/src/legacy/ui/public/share/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts similarity index 83% rename from src/legacy/ui/public/share/lib/url_shortener.ts rename to src/plugins/share/public/lib/url_shortener.ts index 037214bd9b450..29d91bdb1aae6 100644 --- a/src/legacy/ui/public/share/lib/url_shortener.ts +++ b/src/plugins/share/public/lib/url_shortener.ts @@ -17,13 +17,13 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; import url from 'url'; -import chrome from '../../chrome'; - -export async function shortenUrl(absoluteUrl: string) { - const basePath = chrome.getBasePath(); +import { HttpStart } from 'kibana/public'; +export async function shortenUrl( + absoluteUrl: string, + { basePath, post }: { basePath: string; post: HttpStart['post'] } +) { const parsedUrl = url.parse(absoluteUrl); if (!parsedUrl || !parsedUrl.path) { return; @@ -34,7 +34,7 @@ export async function shortenUrl(absoluteUrl: string) { const body = JSON.stringify({ url: relativeUrl }); - const resp = await kfetch({ method: 'POST', pathname: '/api/shorten_url', body }); + const resp = await post('/api/shorten_url', { body }); return url.format({ protocol: parsedUrl.protocol, host: parsedUrl.host, diff --git a/packages/kbn-es-query/src/kuery/filter_migration/phrase.js b/src/plugins/share/public/plugin.test.mocks.ts similarity index 66% rename from packages/kbn-es-query/src/kuery/filter_migration/phrase.js rename to src/plugins/share/public/plugin.test.mocks.ts index 49cde8aa97448..bd814ebc2500b 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/phrase.js +++ b/src/plugins/share/public/plugin.test.mocks.ts @@ -17,13 +17,12 @@ * under the License. */ -import { nodeTypes } from '../node_types'; +import { shareMenuRegistryMock } from './services/share_menu_registry.mock'; +import { shareMenuManagerMock } from './services/share_menu_manager.mock'; -export function convertPhraseFilter(filter) { - if (filter.meta.type !== 'phrase') { - throw new Error(`Expected filter of type "phrase", got "${filter.meta.type}"`); - } - - const { key, params } = filter.meta; - return nodeTypes.function.buildNode('is', key, params.query); -} +export const registryMock = shareMenuRegistryMock.create(); +export const managerMock = shareMenuManagerMock.create(); +jest.doMock('./services', () => ({ + ShareMenuRegistry: jest.fn(() => registryMock), + ShareMenuManager: jest.fn(() => managerMock), +})); diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts new file mode 100644 index 0000000000000..5610490be33b3 --- /dev/null +++ b/src/plugins/share/public/plugin.test.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 { registryMock, managerMock } from './plugin.test.mocks'; +import { SharePlugin } from './plugin'; +import { CoreStart } from 'kibana/public'; + +describe('SharePlugin', () => { + beforeEach(() => { + managerMock.start.mockClear(); + registryMock.setup.mockClear(); + registryMock.start.mockClear(); + }); + + describe('setup', () => { + test('wires up and returns registry', async () => { + const setup = await new SharePlugin().setup(); + expect(registryMock.setup).toHaveBeenCalledWith(); + expect(setup.register).toBeDefined(); + }); + }); + + describe('start', () => { + test('wires up and returns show function, but not registry', async () => { + const service = new SharePlugin(); + await service.setup(); + const start = await service.start({} as CoreStart); + expect(registryMock.start).toHaveBeenCalled(); + expect(managerMock.start).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ getShareMenuItems: expect.any(Function) }) + ); + expect(start.toggleShareContextMenu).toBeDefined(); + }); + }); +}); diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts new file mode 100644 index 0000000000000..6d78211cf9954 --- /dev/null +++ b/src/plugins/share/public/plugin.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 { CoreStart, Plugin } from 'src/core/public'; +import { ShareMenuManager, ShareMenuManagerStart } from './services'; +import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; + +export class SharePlugin implements Plugin { + private readonly shareMenuRegistry = new ShareMenuRegistry(); + private readonly shareContextMenu = new ShareMenuManager(); + + public async setup() { + return { + ...this.shareMenuRegistry.setup(), + }; + } + + public async start(core: CoreStart) { + return { + ...this.shareContextMenu.start(core, this.shareMenuRegistry.start()), + }; + } +} + +/** @public */ +export type SharePluginSetup = ShareMenuRegistrySetup; + +/** @public */ +export type SharePluginStart = ShareMenuManagerStart; diff --git a/packages/kbn-es-query/src/kuery/filter_migration/index.js b/src/plugins/share/public/services/index.ts similarity index 91% rename from packages/kbn-es-query/src/kuery/filter_migration/index.js rename to src/plugins/share/public/services/index.ts index f0dbb8f0a6a6a..aebb81df9e968 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/index.js +++ b/src/plugins/share/public/services/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { filterToKueryAST } from './filter_to_kuery'; +export * from './share_menu_registry'; +export * from './share_menu_manager'; diff --git a/packages/kbn-es-query/src/kuery/filter_migration/geo_polygon.js b/src/plugins/share/public/services/share_menu_manager.mock.ts similarity index 60% rename from packages/kbn-es-query/src/kuery/filter_migration/geo_polygon.js rename to src/plugins/share/public/services/share_menu_manager.mock.ts index 33701a0e4d856..7104abeb26090 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/geo_polygon.js +++ b/src/plugins/share/public/services/share_menu_manager.mock.ts @@ -17,13 +17,24 @@ * under the License. */ -import { nodeTypes } from '../node_types'; +import { ShareMenuManager, ShareMenuManagerStart } from './share_menu_manager'; -export function convertGeoPolygon(filter) { - if (filter.meta.type !== 'geo_polygon') { - throw new Error(`Expected filter of type "geo_polygon", got "${filter.meta.type}"`); - } +const createStartMock = (): jest.Mocked => { + const start = { + toggleShareContextMenu: jest.fn(), + }; + return start; +}; - const { key, params: { points } } = filter.meta; - return nodeTypes.function.buildNode('geoPolygon', key, points); -} +const createMock = (): jest.Mocked> => { + const service = { + start: jest.fn(), + }; + service.start.mockImplementation(createStartMock); + return service; +}; + +export const shareMenuManagerMock = { + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx new file mode 100644 index 0000000000000..35116efa85961 --- /dev/null +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -0,0 +1,112 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiWrappingPopover } from '@elastic/eui'; + +import { CoreStart, HttpStart } from 'kibana/public'; +import { ShareContextMenu } from '../components/share_context_menu'; +import { ShareMenuItem, ShowShareMenuOptions } from '../types'; +import { ShareMenuRegistryStart } from './share_menu_registry'; + +export class ShareMenuManager { + private isOpen = false; + + private container = document.createElement('div'); + + start(core: CoreStart, shareRegistry: ShareMenuRegistryStart) { + return { + /** + * Collects share menu items from registered providers and mounts the share context menu under + * the given `anchorElement`. If the context menu is already opened, a call to this method closes it. + * @param options + */ + toggleShareContextMenu: (options: ShowShareMenuOptions) => { + const menuItems = shareRegistry.getShareMenuItems({ ...options, onClose: this.onClose }); + this.toggleShareContextMenu({ + ...options, + menuItems, + post: core.http.post, + basePath: core.http.basePath.get(), + }); + }, + }; + } + + private onClose = () => { + ReactDOM.unmountComponentAtNode(this.container); + this.isOpen = false; + }; + + private toggleShareContextMenu({ + anchorElement, + allowEmbed, + allowShortUrl, + objectId, + objectType, + sharingData, + menuItems, + shareableUrl, + post, + basePath, + }: ShowShareMenuOptions & { + menuItems: ShareMenuItem[]; + post: HttpStart['post']; + basePath: string; + }) { + if (this.isOpen) { + this.onClose(); + return; + } + + this.isOpen = true; + + document.body.appendChild(this.container); + const element = ( + + + + + + ); + ReactDOM.render(element, this.container); + } +} +export type ShareMenuManagerStart = ReturnType; diff --git a/src/plugins/share/public/services/share_menu_registry.mock.ts b/src/plugins/share/public/services/share_menu_registry.mock.ts new file mode 100644 index 0000000000000..b69032f0b3e09 --- /dev/null +++ b/src/plugins/share/public/services/share_menu_registry.mock.ts @@ -0,0 +1,55 @@ +/* + * 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 { + ShareMenuRegistry, + ShareMenuRegistrySetup, + ShareMenuRegistryStart, +} from './share_menu_registry'; +import { ShareMenuItem, ShareContext } from '../types'; + +const createSetupMock = (): jest.Mocked => { + const setup = { + register: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = { + getShareMenuItems: jest.fn((props: ShareContext) => [] as ShareMenuItem[]), + }; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const shareMenuRegistryMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/share/public/services/share_menu_registry.test.ts b/src/plugins/share/public/services/share_menu_registry.test.ts new file mode 100644 index 0000000000000..b79f1858af051 --- /dev/null +++ b/src/plugins/share/public/services/share_menu_registry.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 { ShareMenuRegistry } from './share_menu_registry'; +import { ShareMenuItem, ShareContext } from '../types'; + +describe('ShareActionsRegistry', () => { + describe('setup', () => { + test('throws when registering duplicate id', () => { + const setup = new ShareMenuRegistry().setup(); + setup.register({ + id: 'myTest', + getShareMenuItems: () => [], + }); + expect(() => + setup.register({ + id: 'myTest', + getShareMenuItems: () => [], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Share menu provider with id [myTest] has already been registered. Use a unique id."` + ); + }); + }); + + describe('start', () => { + describe('getActions', () => { + test('returns a flat list of actions returned by all providers', () => { + const service = new ShareMenuRegistry(); + const registerFunction = service.setup().register; + const shareAction1 = {} as ShareMenuItem; + const shareAction2 = {} as ShareMenuItem; + const shareAction3 = {} as ShareMenuItem; + const provider1Callback = jest.fn(() => [shareAction1]); + const provider2Callback = jest.fn(() => [shareAction2, shareAction3]); + registerFunction({ + id: 'myTest', + getShareMenuItems: provider1Callback, + }); + registerFunction({ + id: 'myTest2', + getShareMenuItems: provider2Callback, + }); + const context = {} as ShareContext; + expect(service.start().getShareMenuItems(context)).toEqual([ + shareAction1, + shareAction2, + shareAction3, + ]); + expect(provider1Callback).toHaveBeenCalledWith(context); + expect(provider2Callback).toHaveBeenCalledWith(context); + }); + }); + }); +}); diff --git a/src/plugins/share/public/services/share_menu_registry.ts b/src/plugins/share/public/services/share_menu_registry.ts new file mode 100644 index 0000000000000..1fec420a9a8f4 --- /dev/null +++ b/src/plugins/share/public/services/share_menu_registry.ts @@ -0,0 +1,57 @@ +/* + * 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 { ShareContext, ShareMenuProvider } from '../types'; + +export class ShareMenuRegistry { + private readonly shareMenuProviders = new Map(); + + public setup() { + return { + /** + * Register an additional source of items for share context menu items. All registered providers + * will be called if a consumer displays the context menu. Returned `ShareMenuItem`s will be shown + * in the context menu together with the default built-in share options. + * Each share provider needs a globally unique id. + * @param shareMenuProvider + */ + register: (shareMenuProvider: ShareMenuProvider) => { + if (this.shareMenuProviders.has(shareMenuProvider.id)) { + throw new Error( + `Share menu provider with id [${shareMenuProvider.id}] has already been registered. Use a unique id.` + ); + } + + this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider); + }, + }; + } + + public start() { + return { + getShareMenuItems: (context: ShareContext) => + Array.from(this.shareMenuProviders.values()).flatMap(shareActionProvider => + shareActionProvider.getShareMenuItems(context) + ), + }; + } +} + +export type ShareMenuRegistrySetup = ReturnType; +export type ShareMenuRegistryStart = ReturnType; diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts new file mode 100644 index 0000000000000..2472129757133 --- /dev/null +++ b/src/plugins/share/public/types.ts @@ -0,0 +1,90 @@ +/* + * 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 { + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, +} from '@elastic/eui/src/components/context_menu/context_menu'; + +/** + * @public + * Properties of the current object to share. Registered share + * menu providers will provide suitable items which have to + * be rendered in an appropriate place by the caller. + * + * It is possible to use the static function `toggleShareContextMenu` + * to render the menu as a popover. + * */ +export interface ShareContext { + objectType: string; + objectId?: string; + /** + * Current url for sharing. This can be set in cases where `window.location.href` + * does not contain a shareable URL (e.g. if using session storage to store the current + * app state is enabled). In these cases the property should contain the URL in a + * format which makes it possible to use it without having access to any other state + * like the current session. + * + * If not set it will default to `window.location.href` + */ + shareableUrl: string; + sharingData: { [key: string]: unknown }; + isDirty: boolean; + onClose: () => void; +} + +/** + * @public + * Eui context menu entry shown directly in the context menu. `sortOrder` is + * used to order the individual items in a flat list returned by all registered + * menu providers. + * */ +export interface ShareContextMenuPanelItem extends EuiContextMenuPanelItemDescriptor { + sortOrder: number; +} + +/** + * @public + * Definition of a menu item rendered in the share menu. `shareMenuItem` is shown + * directly in the context menu. If the item is clicked, the `panel` is shown. + * */ +export interface ShareMenuItem { + shareMenuItem: ShareContextMenuPanelItem; + panel: EuiContextMenuPanelDescriptor; +} + +/** + * @public + * A source for additional menu items shown in the share context menu. Any provider + * registered via `share.register()` will be called if a consumer displays the context + * menu. Returned `ShareMenuItem`s will be shown in the context menu together with the + * default built-in share options. Each share provider needs a globally unique id. + * */ +export interface ShareMenuProvider { + readonly id: string; + + getShareMenuItems: (context: ShareContext) => ShareMenuItem[]; +} + +/** @public */ +export interface ShowShareMenuOptions extends Omit { + anchorElement: HTMLElement; + allowEmbed: boolean; + allowShortUrl: boolean; +} diff --git a/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx index 88a0c4ca08145..9e3d206c9a6dc 100644 --- a/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx +++ b/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { EuiFlyout } from '@elastic/eui'; import { CoreStart } from 'src/core/public'; import { createAction, IAction } from '../../actions'; +import { toMountPoint } from '../../../../kibana_react/public'; export const HELLO_WORLD_ACTION_ID = 'HELLO_WORLD_ACTION_ID'; @@ -29,9 +30,11 @@ export function createHelloWorldAction(overlays: CoreStart['overlays']): IAction type: HELLO_WORLD_ACTION_ID, execute: async () => { const flyoutSession = overlays.openFlyout( - flyoutSession && flyoutSession.close()}> - Hello World, I am a hello world action! - , + toMountPoint( + flyoutSession && flyoutSession.close()}> + Hello World, I am a hello world action! + + ), { 'data-test-subj': 'helloWorldAction', } diff --git a/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx index 3c4ecfb6e7c8a..e984dd8fb64cc 100644 --- a/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx +++ b/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { EuiFlyout } from '@elastic/eui'; import { CoreStart } from 'src/core/public'; import { IAction, createAction } from '../../actions'; +import { toMountPoint } from '../../../../kibana_react/public'; export const SAY_HELLO_ACTION = 'SAY_HELLO_ACTION'; @@ -31,9 +32,11 @@ export function createSayHelloAction(overlays: CoreStart['overlays']): IAction<{ isCompatible: async ({ name }) => name !== undefined, execute: async context => { const flyoutSession = overlays.openFlyout( - flyoutSession && flyoutSession.close()}> - this.getDisplayName(context) - , + toMountPoint( + flyoutSession && flyoutSession.close()}> + this.getDisplayName(context) + + ), { 'data-test-subj': 'sayHelloAction', } diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx index 71724595d462a..4ce748e2c7118 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx @@ -22,6 +22,7 @@ import { npStart, npSetup } from 'ui/new_platform'; import { CONTEXT_MENU_TRIGGER, IEmbeddable } from '../../../../../src/plugins/embeddable/public'; import { createAction } from '../../../../../src/plugins/ui_actions/public'; +import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; interface ActionContext { embeddable: IEmbeddable; @@ -36,16 +37,18 @@ function createSamplePanelAction() { return; } npStart.core.overlays.openFlyout( - - - -

{embeddable.getTitle()}

-
-
- -

This is a sample action

-
-
, + toMountPoint( + + + +

{embeddable.getTitle()}

+
+
+ +

This is a sample action

+
+
+ ), { 'data-test-subj': 'samplePanelActionFlyout', } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index de7f0e133ba49..879733685c5f7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -82,8 +82,6 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { const errorUrl = error.error.page?.url || error.url?.full; const method = error.http?.request.method; - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const status = error.http?.response?.status_code; return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index c4cc7dbfd5d1f..8903900a625c1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -148,8 +148,6 @@ export class ServiceIntegrations extends React.Component { panels={[ { id: 0, - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase items: this.getPanelItems(license.features.ml?.is_available) } ]} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx index 46d6f80bf4834..853ce26d324fb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx @@ -88,21 +88,17 @@ export function AddEditFlyout({ // config settings const [sampleRate, setSampleRate] = useState( - // TODO(TS-3.7-ESLINT) ( - selectedConfig?.settings.transaction_sample_rate || // eslint-disable-line @typescript-eslint/camelcase + selectedConfig?.settings.transaction_sample_rate || defaultSettings.TRANSACTION_SAMPLE_RATE ).toString() ); const [captureBody, setCaptureBody] = useState( - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase selectedConfig?.settings.capture_body || defaultSettings.CAPTURE_BODY ); const [transactionMaxSpans, setTransactionMaxSpans] = useState( - // TODO(TS-3.7-ESLINT) ( - selectedConfig?.settings.transaction_max_spans || // eslint-disable-line @typescript-eslint/camelcase + selectedConfig?.settings.transaction_max_spans || defaultSettings.TRANSACTION_MAX_SPANS ).toString() ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx index bb7c03c63f8ab..cc1f9dd529bce 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx @@ -101,8 +101,6 @@ export function SpanFlyout({ const dbContext = span.span.db; const httpContext = span.span.http; const spanTypes = getSpanTypes(span); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const spanHttpStatusCode = httpContext?.response.status_code; const spanHttpUrl = httpContext?.url?.original; const spanHttpMethod = httpContext?.method; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx index 4de70895ff32b..c24435d381008 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx @@ -29,8 +29,6 @@ const getTransactionResultSummaryItem = (transaction: Transaction) => { if (url) { const method = transaction.http?.request.method; - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const status = transaction.http?.response?.status_code; return ; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index bb4d9fa264980..c032d60359903 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -171,8 +171,6 @@ export class TransactionCharts extends Component { {license => - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase this.renderMLHeader(license.features.ml?.is_available) } diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts index baaa2d97752e3..24e7114efddee 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts @@ -105,8 +105,6 @@ export async function getErrorGroups({ // aggregations can be undefined when no matching indices are found. // this is an exception rather than the rule so the ES type does not account for this. - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const hits = (resp.aggregations?.error_groups.buckets || []).map(bucket => { const source = bucket.sample.hits.hits[0]._source; const message = diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index 240a3bd35cbea..6611ae76bc339 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -58,13 +58,9 @@ export async function getServicesItems(setup: Setup) { const eventTypes = bucket.events.buckets; const transactions = eventTypes.find(e => e.key === 'transaction'); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const totalTransactions = transactions?.doc_count || 0; const errors = eventTypes.find(e => e.key === 'error'); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const totalErrors = errors?.doc_count || 0; const deltaAsMinutes = (end - start) / 1000 / 60; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts index 83df9153e557b..dccf8b110d082 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts @@ -48,8 +48,6 @@ export async function getAgentNameByService({ }; const { aggregations } = await client.search(params); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const agentName = aggregations?.agent_names.buckets[0].key as | string | undefined; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index 1c84c68fd8c47..3166938090d8f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -148,8 +148,6 @@ export async function getTransactionBreakdown({ const kpiNames = kpis.map(kpi => kpi.name); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const bucketsByDate = resp.aggregations?.by_date.buckets || []; const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index dfa9e9b70abdb..4933f1b1ed431 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -49,8 +49,6 @@ export async function getMlBucketSize({ try { const resp = await client.search(params); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase return resp.hits.hits[0]?._source.bucket_span || 0; } catch (err) { const isHttpError = 'statusCode' in err; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts index 83a7ffe1f2412..6f8fb39d89337 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts @@ -293,8 +293,6 @@ function getESResponse(buckets: any): ESResponse { lower: { value: bucket?.lower?.value || null }, upper: { value: bucket?.upper?.value || null }, anomaly_score: { - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase value: bucket?.anomaly_score?.value || null } }; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts index 2994990067d00..aa69d069ebe0f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts @@ -32,8 +32,6 @@ export function anomalySeriesTransform( timeSeriesDates: number[] ) { const buckets = - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase response.aggregations?.ml_avg_response_times.buckets.map(getBucket) || []; const bucketSizeInMillis = Math.max(bucketSize, mlBucketSize) * 1000; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index 1752258078add..b16958a269270 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -19,15 +19,9 @@ export function timeseriesTransformer({ bucketSize: number; }) { const aggs = timeseriesResponse.aggregations; - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const overallAvgDuration = aggs?.overall_avg_duration.value || null; - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const responseTimeBuckets = aggs?.response_times.buckets || []; const { avg, p95, p99 } = getResponseTime(responseTimeBuckets); - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase const transactionResultBuckets = aggs?.transaction_results.buckets || []; const tpmBuckets = getTpmBuckets(transactionResultBuckets, bucketSize); diff --git a/x-pack/legacy/plugins/beats_management/public/components/navigation/child_routes.tsx b/x-pack/legacy/plugins/beats_management/public/components/navigation/child_routes.tsx index 189d7b1d2a3bd..89007e6b03ba6 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/navigation/child_routes.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/navigation/child_routes.tsx @@ -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 React, { SFC } from 'react'; +import React, { FC } from 'react'; import { Route, Switch } from 'react-router-dom'; interface RouteConfig { @@ -12,7 +12,7 @@ interface RouteConfig { routes?: RouteConfig[]; } -export const ChildRoutes: SFC<{ +export const ChildRoutes: FC<{ routes?: RouteConfig[]; useSwitch?: boolean; [other: string]: any; diff --git a/x-pack/legacy/plugins/beats_management/types/eui.d.ts b/x-pack/legacy/plugins/beats_management/types/eui.d.ts index 553f10aaf4d19..636d0a2f7b51e 100644 --- a/x-pack/legacy/plugins/beats_management/types/eui.d.ts +++ b/x-pack/legacy/plugins/beats_management/types/eui.d.ts @@ -11,6 +11,6 @@ import * as eui from '@elastic/eui'; import { Moment } from 'moment'; -import { ChangeEventHandler, MouseEventHandler, ReactType, Ref, SFC } from 'react'; +import { ChangeEventHandler, MouseEventHandler, ReactType, Ref, FC } from 'react'; declare module '@elastic/eui' {} diff --git a/x-pack/legacy/plugins/beats_management/types/formsy.d.ts b/x-pack/legacy/plugins/beats_management/types/formsy.d.ts index fbe81194a7d6b..d97a0056147f5 100644 --- a/x-pack/legacy/plugins/beats_management/types/formsy.d.ts +++ b/x-pack/legacy/plugins/beats_management/types/formsy.d.ts @@ -5,8 +5,8 @@ */ declare module 'formsy-react' { - import React, { SFC } from 'react'; - let Formsy: SFC; + import React, { FC } from 'react'; + let Formsy: FC; export interface FormsyInputProps { getErrorMessage(): any; getValue(): any; @@ -43,6 +43,6 @@ declare module 'formsy-react' { // function withFormsy( // component: // | React.Component - // | SFC + // | FC // ): React.Component; } diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index b60f6b267ad84..aa08841e03f52 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -11,6 +11,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { isColorDark, hexToRgb } from '@elastic/eui'; +import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { addAppRedirectMessageToUrl } from 'ui/notify'; @@ -551,9 +552,11 @@ export function initGraphApp(angularModule, deps) { canEditDrillDownUrls: canEditDrillDownUrls }), $scope.$digest.bind($scope)); coreStart.overlays.openFlyout( - - - , { + toMountPoint( + + + + ), { size: 'm', closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }), 'data-test-subj': 'graphSettingsFlyout', diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 369963fb46097..8dede207b803c 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -80,7 +80,8 @@ function GuidancePanelComponent(props: GuidancePanelProps) { } = props; const kibana = useKibana(); - const { overlays, savedObjects, uiSettings, chrome, application } = kibana.services; + const { services, overlays } = kibana; + const { savedObjects, uiSettings, chrome, application } = services; if (!overlays || !chrome || !application) return null; const onOpenDatasourcePicker = () => { diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx index 293219cca9876..b6200d831b248 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx @@ -86,7 +86,8 @@ export function SearchBarComponent(props: SearchBarProps) { }, [currentDatasource]); const kibana = useKibana(); - const { overlays, savedObjects, uiSettings } = kibana.services; + const { services, overlays } = kibana; + const { savedObjects, uiSettings } = services; if (!overlays) return null; return ( diff --git a/x-pack/legacy/plugins/graph/public/services/source_modal.tsx b/x-pack/legacy/plugins/graph/public/services/source_modal.tsx index c985271f4dfe0..20a5b6d0786bd 100644 --- a/x-pack/legacy/plugins/graph/public/services/source_modal.tsx +++ b/x-pack/legacy/plugins/graph/public/services/source_modal.tsx @@ -6,6 +6,7 @@ import { CoreStart } from 'src/core/public'; import React from 'react'; +import { KibanaReactOverlays } from 'src/plugins/kibana_react/public'; import { SourceModal } from '../components/source_modal'; import { IndexPatternSavedObject } from '../types'; @@ -15,7 +16,7 @@ export function openSourceModal( savedObjects, uiSettings, }: { - overlays: CoreStart['overlays']; + overlays: KibanaReactOverlays; savedObjects: CoreStart['savedObjects']; uiSettings: CoreStart['uiSettings']; }, diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx index 33f0a5b1399a1..8e76657b6f21a 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; export const JobStoppedCallout: React.FC = () => ( - + {buttonLabel} ) : ( - + {buttonLabel} ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx index cf3c46c9c4038..0586f5282ddf7 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx @@ -14,7 +14,6 @@ import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { getLogEntryRateSeriesForPartition, getAnnotationsForPartition, - formatAnomalyScore, getTotalNumberOfLogEntriesForPartition, } from '../helpers/data_formatters'; import { AnalyzeInMlButton } from '../analyze_in_ml_button'; @@ -26,7 +25,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; -}> = ({ results, timeRange, setTimeRange, topAnomalyScore, partitionId, jobId }) => { +}> = ({ results, timeRange, setTimeRange, partitionId, jobId }) => { const logEntryRateSeries = useMemo( () => results && results.histogramBuckets @@ -65,8 +64,10 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ /> + - - + diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx index 26f44519312e5..e870c2d442719 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx @@ -109,7 +109,6 @@ export const AnomaliesResults: React.FunctionComponent<{ onRecreateMlJobForUpdate={viewSetupForUpdate} /> - }> {!results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? ( { - + {copy => ( - + { private renderGenerateReportButton = (isDisabled: boolean) => { return ( { + }: ShareContext) => { if ('search' !== objectType) { return []; } @@ -44,8 +44,10 @@ function reportingProvider() { toolTipContent: xpackInfo.get('features.reporting.csv.message'), disabled: !xpackInfo.get('features.reporting.csv.enableLinks', false) ? true : false, ['data-test-subj']: 'csvReportMenuItem', + sortOrder: 1, }, panel: { + id: 'csvReportingPanel', title: panelTitle, content: ( { + shareableUrl, + }: ShareContext) => { if (!['dashboard', 'visualization'].includes(objectType)) { return []; } // Dashboard only mode does not currently support reporting // https://github.com/elastic/kibana/issues/18286 - if (objectType === 'dashboard' && dashboardConfig.getHideWriteControls()) { + if (objectType === 'dashboard' && injector.get('dashboardConfig').getHideWriteControls()) { return []; } const getReportingJobParams = () => { // Replace hashes with original RISON values. - const unhashedUrl = unhashUrl(window.location.href, getUnhashableStates()); - const relativeUrl = unhashedUrl.replace(window.location.origin + chrome.getBasePath(), ''); + const relativeUrl = shareableUrl.replace(window.location.origin + chrome.getBasePath(), ''); const browserTimezone = chrome.getUiSettingsClient().get('dateFormat:tz') === 'Browser' @@ -53,8 +52,7 @@ function reportingProvider(dashboardConfig: any) { const getPngJobParams = () => { // Replace hashes with original RISON values. - const unhashedUrl = unhashUrl(window.location.href, getUnhashableStates()); - const relativeUrl = unhashedUrl.replace(window.location.origin + chrome.getBasePath(), ''); + const relativeUrl = shareableUrl.replace(window.location.origin + chrome.getBasePath(), ''); const browserTimezone = chrome.getUiSettingsClient().get('dateFormat:tz') === 'Browser' @@ -87,6 +85,7 @@ function reportingProvider(dashboardConfig: any) { sortOrder: 10, }, panel: { + id: 'reportingPdfPanel', title: panelTitle, content: ( { + npSetup.plugins.share.register(await reportingProvider()); +})(); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx index a354155fcfb0a..6af7672f6fef8 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx @@ -6,7 +6,7 @@ import { EuiIcon, EuiIconTip, EuiText, IconType, PropsOf, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; -import React, { ReactNode, SFC } from 'react'; +import React, { ReactNode, FC } from 'react'; import { PRIVILEGE_SOURCE, PrivilegeExplanation, @@ -21,7 +21,7 @@ interface Props extends PropsOf { tooltipContent?: ReactNode; } -export const PrivilegeDisplay: SFC = (props: Props) => { +export const PrivilegeDisplay: FC = (props: Props) => { const { explanation } = props; if (!explanation) { @@ -39,7 +39,7 @@ export const PrivilegeDisplay: SFC = (props: Props) => { return ; }; -const SimplePrivilegeDisplay: SFC = (props: Props) => { +const SimplePrivilegeDisplay: FC = (props: Props) => { const { privilege, iconType, iconTooltipContent, explanation, tooltipContent, ...rest } = props; const text = ( @@ -55,7 +55,7 @@ const SimplePrivilegeDisplay: SFC = (props: Props) => { return text; }; -export const SupersededPrivilegeDisplay: SFC = (props: Props) => { +export const SupersededPrivilegeDisplay: FC = (props: Props) => { const { supersededPrivilege, actualPrivilegeSource } = props.explanation || ({} as PrivilegeExplanation); @@ -77,7 +77,7 @@ export const SupersededPrivilegeDisplay: SFC = (props: Props) => { ); }; -export const EffectivePrivilegeDisplay: SFC = (props: Props) => { +export const EffectivePrivilegeDisplay: FC = (props: Props) => { const { explanation, ...rest } = props; const source = getReadablePrivilegeSource(explanation!.actualPrivilegeSource); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts index a2d0daf34fe81..8a9477ad67901 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts @@ -81,7 +81,6 @@ const loginViaEnvironmentCredentials = () => { username: Cypress.env(ELASTICSEARCH_USERNAME), password: Cypress.env(ELASTICSEARCH_PASSWORD), }, - followRedirect: false, headers: { 'kbn-xsrf': 'cypress-creds-via-env' }, method: 'POST', url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, @@ -108,7 +107,6 @@ const loginViaConfig = () => { username: config.elasticsearch.username, password: config.elasticsearch.password, }, - followRedirect: false, headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, method: 'POST', url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts index 57a1f318a7e31..400f82bf81188 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts @@ -57,7 +57,7 @@ describe('Events Viewer', () => { .should('eq', 'Customize Columns'); }); - it('closes the fields browser when the user clicks outside of it', () => { + it.skip('closes the fields browser when the user clicks outside of it', () => { openEventsViewerFieldsBrowser(); clickOutsideFieldsBrowser(); @@ -81,7 +81,7 @@ describe('Events Viewer', () => { ); }); - it('removes the message field from the timeline when the user un-checks the field', () => { + it.skip('removes the message field from the timeline when the user un-checks the field', () => { const toggleField = 'message'; cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should('exist'); @@ -99,7 +99,7 @@ describe('Events Viewer', () => { ); }); - it('filters the events by applying filter criteria from the search bar at the top of the page', () => { + it.skip('filters the events by applying filter criteria from the search bar at the top of the page', () => { const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data cy.get(HEADER_SUBTITLE) @@ -119,7 +119,7 @@ describe('Events Viewer', () => { }); }); - it('adds a field to the events viewer when the user clicks the checkbox', () => { + it.skip('adds a field to the events viewer when the user clicks the checkbox', () => { const filterInput = 'host.geo.c'; const toggleField = 'host.geo.city_name'; @@ -158,7 +158,7 @@ describe('Events Viewer', () => { }); }); - it('launches the inspect query modal when the inspect button is clicked', () => { + it.skip('launches the inspect query modal when the inspect button is clicked', () => { // wait for data to load cy.get(HEADER_SUBTITLE) .invoke('text') @@ -171,7 +171,7 @@ describe('Events Viewer', () => { cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist'); }); - it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { + it.skip('resets all fields in the events viewer when `Reset Fields` is clicked', () => { const filterInput = 'host.geo.c'; const toggleField = 'host.geo.city_name'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts index 5304bcb88a9d3..baf6b7cd2027d 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts @@ -34,7 +34,7 @@ const defaultHeaders = [ { id: 'user.name' }, ]; -describe('Fields Browser', () => { +describe.skip('Fields Browser', () => { beforeEach(() => { loginAndWaitForPage(HOSTS_PAGE); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts index a03ff0c1845f8..cec5d6d88d86d 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts @@ -28,7 +28,7 @@ describe('ml conditional links', () => { return logout(); }); - it('sets the KQL from a single IP with a value for the query', () => { + it.skip('sets the KQL from a single IP with a value for the query', () => { loginAndWaitForPage(mlNetworkSingleIpKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -37,7 +37,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a multiple IPs with a null for the query', () => { + it.skip('sets the KQL from a multiple IPs with a null for the query', () => { loginAndWaitForPage(mlNetworkMultipleIpNullKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -46,7 +46,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a multiple IPs with a value for the query', () => { + it.skip('sets the KQL from a multiple IPs with a value for the query', () => { loginAndWaitForPage(mlNetworkMultipleIpKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -55,7 +55,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a $ip$ with a value for the query', () => { + it.skip('sets the KQL from a $ip$ with a value for the query', () => { loginAndWaitForPage(mlNetworkKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -64,7 +64,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a single host name with a value for query', () => { + it.skip('sets the KQL from a single host name with a value for query', () => { loginAndWaitForPage(mlHostSingleHostKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -73,7 +73,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a multiple host names with null for query', () => { + it.skip('sets the KQL from a multiple host names with null for query', () => { loginAndWaitForPage(mlHostMultiHostNullKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -82,7 +82,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a multiple host names with a value for query', () => { + it.skip('sets the KQL from a multiple host names with a value for query', () => { loginAndWaitForPage(mlHostMultiHostKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -91,7 +91,7 @@ describe('ml conditional links', () => { ); }); - it('sets the KQL from a undefined/null host name but with a value for query', () => { + it.skip('sets the KQL from a undefined/null host name but with a value for query', () => { loginAndWaitForPage(mlHostVariableHostKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( 'have.attr', @@ -100,7 +100,7 @@ describe('ml conditional links', () => { ); }); - it('redirects from a single IP with a null for the query', () => { + it.skip('redirects from a single IP with a null for the query', () => { loginAndWaitForPage(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', @@ -108,7 +108,7 @@ describe('ml conditional links', () => { ); }); - it('redirects from a single IP with a value for the query', () => { + it.skip('redirects from a single IP with a value for the query', () => { loginAndWaitForPage(mlNetworkSingleIpKqlQuery); cy.url().should( 'include', @@ -116,7 +116,7 @@ describe('ml conditional links', () => { ); }); - it('redirects from a multiple IPs with a null for the query', () => { + it.skip('redirects from a multiple IPs with a null for the query', () => { loginAndWaitForPage(mlNetworkMultipleIpNullKqlQuery); cy.url().should( 'include', diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts index 29e04695e496d..7822f4d30365d 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts @@ -23,7 +23,7 @@ describe('Pagination', () => { return logout(); }); - it('pagination updates results and page number', () => { + it.skip('pagination updates results and page number', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); @@ -82,7 +82,7 @@ describe('Pagination', () => { * We only want to comment this code/test for now because it can be nondeterministic * when we figure out a way to really mock the data, we should come back to it */ - it('pagination resets results and page number to first page when refresh is clicked', () => { + it.skip('pagination resets results and page number to first page when refresh is clicked', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); cy.get(NUMBERED_PAGINATION, { timeout: DEFAULT_TIMEOUT }); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts index f11efde021c1c..3f6ed86f29285 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts @@ -23,7 +23,7 @@ describe('toggle column in timeline', () => { const timestampField = '@timestamp'; const idField = '_id'; - it('displays a checked Toggle field checkbox for `@timestamp`, a default timeline column', () => { + it.skip('displays a checked Toggle field checkbox for `@timestamp`, a default timeline column', () => { populateTimeline(); toggleFirstTimelineEventDetails(); @@ -39,7 +39,7 @@ describe('toggle column in timeline', () => { ); }); - it('removes the @timestamp field from the timeline when the user un-checks the toggle', () => { + it.skip('removes the @timestamp field from the timeline when the user un-checks the toggle', () => { populateTimeline(); toggleFirstTimelineEventDetails(); @@ -57,7 +57,7 @@ describe('toggle column in timeline', () => { ); }); - it('adds the _id field to the timeline when the user checks the field', () => { + it.skip('adds the _id field to the timeline when the user checks the field', () => { populateTimeline(); toggleFirstTimelineEventDetails(); @@ -71,7 +71,7 @@ describe('toggle column in timeline', () => { cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${idField}"]`).should('exist'); }); - it('adds the _id field to the timeline via drag and drop', () => { + it.skip('adds the _id field to the timeline via drag and drop', () => { populateTimeline(); toggleFirstTimelineEventDetails(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts index 63104d9804b6e..4ba8b8c44f366 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts @@ -165,23 +165,23 @@ describe('url state', () => { ); }); - it('sets kql on network page', () => { + it.skip('sets kql on network page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); }); - it('sets kql on hosts page', () => { + it.skip('sets kql on hosts page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); }); - it('sets the url state when kql is set', () => { + it.skip('sets the url state when kql is set', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); cy.get(KQL_INPUT, { timeout: 5000 }).type('source.ip: "10.142.0.9" {enter}'); cy.url().should('include', `query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')`); }); - it('sets the url state when kql is set and check if href reflect this change', () => { + it.skip('sets the url state when kql is set and check if href reflect this change', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); cy.get(KQL_INPUT, { timeout: 5000 }).type('source.ip: "10.142.0.9" {enter}'); cy.get(NAVIGATION_HOSTS) @@ -194,7 +194,7 @@ describe('url state', () => { ); }); - it('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => { + it.skip('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlHost); cy.get(KQL_INPUT, { timeout: 5000 }).type('host.name: "siem-kibana" {enter}'); cy.get(NAVIGATION_HOSTS_ALL_HOSTS, { timeout: 5000 }) @@ -241,7 +241,7 @@ describe('url state', () => { ); }); - it('Do not clears kql when navigating to a new page', () => { + it.skip('Do not clears kql when navigating to a new page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); cy.get(NAVIGATION_NETWORK).click({ force: true }); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx index b78f2b0d07d39..408743d261797 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx @@ -307,7 +307,7 @@ const withUnfocused = (state: AutocompleteFieldState) => ({ isFocused: false, }); -export const FixedEuiFieldSearch: React.FC & +export const FixedEuiFieldSearch: React.SFC & EuiFieldSearchProps & { inputRef?: (element: HTMLInputElement | null) => void; onSearch: (value: string) => void; diff --git a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx b/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx index 0211fe7e82643..0d9751ca43db9 100644 --- a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx +++ b/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx @@ -5,7 +5,7 @@ */ import { EuiAvatar, isValidHex } from '@elastic/eui'; -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { MAX_SPACE_INITIALS } from '../../common'; import { Space } from '../../common/model/space'; import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from '../lib/space_attributes'; @@ -17,7 +17,7 @@ interface Props { announceSpaceName?: boolean; } -export const SpaceAvatar: SFC = (props: Props) => { +export const SpaceAvatar: FC = (props: Props) => { const { space, size, announceSpaceName, ...rest } = props; const spaceName = space.name ? space.name.trim() : ''; 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 fe071dba25467..c9bc0891df8c8 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 @@ -5,7 +5,7 @@ */ import { EuiContextMenuPanel, EuiText } from '@elastic/eui'; -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { ManageSpacesButton } from '../../../components'; import { getSpacesFeatureDescription } from '../../../lib/constants'; @@ -13,7 +13,7 @@ interface Props { onManageSpacesClick: () => void; } -export const SpacesDescription: SFC = (props: Props) => { +export const SpacesDescription: FC = (props: Props) => { const panelProps = { className: 'spcDescription', title: 'Spaces', diff --git a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx index 28bb9b2687913..c79bf52a86642 100644 --- a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx @@ -19,6 +19,7 @@ import { import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; +import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; const MAX_SIMPLE_MESSAGE_LENGTH = 140; @@ -43,27 +44,29 @@ export const ToastNotificationText: FC<{ text: any }> = ({ text }) => { const openModal = () => { const modal = npStart.core.overlays.openModal( - modal.close()}> - - - {i18n.translate('xpack.transform.toastText.modalTitle', { - defaultMessage: 'Error details', - })} - - - - - {formattedText} - - - - modal.close()}> - {i18n.translate('xpack.transform.toastText.closeModalButtonText', { - defaultMessage: 'Close', - })} - - - + toMountPoint( + modal.close()}> + + + {i18n.translate('xpack.transform.toastText.modalTitle', { + defaultMessage: 'Error details', + })} + + + + + {formattedText} + + + + modal.close()}> + {i18n.translate('xpack.transform.toastText.closeModalButtonText', { + defaultMessage: 'Close', + })} + + + + ) ); }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx index 3bfe6c2cd0c5f..fb0a71baea321 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import ReactDOM from 'react-dom'; import { act } from 'react-dom/test-utils'; @@ -22,7 +22,7 @@ interface TestHookProps { callback: Callback; } -const TestHook: SFC = ({ callback }) => { +const TestHook: FC = ({ callback }) => { callback(); return null; }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 05b68631b7856..9ab3edbe8ad6b 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, SFC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; @@ -60,7 +60,7 @@ interface Props { onChange(s: StepDetailsExposedState): void; } -export const StepCreateForm: SFC = React.memo( +export const StepCreateForm: FC = React.memo( ({ createIndexPattern, transformConfig, transformId, onChange, overrides }) => { const defaults = { ...getDefaultStepCreateState(), ...overrides }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx index 1790b945df9cd..ebce5fe667f1e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; -export const StepCreateSummary: SFC = React.memo(() => { +export const StepCreateSummary: FC = React.memo(() => { return null; }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx index 681a0f7e6ba3f..ad7ef04c39760 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC, useEffect, useRef, useState } from 'react'; +import React, { FC, useEffect, useRef, useState } from 'react'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; @@ -68,7 +68,7 @@ interface PreviewTitleProps { previewRequest: PreviewRequestBody; } -const PreviewTitle: SFC = ({ previewRequest }) => { +const PreviewTitle: FC = ({ previewRequest }) => { const euiCopyText = i18n.translate('xpack.transform.pivotPreview.copyClipboardTooltip', { defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.', }); @@ -102,7 +102,7 @@ interface ErrorMessageProps { message: string; } -const ErrorMessage: SFC = ({ message }) => ( +const ErrorMessage: FC = ({ message }) => ( {message} @@ -114,7 +114,7 @@ interface PivotPreviewProps { query: PivotQuery; } -export const PivotPreview: SFC = React.memo(({ aggs, groupBy, query }) => { +export const PivotPreview: FC = React.memo(({ aggs, groupBy, query }) => { const [clearTable, setClearTable] = useState(false); const indexPattern = useCurrentIndexPattern(); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 8eea3b020dcfe..ebb0660cac55f 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, SFC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; @@ -195,7 +195,7 @@ interface Props { onChange(s: StepDefineExposedState): void; } -export const StepDefineForm: SFC = React.memo(({ overrides = {}, onChange }) => { +export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange }) => { const kibanaContext = useContext(KibanaContext); const defaults = { ...getDefaultStepDefineState(kibanaContext), ...overrides }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx index 0843dfc306f85..824ff0462cbba 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import ReactDOM from 'react-dom'; import { SimpleQuery } from '../../../../common'; @@ -23,7 +23,7 @@ interface TestHookProps { callback: Callback; } -const TestHook: SFC = ({ callback }) => { +const TestHook: FC = ({ callback }) => { callback(); return null; }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index aa917ca3b05ec..ef6159a1f7bb0 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, SFC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { metadata } from 'ui/metadata'; @@ -54,7 +54,7 @@ interface Props { onChange(s: StepDetailsExposedState): void; } -export const StepDetailsForm: SFC = React.memo(({ overrides = {}, onChange }) => { +export const StepDetailsForm: FC = React.memo(({ overrides = {}, onChange }) => { const kibanaContext = useContext(KibanaContext); const defaults = { ...getDefaultStepDetailsState(), ...overrides }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx index ddb34dfa3f87d..7d4d25c1d05cf 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; @@ -12,7 +12,7 @@ import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { StepDetailsExposedState } from './step_details_form'; -export const StepDetailsSummary: SFC = React.memo( +export const StepDetailsSummary: FC = React.memo( ({ continuousModeDateField, createIndexPattern, diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index d5b32fc7f05af..25d3915a1eae9 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, SFC, useContext, useEffect, useRef, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; @@ -42,7 +42,7 @@ interface DefinePivotStepProps { setStepDefineState: React.Dispatch>; } -const StepDefine: SFC = ({ +const StepDefine: FC = ({ isCurrentStep, stepDefineState, setCurrentStep, @@ -67,7 +67,7 @@ const StepDefine: SFC = ({ ); }; -export const Wizard: SFC = React.memo(() => { +export const Wizard: FC = React.memo(() => { const kibanaContext = useContext(KibanaContext); // The current WIZARD_STEP diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx index bfdfcbf696d35..35ca789f4daaf 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; @@ -17,7 +17,7 @@ interface StepsNavProps { next?(): void; } -export const WizardNav: SFC = ({ +export const WizardNav: FC = ({ previous, previousActive = true, next, diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx index 24d09c899580e..687bb0df3f577 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { EuiTabbedContent } from '@elastic/eui'; @@ -35,7 +35,7 @@ interface Props { item: TransformListRow; } -export const ExpandedRow: SFC = ({ item }) => { +export const ExpandedRow: FC = ({ item }) => { const stateValues = { ...item.stats }; delete stateValues.stats; delete stateValues.checkpointing; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx index 7ba05a5fe41ec..cae95286c464d 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, SFC } from 'react'; +import React, { Fragment, FC } from 'react'; import { EuiDescriptionList, @@ -29,7 +29,7 @@ interface SectionProps { section: SectionConfig; } -export const Section: SFC = ({ section }) => { +export const Section: FC = ({ section }) => { if (section.items.length === 0) { return null; } @@ -48,7 +48,7 @@ interface ExpandedRowDetailsPaneProps { sections: SectionConfig[]; } -export const ExpandedRowDetailsPane: SFC = ({ sections }) => { +export const ExpandedRowDetailsPane: FC = ({ sections }) => { return ( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx index 416d93007daba..ac7fdcb129531 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC } from 'react'; import { // @ts-ignore @@ -18,7 +18,7 @@ interface Props { json: object; } -export const ExpandedRowJsonPane: SFC = ({ json }) => { +export const ExpandedRowJsonPane: FC = ({ json }) => { return ( diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx index a66244b0e7886..e9ab6f3fe1848 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, StatelessComponent } from 'react'; +import React, { Fragment, FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; -export const DeprecationCountSummary: StatelessComponent<{ +export const DeprecationCountSummary: FunctionComponent<{ deprecations: EnrichedDeprecationInfo[]; allDeprecations: EnrichedDeprecationInfo[]; }> = ({ deprecations, allDeprecations }) => ( diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts index bbdcf99495288..55e913e0f31da 100644 --- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts @@ -14,7 +14,6 @@ import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public'; import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers'; import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory'; import { CustomTimeRangeAction } from './custom_time_range_action'; -import { coreMock } from '../../../../src/core/public/mocks'; /* eslint-disable */ import { HelloWorldEmbeddableFactory, @@ -29,6 +28,12 @@ import { ReactElement } from 'react'; jest.mock('ui/new_platform'); +const createOpenModalMock = () => { + const mock = jest.fn(); + mock.mockReturnValue({ close: jest.fn() }); + return mock; +}; + test('Custom time range action prevents embeddable from using container time', async done => { const embeddableFactories = new Map(); embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); @@ -66,11 +71,10 @@ test('Custom time range action prevents embeddable from using container time', a expect(child2).toBeDefined(); expect(child2.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' }); - const start = coreMock.createStart(); - const overlayMock = start.overlays; - overlayMock.openModal.mockClear(); + const openModalMock = createOpenModalMock(); + new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, commonlyUsedRanges: [], dateFormat: 'MM YYY', }).execute({ @@ -78,7 +82,7 @@ test('Custom time range action prevents embeddable from using container time', a }); await nextTick(); - const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement; + const openModal = openModalMock.mock.calls[0][0] as ReactElement; const wrapper = mount(openModal); wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } }); @@ -129,11 +133,9 @@ test('Removing custom time range action resets embeddable back to container time const child1 = container.getChild('1'); const child2 = container.getChild('2'); - const start = coreMock.createStart(); - const overlayMock = start.overlays; - overlayMock.openModal.mockClear(); + const openModalMock = createOpenModalMock(); new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, commonlyUsedRanges: [], dateFormat: 'MM YYY', }).execute({ @@ -141,7 +143,7 @@ test('Removing custom time range action resets embeddable back to container time }); await nextTick(); - const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement; + const openModal = openModalMock.mock.calls[0][0] as ReactElement; const wrapper = mount(openModal); wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } }); @@ -151,7 +153,7 @@ test('Removing custom time range action resets embeddable back to container time container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, commonlyUsedRanges: [], dateFormat: 'MM YYY', }).execute({ @@ -159,7 +161,7 @@ test('Removing custom time range action resets embeddable back to container time }); await nextTick(); - const openModal2 = (overlayMock.openModal as any).mock.calls[1][0]; + const openModal2 = openModalMock.mock.calls[1][0]; const wrapper2 = mount(openModal2); findTestSubject(wrapper2, 'removePerPanelTimeRangeButton').simulate('click'); @@ -209,11 +211,9 @@ test('Cancelling custom time range action leaves state alone', async done => { const child1 = container.getChild('1'); const child2 = container.getChild('2'); - const start = coreMock.createStart(); - const overlayMock = start.overlays; - overlayMock.openModal.mockClear(); + const openModalMock = createOpenModalMock(); new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, commonlyUsedRanges: [], dateFormat: 'MM YYY', }).execute({ @@ -221,7 +221,7 @@ test('Cancelling custom time range action leaves state alone', async done => { }); await nextTick(); - const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement; + const openModal = openModalMock.mock.calls[0][0] as ReactElement; const wrapper = mount(openModal); wrapper.setState({ timeRange: { from: 'now-300m', to: 'now-400m' } }); @@ -263,9 +263,9 @@ test(`badge is compatible with embeddable that inherits from parent`, async () = const child = container.getChild('1'); - const start = coreMock.createStart(); + const openModalMock = createOpenModalMock(); const compatible = await new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, commonlyUsedRanges: [], dateFormat: 'MM YYY', }).isCompatible({ @@ -333,9 +333,9 @@ test('Attempting to execute on incompatible embeddable throws an error', async ( const child = container.getChild('1'); - const start = coreMock.createStart(); + const openModalMock = createOpenModalMock(); const action = await new CustomTimeRangeAction({ - openModal: start.overlays.openModal, + openModal: openModalMock, dateFormat: 'MM YYYY', commonlyUsedRanges: [], }); diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts index c6046c02f0833..d2b9fa9ac1655 100644 --- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts @@ -13,7 +13,6 @@ import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public'; import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers'; import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory'; import { CustomTimeRangeBadge } from './custom_time_range_badge'; -import { coreMock } from '../../../../src/core/public/mocks'; import { ReactElement } from 'react'; import { nextTick } from 'test_utils/enzyme_helpers'; @@ -50,11 +49,11 @@ test('Removing custom time range from badge resets embeddable back to container const child1 = container.getChild('1'); const child2 = container.getChild('2'); - const start = coreMock.createStart(); - const overlayMock = start.overlays; - overlayMock.openModal.mockClear(); + const openModalMock = jest.fn(); + openModalMock.mockReturnValue({ close: jest.fn() }); + new CustomTimeRangeBadge({ - openModal: start.overlays.openModal, + openModal: openModalMock, dateFormat: 'MM YYYY', commonlyUsedRanges: [], }).execute({ @@ -62,7 +61,7 @@ test('Removing custom time range from badge resets embeddable back to container }); await nextTick(); - const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement; + const openModal = openModalMock.mock.calls[0][0] as ReactElement; const wrapper = mount(openModal); findTestSubject(wrapper, 'removePerPanelTimeRangeButton').simulate('click'); @@ -102,9 +101,9 @@ test(`badge is not compatible with embeddable that inherits from parent`, async const child = container.getChild('1'); - const start = coreMock.createStart(); + const openModalMock = jest.fn(); const compatible = await new CustomTimeRangeBadge({ - openModal: start.overlays.openModal, + openModal: openModalMock, dateFormat: 'MM YYYY', commonlyUsedRanges: [], }).isCompatible({ @@ -137,9 +136,9 @@ test(`badge is compatible with embeddable that has custom time range`, async () const child = container.getChild('1'); - const start = coreMock.createStart(); + const openModalMock = jest.fn(); const compatible = await new CustomTimeRangeBadge({ - openModal: start.overlays.openModal, + openModal: openModalMock, dateFormat: 'MM YYYY', commonlyUsedRanges: [], }).isCompatible({ @@ -171,9 +170,9 @@ test('Attempting to execute on incompatible embeddable throws an error', async ( const child = container.getChild('1'); - const start = coreMock.createStart(); + const openModalMock = jest.fn(); const badge = await new CustomTimeRangeBadge({ - openModal: start.overlays.openModal, + openModal: openModalMock, dateFormat: 'MM YYYY', commonlyUsedRanges: [], }); diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index fc106cc8ec26b..e2d1892b1355e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -10,6 +10,7 @@ import { CoreStart, Plugin, } from '../../../../src/core/public'; +import { createReactOverlays } from '../../../../src/plugins/kibana_react/public'; import { IUiActionsStart, IUiActionsSetup } from '../../../../src/plugins/ui_actions/public'; import { CONTEXT_MENU_TRIGGER, @@ -44,8 +45,9 @@ export class AdvancedUiActionsPublicPlugin public start(core: CoreStart, { uiActions }: StartDependencies): Start { const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; + const { openModal } = createReactOverlays(core); const timeRangeAction = new CustomTimeRangeAction({ - openModal: core.overlays.openModal, + openModal, dateFormat, commonlyUsedRanges, }); @@ -53,7 +55,7 @@ export class AdvancedUiActionsPublicPlugin uiActions.attachAction(CONTEXT_MENU_TRIGGER, timeRangeAction.id); const timeRangeBadge = new CustomTimeRangeBadge({ - openModal: core.overlays.openModal, + openModal, dateFormat, commonlyUsedRanges, }); diff --git a/x-pack/plugins/advanced_ui_actions/public/types.ts b/x-pack/plugins/advanced_ui_actions/public/types.ts index bbd7c5528276f..313b09535b196 100644 --- a/x-pack/plugins/advanced_ui_actions/public/types.ts +++ b/x-pack/plugins/advanced_ui_actions/public/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { OverlayRef } from '../../../../src/core/public'; +import { KibanaReactOverlays } from '../../../../src/plugins/kibana_react/public'; export interface CommonlyUsedRange { from: string; @@ -12,10 +12,4 @@ export interface CommonlyUsedRange { display: string; } -export type OpenModal = ( - modalChildren: React.ReactNode, - modalProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } -) => OverlayRef; +export type OpenModal = KibanaReactOverlays['openModal']; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cc905569a0ab0..5b59036b89f49 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -493,22 +493,6 @@ "common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", "common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", "common.ui.scriptingLanguages.errorFetchingToastDescription": "Elasticsearch から利用可能なスクリプト言語の取得中にエラーが発生しました", - "common.ui.share.contextMenu.embedCodeLabel": "埋め込みコード", - "common.ui.share.contextMenu.embedCodePanelTitle": "埋め込みコード", - "common.ui.share.contextMenu.permalinkPanelTitle": "パーマリンク", - "common.ui.share.contextMenu.permalinksLabel": "パーマリンク", - "common.ui.share.contextMenuTitle": "この {objectType} を共有", - "common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。", - "common.ui.share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー", - "common.ui.share.urlPanel.copyLinkButtonLabel": "リンクをコピー", - "common.ui.share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成", - "common.ui.share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。", - "common.ui.share.urlPanel.savedObjectLabel": "保存されたオブジェクト", - "common.ui.share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。", - "common.ui.share.urlPanel.shortUrlLabel": "短い URL", - "common.ui.share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.", - "common.ui.share.urlPanel.snapshotLabel": "スナップショット", - "common.ui.share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}", "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", @@ -2836,6 +2820,22 @@ "server.status.redTitle": "赤", "server.status.uninitializedTitle": "アンインストールしました", "server.status.yellowTitle": "黄色", + "share.contextMenu.embedCodeLabel": "埋め込みコード", + "share.contextMenu.embedCodePanelTitle": "埋め込みコード", + "share.contextMenu.permalinkPanelTitle": "パーマリンク", + "share.contextMenu.permalinksLabel": "パーマリンク", + "share.contextMenuTitle": "この {objectType} を共有", + "share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。", + "share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー", + "share.urlPanel.copyLinkButtonLabel": "リンクをコピー", + "share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成", + "share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。", + "share.urlPanel.savedObjectLabel": "保存されたオブジェクト", + "share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。", + "share.urlPanel.shortUrlLabel": "短い URL", + "share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.", + "share.urlPanel.snapshotLabel": "スナップショット", + "share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}", "statusPage.loadStatus.serverIsDownErrorMessage": "サーバーステータスのリクエストに失敗しました。サーバーがダウンしている可能性があります。", "statusPage.loadStatus.serverStatusCodeErrorMessage": "サーバーステータスのリクエストに失敗しました。ステータスコード: {responseStatus}", "statusPage.metricsTiles.columns.heapTotalHeader": "ヒープ合計", @@ -6162,7 +6162,6 @@ "xpack.infra.analysisSetup.steps.setupProcess.viewResultsButton": "結果を表示", "xpack.infra.logs.analysis.analyzeInMlButtonLabel": "ML で分析", "xpack.infra.logs.analysis.anomaliesExpandedRowNumberOfLogEntriesDescription": "ログエントリーの数です", - "xpack.infra.logs.analysis.anomaliesExpandedRowTopAnomalyScoreDescription": "最高異常スコア", "xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "15 分ごとのログエントリー (平均)", "xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "異常を読み込み中", "xpack.infra.logs.analysis.anomaliesSectionTitle": "異常", @@ -12775,4 +12774,4 @@ "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 044d07b917299..2b3c788125e6a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -493,22 +493,6 @@ "common.ui.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}", "common.ui.savedObjects.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认", "common.ui.scriptingLanguages.errorFetchingToastDescription": "从 Elasticsearch 获取可用的脚本语言时出错", - "common.ui.share.contextMenu.embedCodeLabel": "嵌入代码", - "common.ui.share.contextMenu.embedCodePanelTitle": "嵌入代码", - "common.ui.share.contextMenu.permalinkPanelTitle": "固定链接", - "common.ui.share.contextMenu.permalinksLabel": "固定链接", - "common.ui.share.contextMenuTitle": "共享此 {objectType}", - "common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。", - "common.ui.share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码", - "common.ui.share.urlPanel.copyLinkButtonLabel": "复制链接", - "common.ui.share.urlPanel.generateLinkAsLabel": "将链接生成为", - "common.ui.share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。", - "common.ui.share.urlPanel.savedObjectLabel": "已保存对象", - "common.ui.share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL,以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL,但应能很好地处理短 URL。", - "common.ui.share.urlPanel.shortUrlLabel": "短 URL", - "common.ui.share.urlPanel.snapshotDescription": "快照 URL 将{objectType}的当前状态编入 URL 自身之中。通过此 URL,将无法看到对已保存的{objectType}的编辑。", - "common.ui.share.urlPanel.snapshotLabel": "快照", - "common.ui.share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误:{errorMessage}", "common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", @@ -2837,6 +2821,22 @@ "server.status.redTitle": "红", "server.status.uninitializedTitle": "未初始化", "server.status.yellowTitle": "黄", + "share.contextMenu.embedCodeLabel": "嵌入代码", + "share.contextMenu.embedCodePanelTitle": "嵌入代码", + "share.contextMenu.permalinkPanelTitle": "固定链接", + "share.contextMenu.permalinksLabel": "固定链接", + "share.contextMenuTitle": "共享此 {objectType}", + "share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。", + "share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码", + "share.urlPanel.copyLinkButtonLabel": "复制链接", + "share.urlPanel.generateLinkAsLabel": "将链接生成为", + "share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。", + "share.urlPanel.savedObjectLabel": "已保存对象", + "share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL,以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL,但应能很好地处理短 URL。", + "share.urlPanel.shortUrlLabel": "短 URL", + "share.urlPanel.snapshotDescription": "快照 URL 将{objectType}的当前状态编入 URL 自身之中。通过此 URL,将无法看到对已保存的{objectType}的编辑。", + "share.urlPanel.snapshotLabel": "快照", + "share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误:{errorMessage}", "statusPage.loadStatus.serverIsDownErrorMessage": "无法请求服务器状态。也许您的服务器已关闭?", "statusPage.loadStatus.serverStatusCodeErrorMessage": "无法使用状态代码 {responseStatus} 请求服务器状态", "statusPage.metricsTiles.columns.heapTotalHeader": "堆总计", @@ -6164,7 +6164,6 @@ "xpack.infra.analysisSetup.steps.setupProcess.viewResultsButton": "查看结果", "xpack.infra.logs.analysis.analyzeInMlButtonLabel": "在 ML 中分析", "xpack.infra.logs.analysis.anomaliesExpandedRowNumberOfLogEntriesDescription": "日志条目数", - "xpack.infra.logs.analysis.anomaliesExpandedRowTopAnomalyScoreDescription": "最大异常分数", "xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "每 15 分钟日志条目数(平均值)", "xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "正在加载异常", "xpack.infra.logs.analysis.anomaliesSectionTitle": "异常", @@ -12865,4 +12864,4 @@ "xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 5cdf7f4fc30c4..cef4aef6df865 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4209,24 +4209,24 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.7.0.tgz#dff176bdb73dfd7e2e43062452189bd1b9db6021" - integrity sha512-H5G7yi0b0FgmqaEUpzyBlVh0d9lq4cWG2ap0RKa6BkF3rpBb6IrAoubt1NWh9R2kRs/f0k6XwRDiDz3X/FqXhQ== +"@typescript-eslint/eslint-plugin@^2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.8.0.tgz#eca584d46094ebebc3cb3e9fb625bfbc904a534d" + integrity sha512-ohqul5s6XEB0AzPWZCuJF5Fd6qC0b4+l5BGEnrlpmvXxvyymb8yw8Bs4YMF8usNAeuCJK87eFIHy8g8GFvOtGA== dependencies: - "@typescript-eslint/experimental-utils" "2.7.0" - eslint-utils "^1.4.2" + "@typescript-eslint/experimental-utils" "2.8.0" + eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" - regexpp "^2.0.1" + regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.7.0.tgz#58d790a3884df3041b5a5e08f9e5e6b7c41864b5" - integrity sha512-9/L/OJh2a5G2ltgBWJpHRfGnt61AgDeH6rsdg59BH0naQseSwR7abwHq3D5/op0KYD/zFT4LS5gGvWcMmegTEg== +"@typescript-eslint/experimental-utils@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.8.0.tgz#208b4164d175587e9b03ce6fea97d55f19c30ca9" + integrity sha512-jZ05E4SxCbbXseQGXOKf3ESKcsGxT8Ucpkp1jiVp55MGhOvZB2twmWKf894PAuVQTCgbPbJz9ZbRDqtUWzP8xA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.7.0" + "@typescript-eslint/typescript-estree" "2.8.0" eslint-scope "^5.0.0" "@typescript-eslint/experimental-utils@^1.13.0": @@ -4238,14 +4238,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/parser@^2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.7.0.tgz#b5e6a4944e2b68dba1e7fbfd5242e09ff552fd12" - integrity sha512-ctC0g0ZvYclxMh/xI+tyqP0EC2fAo6KicN9Wm2EIao+8OppLfxji7KAGJosQHSGBj3TcqUrA96AjgXuKa5ob2g== +"@typescript-eslint/parser@^2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.8.0.tgz#e10f7c40c8cf2fb19920c879311e6c46ad17bacb" + integrity sha512-NseXWzhkucq+JM2HgqAAoKEzGQMb5LuTRjFPLQzGIdLthXMNUfuiskbl7QSykvWW6mvzCtYbw1fYWGa2EIaekw== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.7.0" - "@typescript-eslint/typescript-estree" "2.7.0" + "@typescript-eslint/experimental-utils" "2.8.0" + "@typescript-eslint/typescript-estree" "2.8.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -4256,13 +4256,14 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.7.0.tgz#34fd98c77a07b40d04d5b4203eddd3abeab909f4" - integrity sha512-vVCE/DY72N4RiJ/2f10PTyYekX2OLaltuSIBqeHYI44GQ940VCYioInIb8jKMrK9u855OEJdFC+HmWAZTnC+Ag== +"@typescript-eslint/typescript-estree@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.8.0.tgz#fcc3fe6532840085d29b75432c8a59895876aeca" + integrity sha512-ksvjBDTdbAQ04cR5JyFSDX113k66FxH1tAXmi+dj6hufsl/G0eMc/f1GgLjEVPkYClDbRKv+rnBFuE5EusomUw== dependencies: debug "^4.1.1" - glob "^7.1.4" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" is-glob "^4.0.1" lodash.unescape "4.0.1" semver "^6.3.0" @@ -11381,12 +11382,12 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" - integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q== +eslint-utils@^1.4.2, eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== dependencies: - eslint-visitor-keys "^1.0.0" + eslint-visitor-keys "^1.1.0" eslint-visitor-keys@^1.0.0: version "1.0.0" @@ -13483,6 +13484,18 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@~7.0.0: version "7.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"