diff --git a/.i18nrc.json b/.i18nrc.json index 19d361aed9344..e18f529b92ac3 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -24,6 +24,7 @@ "src/legacy/core_plugins/management", "src/plugins/management" ], + "maps_legacy": "src/plugins/maps_legacy", "indexPatternManagement": "src/plugins/index_pattern_management", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 430590c7a2505..121ae80734dfd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -29,7 +29,6 @@ export declare class Field implements IFieldType | [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | any | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | -| [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) | | Record<string, string> | | | [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md deleted file mode 100644 index 664a7b7b7ca0e..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) - -## IndexPatternField.routes property - -Signature: - -```typescript -routes: Record; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md index 4b7184b7dc151..478b73f5f8581 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md @@ -24,5 +24,5 @@ export declare class FieldList extends Array implements IFieldList | [getByName](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md) | | (name: string) => Field | undefined | | | [getByType](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md) | | (type: string) => any[] | | | [remove](./kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md) | | (field: IFieldType) => void | | -| [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) | | (field: Field) => void | | +| [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) | | (field: Record<string, any>) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md index ca03ec4b72893..d5156ed41e493 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md @@ -7,5 +7,5 @@ Signature: ```typescript -update: (field: Field) => void; +update: (field: Record) => void; ``` diff --git a/docs/images/tutorial-ilm-custom-policy.png b/docs/images/tutorial-ilm-custom-policy.png new file mode 100644 index 0000000000000..03b67829f605c Binary files /dev/null and b/docs/images/tutorial-ilm-custom-policy.png differ diff --git a/docs/images/tutorial-ilm-delete-phase-creation.png b/docs/images/tutorial-ilm-delete-phase-creation.png new file mode 100644 index 0000000000000..91a55733c284e Binary files /dev/null and b/docs/images/tutorial-ilm-delete-phase-creation.png differ diff --git a/docs/images/tutorial-ilm-delete-rollover.png b/docs/images/tutorial-ilm-delete-rollover.png new file mode 100644 index 0000000000000..ba021ecc2ac5c Binary files /dev/null and b/docs/images/tutorial-ilm-delete-rollover.png differ diff --git a/docs/images/tutorial-ilm-hotphaserollover-default.png b/docs/images/tutorial-ilm-hotphaserollover-default.png new file mode 100644 index 0000000000000..a9088c63d885d Binary files /dev/null and b/docs/images/tutorial-ilm-hotphaserollover-default.png differ diff --git a/docs/images/tutorial-ilm-modify-default-warm-phase-rollover.png b/docs/images/tutorial-ilm-modify-default-warm-phase-rollover.png new file mode 100644 index 0000000000000..c6f1e9b40e977 Binary files /dev/null and b/docs/images/tutorial-ilm-modify-default-warm-phase-rollover.png differ diff --git a/docs/management/index-lifecycle-policies/example-index-lifecycle-policy.asciidoc b/docs/management/index-lifecycle-policies/example-index-lifecycle-policy.asciidoc index f68708f1b6394..e6d94e9ca61a3 100644 --- a/docs/management/index-lifecycle-policies/example-index-lifecycle-policy.asciidoc +++ b/docs/management/index-lifecycle-policies/example-index-lifecycle-policy.asciidoc @@ -1,23 +1,179 @@ [role="xpack"] + [[example-using-index-lifecycle-policy]] -=== Example of using an index lifecycle policy +=== Tutorial: Use {ilm-init} to manage {filebeat} time-based indices + +With {ilm} ({ilm-init}), you can create policies that perform actions automatically +on indices as they age and grow. {ilm-init} policies help you to manage +performance, resilience, and retention of your data during its lifecycle. This tutorial shows +you how to use {kib}’s *Index Lifecycle Policies* to modify and create {ilm-init} +policies. You can learn more about all of the actions, benefits, and lifecycle +phases in the {ref}/overview-index-lifecycle-management.html[{ilm-init} overview]. + + +[discrete] +[[example-using-index-lifecycle-policy-scenario]] +==== Scenario + +You’re tasked with sending syslog files to an {es} cluster. This +log data has the following data retention guidelines: + +* Keep logs on hot data nodes for 30 days +* Roll over to a new index if the size reaches 50GB +* After 30 days: +** Move the logs to warm data nodes +** Set {ref}/glossary.html#glossary-replica-shard[replica shards] to 1 +** {ref}/indices-forcemerge.html[Force merge] multiple index segments to free up the space used by deleted documents +* Delete logs after 90 days + + +[discrete] +[[example-using-index-lifecycle-policy-prerequisites]] +==== Prerequisites + +To complete this tutorial, you'll need: + +* An {es} cluster with hot and warm nodes configured for shard allocation +awareness. If you’re using {cloud}/ec-getting-started-templates-hot-warm.html[{ess}], +choose the hot-warm architecture deployment template. + ++ +For a self-managed cluster, add node attributes as described for {ref}/shard-allocation-filtering.html[shard allocation filtering] +to label data nodes as hot or warm. This step is required to migrate shards between +nodes configured with specific hardware for the hot or warm phases. ++ +For example, you can set this in your `elasticsearch.yml` for each data node: ++ +[source,yaml] +-------------------------------------------------------------------------------- +node.attr.data: "warm" +-------------------------------------------------------------------------------- + +* A server with {filebeat} installed and configured to send logs to the `elasticsearch` +output as described in {filebeat-ref}/filebeat-getting-started.html[Getting Started with {filebeat}]. + +[discrete] +[[example-using-index-lifecycle-policy-view-fb-ilm-policy]] +==== View the {filebeat} {ilm-init} policy + +{filebeat} includes a default {ilm-init} policy that enables rollover. {ilm-init} +is enabled automatically if you’re using the default `filebeat.yml` and index template. + +To view the default policy in {kib}, go to *Management > Index Lifecycle Policies*, +search for _filebeat_, and choose the _filebeat-version_ policy. + +This policy initiates the rollover action when the index size reaches 50GB or +becomes 30 days old. + +[role="screenshot"] +image::images/tutorial-ilm-hotphaserollover-default.png["Default policy"] + + +[float] +==== Modify the policy + +The default policy is enough to prevent the creation of many tiny daily indices. +You can modify the policy to meet more complex requirements. + +. Activate the warm phase. + ++ +. Set either of the following options to control when the index moves to the warm phase: + +** Provide a value for *Timing for warm phase*. Setting this to *15* keeps the +indices on hot nodes for a range of 15-45 days, depending on when the initial +rollover occurred. + +** Enable *Move to warm phase on rollover*. The index might move to the warm phase +more quickly than intended if it reaches the *Maximum index size* before the +the *Maximum age*. + +. In the *Select a node attribute to control shard allocation* dropdown, select +*data:warm(2)* to migrate shards to warm data nodes. + +. Change *Number of replicas* to *1*. + +. Enable *Force merge data* and set *Number of segments* to *1*. ++ +NOTE: When rollover is enabled in the hot phase, action timing in the other phases +is based on the rollover date. + ++ +[role="screenshot"] +image::images/tutorial-ilm-modify-default-warm-phase-rollover.png["Modify to add warm phase"] + +. Activate the delete phase and set *Timing for delete phase* to *90* days. ++ +[role="screenshot"] +image::images/tutorial-ilm-delete-rollover.png["Add a delete phase"] + +[float] +==== Create a custom policy + +If meeting a specific retention time period is most important, you can create a +custom policy. For this option, you will use {filebeat} daily indices without +rollover. + +. Create a custom policy in {kib}, go to *Management > Index Lifecycle Policies > +Create Policy*. + +. Activate the warm phase and configure it as follows: ++ +|=== +|*Setting* |*Value* + +|Timing for warm phase +|30 days from index creation + +|Node attribute +|`data:warm` + +|Number of replicas +|1 + +|Force merge data +|enable + +|Number of segments +|1 +|=== + ++ +[role="screenshot"] +image::images/tutorial-ilm-custom-policy.png["Modify the custom policy to add a warm phase"] + -A common use case for managing index lifecycle policies is when you’re using -{beats-ref}/beats-reference.html[Beats] to continually send time-series data, -such as metrics and log data, to {es}. When you create the Beats packages, an -index template is installed. The template includes a default policy to apply -when new indices are created. ++ +. Activate the delete phase and set the timing. ++ +|=== +|*Setting* |*Value* +|Timing for delete phase +|90 +|=== -You can edit the policy in {kib}'s *Index Lifecycle Policies*. For example, you might: ++ +[role="screenshot"] +image::images/tutorial-ilm-delete-phase-creation.png["Delete phase"] -* Rollover the index when it reaches 50 GB in size or is 30 days old. These -settings are the default for the Beats lifecycle policy. This avoids -having 1000s of tiny indices. When a rollover occurs, a new “hot” index is -created and added to the index alias. +. Configure the index to use the new policy in *{kib} > Management > Index Lifecycle +Policies* -* Move the index into the warm phase, shrink the index down to a single shard, -and force merge to a single segment. +.. Find your {ilm-init} policy. +.. Click the *Actions* link next to your policy name. +.. Choose *Add policy to index template*. +.. Select your {filebeat} index template name from the *Index template* list. For example, `filebeat-7.5.x`. +.. Click *Add Policy* to save the changes. -* After 60 days, move the index into the cold phase and onto less expensive hardware. ++ +NOTE: If you initially used the default {filebeat} {ilm-init} policy, you will +see a notice that the template already has a policy associated with it. Confirm +that you want to overwrite that configuration. -* Delete the index after 90 days. ++ ++ +TIP: When you change the policy associated with the index template, the active +index will continue to use the policy it was associated with at index creation +unless you manually update it. The next new index will use the updated policy. +For more reasons that your {ilm-init} policy changes might be delayed, see +{ref}/update-lifecycle-policy.html#update-lifecycle-policy[Update Lifecycle Policy]. diff --git a/package.json b/package.json index 2bad3116c9ef2..388e46aedf37d 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,7 @@ "@types/recompose": "^0.30.6", "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", - "@types/selenium-webdriver": "4.0.9", + "@types/selenium-webdriver": "^4.0.5", "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", @@ -462,7 +462,6 @@ "load-grunt-config": "^3.0.1", "mocha": "^7.1.1", "mock-http-server": "1.3.0", - "ms-chromium-edge-driver": "^0.2.0", "multistream": "^2.1.1", "murmurhash3js": "3.0.1", "mutation-observer": "^1.0.3", @@ -481,7 +480,7 @@ "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", - "selenium-webdriver": "^4.0.0-alpha.7", + "selenium-webdriver": "^4.0.0-alpha.5", "simple-git": "1.116.0", "simplebar-react": "^2.1.0", "sinon": "^7.4.2", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 399720f310f67..fa8884e2ece75 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -79185,7 +79185,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(704); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(922); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(927); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79370,9 +79370,9 @@ const pAll = __webpack_require__(706); const arrify = __webpack_require__(708); const globby = __webpack_require__(709); const isGlob = __webpack_require__(603); -const cpFile = __webpack_require__(907); -const junk = __webpack_require__(919); -const CpyError = __webpack_require__(920); +const cpFile = __webpack_require__(912); +const junk = __webpack_require__(924); +const CpyError = __webpack_require__(925); const defaultOptions = { ignoreJunk: true @@ -79622,8 +79622,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(710); const glob = __webpack_require__(712); const fastGlob = __webpack_require__(717); -const dirGlob = __webpack_require__(900); -const gitignore = __webpack_require__(903); +const dirGlob = __webpack_require__(905); +const gitignore = __webpack_require__(908); const DEFAULT_FILTER = () => false; @@ -81456,11 +81456,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(719); var taskManager = __webpack_require__(720); -var reader_async_1 = __webpack_require__(871); -var reader_stream_1 = __webpack_require__(895); -var reader_sync_1 = __webpack_require__(896); -var arrayUtils = __webpack_require__(898); -var streamUtils = __webpack_require__(899); +var reader_async_1 = __webpack_require__(876); +var reader_stream_1 = __webpack_require__(900); +var reader_sync_1 = __webpack_require__(901); +var arrayUtils = __webpack_require__(903); +var streamUtils = __webpack_require__(904); /** * Synchronous API. */ @@ -82100,9 +82100,9 @@ var extend = __webpack_require__(837); */ var compilers = __webpack_require__(840); -var parsers = __webpack_require__(867); -var cache = __webpack_require__(868); -var utils = __webpack_require__(869); +var parsers = __webpack_require__(872); +var cache = __webpack_require__(873); +var utils = __webpack_require__(874); var MAX_LENGTH = 1024 * 64; /** @@ -100635,9 +100635,9 @@ var toRegex = __webpack_require__(728); */ var compilers = __webpack_require__(857); -var parsers = __webpack_require__(863); -var Extglob = __webpack_require__(866); -var utils = __webpack_require__(865); +var parsers = __webpack_require__(868); +var Extglob = __webpack_require__(871); +var utils = __webpack_require__(870); var MAX_LENGTH = 1024 * 64; /** @@ -101147,7 +101147,7 @@ var parsers = __webpack_require__(861); * Module dependencies */ -var debug = __webpack_require__(800)('expand-brackets'); +var debug = __webpack_require__(863)('expand-brackets'); var extend = __webpack_require__(737); var Snapdragon = __webpack_require__(767); var toRegex = __webpack_require__(728); @@ -101741,12 +101741,839 @@ exports.createRegex = function(pattern, include) { /* 863 */ /***/ (function(module, exports, __webpack_require__) { +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(864); +} else { + module.exports = __webpack_require__(867); +} + + +/***/ }), +/* 864 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(865); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 865 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(866); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 866 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 867 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(478); +var util = __webpack_require__(29); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(865); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(805); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 868 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; var brackets = __webpack_require__(858); -var define = __webpack_require__(864); -var utils = __webpack_require__(865); +var define = __webpack_require__(869); +var utils = __webpack_require__(870); /** * Characters to use in text regex (we want to "not" match @@ -101901,7 +102728,7 @@ module.exports = parsers; /***/ }), -/* 864 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101939,7 +102766,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 865 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102015,7 +102842,7 @@ utils.createRegex = function(str) { /***/ }), -/* 866 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102026,7 +102853,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(767); -var define = __webpack_require__(864); +var define = __webpack_require__(869); var extend = __webpack_require__(737); /** @@ -102034,7 +102861,7 @@ var extend = __webpack_require__(737); */ var compilers = __webpack_require__(857); -var parsers = __webpack_require__(863); +var parsers = __webpack_require__(868); /** * Customize Snapdragon parser and renderer @@ -102100,7 +102927,7 @@ module.exports = Extglob; /***/ }), -/* 867 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102190,14 +103017,14 @@ function textRegex(pattern) { /***/ }), -/* 868 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(849))(); /***/ }), -/* 869 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102215,7 +103042,7 @@ utils.define = __webpack_require__(836); utils.diff = __webpack_require__(853); utils.extend = __webpack_require__(837); utils.pick = __webpack_require__(854); -utils.typeOf = __webpack_require__(870); +utils.typeOf = __webpack_require__(875); utils.unique = __webpack_require__(740); /** @@ -102513,7 +103340,7 @@ utils.unixify = function(options) { /***/ }), -/* 870 */ +/* 875 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -102648,7 +103475,7 @@ function isBuffer(val) { /***/ }), -/* 871 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102667,9 +103494,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_stream_1 = __webpack_require__(889); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -102730,15 +103557,15 @@ exports.default = ReaderAsync; /***/ }), -/* 872 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(873); -const readdirAsync = __webpack_require__(881); -const readdirStream = __webpack_require__(884); +const readdirSync = __webpack_require__(878); +const readdirAsync = __webpack_require__(886); +const readdirStream = __webpack_require__(889); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -102822,7 +103649,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 873 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102830,11 +103657,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(874); +const DirectoryReader = __webpack_require__(879); let syncFacade = { - fs: __webpack_require__(879), - forEach: __webpack_require__(880), + fs: __webpack_require__(884), + forEach: __webpack_require__(885), sync: true }; @@ -102863,7 +103690,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 874 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102872,9 +103699,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(875); -const stat = __webpack_require__(877); -const call = __webpack_require__(878); +const normalizeOptions = __webpack_require__(880); +const stat = __webpack_require__(882); +const call = __webpack_require__(883); /** * Asynchronously reads the contents of a directory and streams the results @@ -103250,14 +104077,14 @@ module.exports = DirectoryReader; /***/ }), -/* 875 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(876); +const globToRegExp = __webpack_require__(881); module.exports = normalizeOptions; @@ -103434,7 +104261,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 876 */ +/* 881 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -103571,13 +104398,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 877 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(878); +const call = __webpack_require__(883); module.exports = stat; @@ -103652,7 +104479,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 878 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103713,14 +104540,14 @@ function callOnce (fn) { /***/ }), -/* 879 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(878); +const call = __webpack_require__(883); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -103784,7 +104611,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 880 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103813,7 +104640,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 881 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103821,12 +104648,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(882); -const DirectoryReader = __webpack_require__(874); +const maybe = __webpack_require__(887); +const DirectoryReader = __webpack_require__(879); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(883), + forEach: __webpack_require__(888), async: true }; @@ -103868,7 +104695,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 882 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103895,7 +104722,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 883 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103931,7 +104758,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 884 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103939,11 +104766,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(874); +const DirectoryReader = __webpack_require__(879); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(883), + forEach: __webpack_require__(888), async: true }; @@ -103963,16 +104790,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 885 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(886); -var entry_1 = __webpack_require__(888); -var pathUtil = __webpack_require__(887); +var deep_1 = __webpack_require__(891); +var entry_1 = __webpack_require__(893); +var pathUtil = __webpack_require__(892); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -104038,13 +104865,13 @@ exports.default = Reader; /***/ }), -/* 886 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(887); +var pathUtils = __webpack_require__(892); var patternUtils = __webpack_require__(721); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -104128,7 +104955,7 @@ exports.default = DeepFilter; /***/ }), -/* 887 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104159,13 +104986,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 888 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(887); +var pathUtils = __webpack_require__(892); var patternUtils = __webpack_require__(721); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -104251,7 +105078,7 @@ exports.default = EntryFilter; /***/ }), -/* 889 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104271,8 +105098,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(890); -var fs_1 = __webpack_require__(894); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -104322,14 +105149,14 @@ exports.default = FileSystemStream; /***/ }), -/* 890 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(891); -const statProvider = __webpack_require__(893); +const optionsManager = __webpack_require__(896); +const statProvider = __webpack_require__(898); /** * Asynchronous API. */ @@ -104360,13 +105187,13 @@ exports.statSync = statSync; /***/ }), -/* 891 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(892); +const fsAdapter = __webpack_require__(897); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -104379,7 +105206,7 @@ exports.prepare = prepare; /***/ }), -/* 892 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104402,7 +105229,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 893 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104454,7 +105281,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 894 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104485,7 +105312,7 @@ exports.default = FileSystem; /***/ }), -/* 895 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104505,9 +105332,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_stream_1 = __webpack_require__(889); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -104575,7 +105402,7 @@ exports.default = ReaderStream; /***/ }), -/* 896 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104594,9 +105421,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_sync_1 = __webpack_require__(897); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_sync_1 = __webpack_require__(902); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -104656,7 +105483,7 @@ exports.default = ReaderSync; /***/ }), -/* 897 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104675,8 +105502,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(890); -var fs_1 = __webpack_require__(894); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -104722,7 +105549,7 @@ exports.default = FileSystemSync; /***/ }), -/* 898 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104738,7 +105565,7 @@ exports.flatten = flatten; /***/ }), -/* 899 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104759,13 +105586,13 @@ exports.merge = merge; /***/ }), -/* 900 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(901); +const pathType = __webpack_require__(906); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -104831,13 +105658,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 901 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(902); +const pify = __webpack_require__(907); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -104880,7 +105707,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 902 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104971,7 +105798,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 903 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104979,9 +105806,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(717); -const gitIgnore = __webpack_require__(904); -const pify = __webpack_require__(905); -const slash = __webpack_require__(906); +const gitIgnore = __webpack_require__(909); +const pify = __webpack_require__(910); +const slash = __webpack_require__(911); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -105079,7 +105906,7 @@ module.exports.sync = options => { /***/ }), -/* 904 */ +/* 909 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -105548,7 +106375,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 905 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105623,7 +106450,7 @@ module.exports = (input, options) => { /***/ }), -/* 906 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105641,17 +106468,17 @@ module.exports = input => { /***/ }), -/* 907 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const pEvent = __webpack_require__(908); -const CpFileError = __webpack_require__(911); -const fs = __webpack_require__(915); -const ProgressEmitter = __webpack_require__(918); +const pEvent = __webpack_require__(913); +const CpFileError = __webpack_require__(916); +const fs = __webpack_require__(920); +const ProgressEmitter = __webpack_require__(923); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -105765,12 +106592,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 908 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(909); +const pTimeout = __webpack_require__(914); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -106061,12 +106888,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 909 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(910); +const pFinally = __webpack_require__(915); class TimeoutError extends Error { constructor(message) { @@ -106112,7 +106939,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 910 */ +/* 915 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106134,12 +106961,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 911 */ +/* 916 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(912); +const NestedError = __webpack_require__(917); class CpFileError extends NestedError { constructor(message, nested) { @@ -106153,10 +106980,10 @@ module.exports = CpFileError; /***/ }), -/* 912 */ +/* 917 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(913); +var inherits = __webpack_require__(918); var NestedError = function (message, nested) { this.nested = nested; @@ -106207,7 +107034,7 @@ module.exports = NestedError; /***/ }), -/* 913 */ +/* 918 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -106215,12 +107042,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(914); + module.exports = __webpack_require__(919); } /***/ }), -/* 914 */ +/* 919 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -106249,16 +107076,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 915 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(29); const fs = __webpack_require__(22); -const makeDir = __webpack_require__(916); -const pEvent = __webpack_require__(908); -const CpFileError = __webpack_require__(911); +const makeDir = __webpack_require__(921); +const pEvent = __webpack_require__(913); +const CpFileError = __webpack_require__(916); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -106355,7 +107182,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 916 */ +/* 921 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106363,7 +107190,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const {promisify} = __webpack_require__(29); -const semver = __webpack_require__(917); +const semver = __webpack_require__(922); const defaults = { mode: 0o777 & (~process.umask()), @@ -106512,7 +107339,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 917 */ +/* 922 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -108114,7 +108941,7 @@ function coerce (version, options) { /***/ }), -/* 918 */ +/* 923 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -108155,7 +108982,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 919 */ +/* 924 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -108201,12 +109028,12 @@ exports.default = module.exports; /***/ }), -/* 920 */ +/* 925 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(921); +const NestedError = __webpack_require__(926); class CpyError extends NestedError { constructor(message, nested) { @@ -108220,7 +109047,7 @@ module.exports = CpyError; /***/ }), -/* 921 */ +/* 926 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -108276,7 +109103,7 @@ module.exports = NestedError; /***/ }), -/* 922 */ +/* 927 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index f4b91d154cbb8..66f17ab579ec3 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -136,7 +136,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ type: Joi.string() - .valid('chrome', 'firefox', 'ie', 'msedge') + .valid('chrome', 'firefox', 'ie') .default('chrome'), logPollingMs: Joi.number().default(100), diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 5d7b467052029..368d1f47e9c3f 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -24,6 +24,7 @@ - [7. Switch to new platform services](#7-switch-to-new-platform-services) - [8. Migrate to the new plugin system](#8-migrate-to-the-new-plugin-system) - [Bonus: Tips for complex migration scenarios](#bonus-tips-for-complex-migration-scenarios) + - [Keep Kibana fast](#keep-kibana-fast) - [Frequently asked questions](#frequently-asked-questions) - [Is migrating a plugin an all-or-nothing thing?](#is-migrating-a-plugin-an-all-or-nothing-thing) - [Do plugins need to be converted to TypeScript?](#do-plugins-need-to-be-converted-to-typescript) @@ -933,6 +934,66 @@ For a few plugins, some of these steps (such as angular removal) could be a mont One convention that is useful for this is creating a dedicated `public/np_ready` directory to house the code that is ready to migrate, and gradually move more and more code into it until the rest of your plugin is essentially empty. At that point, you'll be able to copy your `index.ts`, `plugin.ts`, and the contents of `./np_ready` over into your plugin in the new platform, leaving your legacy shim behind. This carries the added benefit of providing a way for us to introduce helpful tooling in the future, such as [custom eslint rules](https://github.com/elastic/kibana/pull/40537), which could be run against that specific directory to ensure your code is ready to migrate. +## Keep Kibana fast +**tl;dr**: Load as much code lazily as possible. +Everyone loves snappy applications with responsive UI and hates spinners. Users deserve the best user experiences regardless of whether they run Kibana locally or in the cloud, regardless of their hardware & environment. +There are 2 main aspects of the perceived speed of an application: loading time and responsiveness to user actions. +New platform loads and bootstraps **all** the plugins whenever a user lands on any page. It means that adding every new application affects overall **loading performance** in the new platform, as plugin code is loaded **eagerly** to initialize the plugin and provide plugin API to dependent plugins. +However, it's usually not necessary that the whole plugin code should be loaded and initialized at once. The plugin could keep on loading code covering API functionality on Kibana bootstrap but load UI related code lazily on-demand, when an application page or management section is mounted. +Always prefer to require UI root components lazily when possible (such as in mount handlers). Even if their size may seem negligible, they are likely using some heavy-weight libraries that will also be removed from the initial plugin bundle, therefore, reducing its size by a significant amount. + +```typescript +import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +export class MyPlugin implements Plugin { + setup(core: CoreSetup, plugins: SetupDeps){ + core.application.register({ + id: 'app', + title: 'My app', + async mount(params: AppMountParameters) { + const { mountApp } = await import('./app/mount_app'); + return mountApp(await core.getStartServices(), params); + }, + }); + plugins.management.sections.getSection('another').registerApp({ + id: 'app', + title: 'My app', + order: 1, + async mount(params) { + const { mountManagementSection } = await import('./app/mount_management_section'); + return mountManagementSection(coreSetup, params); + }, + }) + return { + doSomething(){} + } + } +} +``` + +#### How to understand how big the bundle size of my plugin is? +New platform plugins are distributed as a pre-built with `@kbn/optimizer` package artifacts. It allows us to get rid of the shipping of `optimizer` in the distributable version of Kibana. +Every NP plugin artifact contains all plugin dependencies required to run the plugin, except some stateful dependencies shared across plugin bundles via `@kbn/ui-shared-deps`. +It means that NP plugin artifacts tend to have a bigger size than the legacy platform version. +To understand the current size of your plugin artifact, run `@kbn/optimizer` as +```bash +node scripts/build_kibana_platform_plugins.js --dist --no-examples +``` +and check the output in the `target` sub-folder of your plugin folder +```bash +ls -lh plugins/my_plugin/target/public/ +# output +# an async chunk loaded on demand +... 262K 0.plugin.js +# eagerly loaded chunk +... 50K my_plugin.plugin.js +``` +you might see at least one js bundle - `my_plugin.plugin.js`. This is the only artifact loaded by the platform during bootstrap in the browser. The rule of thumb is to keep its size as small as possible. +Other lazily loaded parts of your plugin present in the same folder as separate chunks under `{number}.plugin.js` names. +If you want to investigate what your plugin bundle consists of you need to run `@kbn/optimizer` with `--profile` flag to get generated [webpack stats file](https://webpack.js.org/api/stats/). +Many OSS tools are allowing you to analyze generated stats file +- [an official tool](http://webpack.github.io/analyse/#modules) from webpack authors +- [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) + ## Frequently asked questions ### Is migrating a plugin an all-or-nothing thing? diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 0c4930592b233..959ffaa7e7e08 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -48,6 +48,7 @@ export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; export { scopedHistoryMock } from './application/scoped_history.mock'; +export { applicationServiceMock } from './application/application_service.mock'; function createCoreSetupMock({ basePath = '', @@ -62,9 +63,8 @@ function createCoreSetupMock({ application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), - getStartServices: jest.fn, object, any]>, []>( - () => - Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract]) + getStartServices: jest.fn, any, any]>, []>(() => + Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract]) ), http: httpServiceMock.createSetupContract({ basePath }), notifications: notificationServiceMock.createSetupContract(), diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index f44824238aa21..9f625b4732e26 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -36,7 +36,6 @@ export interface SavedObjectsLegacyService { getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; SavedObjectsClient: typeof SavedObjectsClient; types: string[]; - importAndExportableTypes: string[]; schema: SavedObjectsSchema; getSavedObjectsRepository(...rest: any[]): any; importExport: { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a35bca7375286..37051da4b17da 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2084,8 +2084,6 @@ export interface SavedObjectsLegacyService { // (undocumented) getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; // (undocumented) - importAndExportableTypes: string[]; - // (undocumented) importExport: { objectLimit: number; importSavedObjects(options: SavedObjectsImportOptions): Promise; diff --git a/src/legacy/core_plugins/kibana/inject_vars.js b/src/legacy/core_plugins/kibana/inject_vars.js index 76d1704907ab5..c3b906ee842e3 100644 --- a/src/legacy/core_plugins/kibana/inject_vars.js +++ b/src/legacy/core_plugins/kibana/inject_vars.js @@ -20,10 +20,7 @@ export function injectVars(server) { const serverConfig = server.config(); - const { importAndExportableTypes } = server.savedObjects; - return { - importAndExportableTypes, autocompleteTerminateAfter: serverConfig.get('kibana.autocompleteTerminateAfter'), autocompleteTimeout: serverConfig.get('kibana.autocompleteTimeout'), }; diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 9b7d0afcd7e39..fbfb0a06fabcf 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -17,7 +17,7 @@ @import './visualize/index'; // Has to come after visualize because of some // bad cascading in the Editor layout -@import 'src/legacy/ui/public/vis/index'; +@import '../../../../plugins/maps_legacy/public/index'; // Home styles @import '../../../../plugins/home/public/application/index'; diff --git a/src/legacy/core_plugins/kibana/public/index.ts b/src/legacy/core_plugins/kibana/public/index.ts index a4fffc6eec26d..be22652ab2c1c 100644 --- a/src/legacy/core_plugins/kibana/public/index.ts +++ b/src/legacy/core_plugins/kibana/public/index.ts @@ -20,4 +20,4 @@ export { ProcessedImportResponse, processImportResponse, -} from './management/sections/objects/lib/process_import_response'; +} from '../../../../plugins/saved_objects_management/public/lib'; diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index 705be68a141e7..587a372f91555 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts @@ -17,66 +17,8 @@ * under the License. */ -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public'; -import { createSavedSearchesLoader } from '../../../../../plugins/discover/public'; +import { npSetup } from 'ui/new_platform'; -/** - * This registry is used for the editing mode of Saved Searches, Visualizations, - * Dashboard and Time Lion saved objects. - */ -interface SavedObjectRegistryEntry { - id: string; - service: SavedObjectLoader; - title: string; -} - -export interface ISavedObjectsManagementRegistry { - register(service: SavedObjectRegistryEntry): void; - all(): SavedObjectRegistryEntry[]; - get(id: string): SavedObjectRegistryEntry | undefined; -} - -const registry: SavedObjectRegistryEntry[] = []; - -export const savedObjectManagementRegistry: ISavedObjectsManagementRegistry = { - register: (service: SavedObjectRegistryEntry) => { - registry.push(service); - }, - all: () => { - return registry; - }, - get: (id: string) => { - return _.find(registry, { id }); - }, -}; - -const services = { - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - search: npStart.plugins.data.search, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, -}; - -savedObjectManagementRegistry.register({ - id: 'savedVisualizations', - service: npStart.plugins.visualizations.savedVisualizationsLoader, - title: 'visualizations', -}); - -savedObjectManagementRegistry.register({ - id: 'savedDashboards', - service: npStart.plugins.dashboard.getSavedDashboardLoader(), - title: i18n.translate('kbn.dashboard.savedDashboardsTitle', { - defaultMessage: 'dashboards', - }), -}); +const registry = npSetup.plugins.savedObjectsManagement?.serviceRegistry; -savedObjectManagementRegistry.register({ - id: 'savedSearches', - service: createSavedSearchesLoader(services), - title: 'searches', -}); +export const savedObjectManagementRegistry = registry!; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index.js index 54717ad003ade..adc1741f57263 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index.js @@ -17,5 +17,4 @@ * under the License. */ -import './objects'; import './index_patterns'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js index 0dcf778a5a662..3569e9caf4e27 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js @@ -53,7 +53,6 @@ const renderFieldEditor = ( indexPattern={indexPattern} field={field} helpers={{ - Field: IndexPatternField, getConfig, $http, fieldFormatEditors, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 594430ca01f4c..c65054f583ef2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -49,6 +49,8 @@ const TAB_INDEXED_FIELDS = 'indexedFields'; const TAB_SCRIPTED_FIELDS = 'scriptedFields'; const TAB_SOURCE_FILTERS = 'sourceFilters'; +const EDIT_FIELD_PATH = '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}'; + function updateSourceFiltersTable($scope) { $scope.$$postDigest(() => { const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID); @@ -97,8 +99,8 @@ function updateScriptedFieldsTable($scope) { fieldFilter={$scope.fieldFilter} scriptedFieldLanguageFilter={$scope.scriptedFieldLanguageFilter} helpers={{ - redirectToRoute: (obj, route) => { - $scope.kbnUrl.changeToRoute(obj, route); + redirectToRoute: field => { + $scope.kbnUrl.changePath(EDIT_FIELD_PATH, field); $scope.$apply(); }, getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route), @@ -140,8 +142,8 @@ function updateIndexedFieldsTable($scope) { fieldWildcardMatcher={$scope.fieldWildcardMatcher} indexedFieldTypeFilter={$scope.indexedFieldTypeFilter} helpers={{ - redirectToRoute: (obj, route) => { - $scope.kbnUrl.changeToRoute(obj, route); + redirectToRoute: field => { + $scope.kbnUrl.changePath(EDIT_FIELD_PATH, field); $scope.$apply(); }, getFieldInfo: $scope.getFieldInfo, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap index dc77fe6c8a69d..b38036a0c2bf0 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap @@ -18,7 +18,6 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` "indexPattern": undefined, "info": undefined, "name": "Elastic", - "routes": undefined, "searchable": true, }, ] @@ -45,7 +44,6 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` "indexPattern": undefined, "info": undefined, "name": "timestamp", - "routes": undefined, "type": "date", }, ] @@ -72,7 +70,6 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "indexPattern": undefined, "info": undefined, "name": "Elastic", - "routes": undefined, "searchable": true, }, Object { @@ -82,7 +79,6 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "indexPattern": undefined, "info": undefined, "name": "timestamp", - "routes": undefined, "type": "date", }, Object { @@ -92,7 +88,6 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "indexPattern": undefined, "info": undefined, "name": "conflictingField", - "routes": undefined, "type": "conflict", }, ] diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js index 652efbe98067f..074e5784f3dae 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js @@ -65,7 +65,6 @@ export class IndexedFieldsTable extends Component { return { ...field, displayName: field.displayName, - routes: field.routes, indexPattern: field.indexPattern, format: getFieldFormat(indexPattern, field.name), excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, @@ -104,7 +103,7 @@ export class IndexedFieldsTable extends Component { this.props.helpers.redirectToRoute(field, 'edit')} + editField={field => this.props.helpers.redirectToRoute(field)} /> ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap index a53f4d7f609cb..569b75c848c52 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = ` -
+
@@ -39,11 +39,11 @@ exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should filter based on the query bar 1`] = ` -
+
@@ -72,11 +72,11 @@ exports[`ScriptedFieldsTable should filter based on the query bar 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should hide the table if there are no scripted fields 1`] = ` -
+
@@ -97,11 +97,11 @@ exports[`ScriptedFieldsTable should hide the table if there are no scripted fiel } items={Array []} /> -
+ `; exports[`ScriptedFieldsTable should render normally 1`] = ` -
+
@@ -135,11 +135,11 @@ exports[`ScriptedFieldsTable should render normally 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should show a delete modal 1`] = ` -
+
@@ -173,14 +173,16 @@ exports[`ScriptedFieldsTable should show a delete modal 1`] = ` ] } /> - - - -
+ + `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap index e6f0d6cd819e3..4dfda1b9339b1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CallOuts should render normally 1`] = ` -
+ -
+ `; exports[`CallOuts should render without any call outs 1`] = `""`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx index 12e0ee8839967..407928931191d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; import { CallOuts } from '../call_outs'; describe('CallOuts', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow( { expect(component).toMatchSnapshot(); }); - it('should render without any call outs', async () => { + test('should render without any call outs', () => { const component = shallow( ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx index 0c321c8ba8b01..8e38b569a32fa 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx @@ -20,16 +20,20 @@ import React from 'react'; import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; -export const CallOuts = ({ deprecatedLangsInUse, painlessDocLink }) => { +interface CallOutsProps { + deprecatedLangsInUse: string[]; + painlessDocLink: string; +} + +export const CallOuts = ({ deprecatedLangsInUse, painlessDocLink }: CallOutsProps) => { if (!deprecatedLangsInUse.length) { return null; } return ( -
+ <> {

-
+ ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap new file mode 100644 index 0000000000000..2b320782cb163 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DeleteScritpedFieldConfirmationModal should render normally 1`] = ` + + + +`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx new file mode 100644 index 0000000000000..f3594e7507a6a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; + +describe('DeleteScritpedFieldConfirmationModal', () => { + test('should render normally', () => { + const component = shallow( + {}} + hideDeleteConfirmationModal={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx new file mode 100644 index 0000000000000..1e82174f863b0 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; + +import { ScriptedFieldItem } from '../../types'; + +interface DeleteScritpedFieldConfirmationModalProps { + field: ScriptedFieldItem; + hideDeleteConfirmationModal: ( + event?: React.KeyboardEvent | React.MouseEvent + ) => void; + deleteField: (event: React.MouseEvent) => void; +} + +export const DeleteScritpedFieldConfirmationModal = ({ + field, + hideDeleteConfirmationModal, + deleteField, +}: DeleteScritpedFieldConfirmationModalProps) => { + const title = i18n.translate('kbn.management.editIndexPattern.scripted.deleteFieldLabel', { + defaultMessage: "Delete scripted field '{fieldName}'?", + values: { fieldName: field.name }, + }); + const cancelButtonText = i18n.translate( + 'kbn.management.editIndexPattern.scripted.deleteField.cancelButton', + { defaultMessage: 'Cancel' } + ); + const confirmButtonText = i18n.translate( + 'kbn.management.editIndexPattern.scripted.deleteField.deleteButton', + { defaultMessage: 'Delete' } + ); + + return ( + + + + ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts new file mode 100644 index 0000000000000..b87b572333e6f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_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 { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/__snapshots__/header.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/__snapshots__/header.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 3e377ccfbdd41..19479de8f2aa4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -20,10 +20,10 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Header } from '../header'; +import { Header } from './header'; describe('Header', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow(
); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx index 97c235d82f870..b8f832dad72af 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx @@ -18,13 +18,15 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; - import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export const Header = ({ addScriptedFieldUrl }) => ( +interface HeaderProps { + addScriptedFieldUrl: string; +} + +export const Header = ({ addScriptedFieldUrl }: HeaderProps) => ( @@ -56,7 +58,3 @@ export const Header = ({ addScriptedFieldUrl }) => ( ); - -Header.propTypes = { - addScriptedFieldUrl: PropTypes.string.isRequired, -}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts index 5c0bb41eab765..7d74776fb2bca 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts @@ -20,3 +20,4 @@ export { Table } from './table'; export { Header } from './header'; export { CallOuts } from './call_outs'; +export { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap similarity index 93% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap index 2da4d84463b29..8439887dd468a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap @@ -41,6 +41,7 @@ exports[`Table should render normally 1`] = ` "icon": "pencil", "name": "Edit", "onClick": [Function], + "type": "icon", }, Object { "color": "danger", @@ -48,6 +49,7 @@ exports[`Table should render normally 1`] = ` "icon": "trash", "name": "Delete", "onClick": [Function], + "type": "icon", }, ], "name": "", @@ -58,8 +60,9 @@ exports[`Table should render normally 1`] = ` items={ Array [ Object { - "id": 1, - "name": "Elastic", + "lang": "Elastic", + "name": "1", + "script": "", }, ] } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx similarity index 71% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx index 4545bfa8f64db..13b3875f58687 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx @@ -19,45 +19,50 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { Table } from '../table'; +import { ScriptedFieldItem } from '../../types'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; -const indexPattern = { - fieldFormatMap: { - Elastic: { - type: { - title: 'string', - }, - }, - }, -}; +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); -const items = [{ id: 1, name: 'Elastic' }]; +const items: ScriptedFieldItem[] = [{ name: '1', lang: 'Elastic', script: '' }]; describe('Table', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = getIndexPatternMock({ + fieldFormatMap: { + Elastic: { + type: { + title: 'string', + }, + }, + }, + }); + }); + + test('should render normally', () => { + const component = shallow
(
{}} deleteField={() => {}} - onChange={() => {}} /> ); expect(component).toMatchSnapshot(); }); - it('should render the format', async () => { - const component = shallowWithI18nProvider( + test('should render the format', () => { + const component = shallow(
{}} deleteField={() => {}} - onChange={() => {}} /> ); @@ -65,16 +70,15 @@ describe('Table', () => { expect(formatTableCell).toMatchSnapshot(); }); - it('should allow edits', () => { + test('should allow edits', () => { const editField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} - onChange={() => {}} /> ); @@ -83,16 +87,15 @@ describe('Table', () => { expect(editField).toBeCalled(); }); - it('should allow deletes', () => { + test('should allow deletes', () => { const deleteField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} deleteField={deleteField} - onChange={() => {}} /> ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx similarity index 84% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx index 5e05dd95827c7..14aed11b32203 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx @@ -18,27 +18,24 @@ */ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { EuiInMemoryTable } from '@elastic/eui'; - +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui'; -export class Table extends PureComponent { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - items: PropTypes.array.isRequired, - editField: PropTypes.func.isRequired, - deleteField: PropTypes.func.isRequired, - }; +import { ScriptedFieldItem } from '../../types'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; - renderFormatCell = value => { - const { indexPattern } = this.props; +interface TableProps { + indexPattern: IIndexPattern; + items: ScriptedFieldItem[]; + editField: (field: ScriptedFieldItem) => void; + deleteField: (field: ScriptedFieldItem) => void; +} - const title = - indexPattern.fieldFormatMap[value] && indexPattern.fieldFormatMap[value].type - ? indexPattern.fieldFormatMap[value].type.title - : ''; +export class Table extends PureComponent { + renderFormatCell = (value: string) => { + const { indexPattern } = this.props; + const title = get(indexPattern, ['fieldFormatMap', value, 'type', 'title'], ''); return {title}; }; @@ -46,7 +43,7 @@ export class Table extends PureComponent { render() { const { items, editField, deleteField } = this.props; - const columns = [ + const columns: Array> = [ { field: 'displayName', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.nameHeader', { @@ -101,6 +98,7 @@ export class Table extends PureComponent { name: '', actions: [ { + type: 'icon', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.editHeader', { defaultMessage: 'Edit', }), @@ -112,6 +110,7 @@ export class Table extends PureComponent { onClick: editField, }, { + type: 'icon', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.deleteHeader', { defaultMessage: 'Delete', }), diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx similarity index 75% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index 5be963ad94b7d..914d80f9f61d7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -18,9 +18,10 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; import { ScriptedFieldsTable } from '../scripted_fields_table'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/common/index_patterns'; jest.mock('@elastic/eui', () => ({ EuiTitle: 'eui-title', @@ -36,18 +37,20 @@ jest.mock('@elastic/eui', () => ({ default: () => {}, }, })); -jest.mock('../components/header', () => ({ Header: 'header' })); -jest.mock('../components/call_outs', () => ({ CallOuts: 'call-outs' })); -jest.mock('../components/table', () => ({ +jest.mock('./components/header', () => ({ Header: 'header' })); +jest.mock('./components/call_outs', () => ({ CallOuts: 'call-outs' })); +jest.mock('./components/table', () => ({ // Note: this seems to fix React complaining about non lowercase attributes Table: () => { return 'table'; }, })); + jest.mock('ui/scripting_languages', () => ({ getSupportedScriptingLanguages: () => ['painless'], getDeprecatedScriptingLanguages: () => [], })); + jest.mock('ui/documentation_links', () => ({ documentationLinks: { scriptedFields: { @@ -61,16 +64,22 @@ const helpers = { getRouteHref: () => '#', }; -const indexPattern = { - getScriptedFields: () => [ - { name: 'ScriptedField', lang: 'painless', script: 'x++' }, - { name: 'JustATest', lang: 'painless', script: 'z++' }, - ], -}; +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); describe('ScriptedFieldsTable', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = getIndexPatternMock({ + getScriptedFields: () => [ + { name: 'ScriptedField', lang: 'painless', script: 'x++' }, + { name: 'JustATest', lang: 'painless', script: 'z++' }, + ], + }); + }); + + test('should render normally', async () => { + const component = shallow( ); @@ -82,8 +91,8 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the query bar', async () => { - const component = shallowWithI18nProvider( + test('should filter based on the query bar', async () => { + const component = shallow( ); @@ -98,16 +107,16 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the lang filter', async () => { - const component = shallowWithI18nProvider( + test('should filter based on the lang filter', async () => { + const component = shallow( [ { name: 'ScriptedField', lang: 'painless', script: 'x++' }, { name: 'JustATest', lang: 'painless', script: 'z++' }, { name: 'Bad', lang: 'somethingElse', script: 'z++' }, ], - }} + })} helpers={helpers} /> ); @@ -123,12 +132,12 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should hide the table if there are no scripted fields', async () => { - const component = shallowWithI18nProvider( + test('should hide the table if there are no scripted fields', async () => { + const component = shallow( [], - }} + })} helpers={helpers} /> ); @@ -141,22 +150,22 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should show a delete modal', async () => { - const component = shallowWithI18nProvider( + test('should show a delete modal', async () => { + const component = shallow( ); await component.update(); // Fire `componentWillMount()` - component.instance().startDeleteField({ name: 'ScriptedField' }); + component.instance().startDeleteField({ name: 'ScriptedField', lang: '', script: '' }); await component.update(); // Ensure the modal is visible expect(component).toMatchSnapshot(); }); - it('should delete a field', async () => { + test('should delete a field', async () => { const removeScriptedField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow( { ); await component.update(); // Fire `componentWillMount()` - component.instance().startDeleteField({ name: 'ScriptedField' }); + component.instance().startDeleteField({ name: 'ScriptedField', lang: '', script: '' }); + await component.update(); await component.instance().deleteField(); await component.update(); + expect(removeScriptedField).toBeCalled(); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx similarity index 59% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 69343a5175a25..e8dfbd6496057 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -18,31 +18,42 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { getSupportedScriptingLanguages, getDeprecatedScriptingLanguages, } from 'ui/scripting_languages'; import { documentationLinks } from 'ui/documentation_links'; -import { EuiSpacer, EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { Table, Header, CallOuts } from './components'; - -export class ScriptedFieldsTable extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - fieldFilter: PropTypes.string, - scriptedFieldLanguageFilter: PropTypes.string, - helpers: PropTypes.shape({ - redirectToRoute: PropTypes.func.isRequired, - getRouteHref: PropTypes.func.isRequired, - }), - onRemoveField: PropTypes.func, +import { EuiSpacer } from '@elastic/eui'; + +import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; +import { ScriptedFieldItem } from './types'; + +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; + +interface ScriptedFieldsTableProps { + indexPattern: IIndexPattern; + fieldFilter?: string; + scriptedFieldLanguageFilter?: string; + helpers: { + redirectToRoute: Function; + getRouteHref: Function; }; + onRemoveField?: () => void; +} - constructor(props) { +interface ScriptedFieldsTableState { + deprecatedLangsInUse: string[]; + fieldToDelete: ScriptedFieldItem | undefined; + isDeleteConfirmationModalVisible: boolean; + fields: ScriptedFieldItem[]; +} + +export class ScriptedFieldsTable extends Component< + ScriptedFieldsTableProps, + ScriptedFieldsTableState +> { + constructor(props: ScriptedFieldsTableProps) { super(props); this.state = { @@ -64,7 +75,8 @@ export class ScriptedFieldsTable extends Component { const deprecatedLangs = getDeprecatedScriptingLanguages(); const supportedLangs = getSupportedScriptingLanguages(); - for (const { lang } of fields) { + for (const field of fields) { + const lang: string = field.lang; if (deprecatedLangs.includes(lang) || !supportedLangs.includes(lang)) { deprecatedLangsInUse.push(lang); } @@ -91,7 +103,8 @@ export class ScriptedFieldsTable extends Component { let filteredFields = languageFilteredFields; if (fieldFilter) { - const normalizedFieldFilter = this.props.fieldFilter.toLowerCase(); + const normalizedFieldFilter = fieldFilter.toLowerCase(); + filteredFields = languageFilteredFields.filter(field => field.name.toLowerCase().includes(normalizedFieldFilter) ); @@ -100,18 +113,7 @@ export class ScriptedFieldsTable extends Component { return filteredFields; }; - renderCallOuts() { - const { deprecatedLangsInUse } = this.state; - - return ( - - ); - } - - startDeleteField = field => { + startDeleteField = (field: ScriptedFieldItem) => { this.setState({ fieldToDelete: field, isDeleteConfirmationModalVisible: true }); }; @@ -124,67 +126,47 @@ export class ScriptedFieldsTable extends Component { const { fieldToDelete } = this.state; indexPattern.removeScriptedField(fieldToDelete); - onRemoveField && onRemoveField(); - this.fetchFields(); - this.hideDeleteConfirmationModal(); - }; - renderDeleteConfirmationModal() { - const { fieldToDelete } = this.state; - - if (!fieldToDelete) { - return null; + if (onRemoveField) { + onRemoveField(); } - const title = i18n.translate('kbn.management.editIndexPattern.scripted.deleteFieldLabel', { - defaultMessage: "Delete scripted field '{fieldName}'?", - values: { fieldName: fieldToDelete.name }, - }); - const cancelButtonText = i18n.translate( - 'kbn.management.editIndexPattern.scripted.deleteField.cancelButton', - { defaultMessage: 'Cancel' } - ); - const confirmButtonText = i18n.translate( - 'kbn.management.editIndexPattern.scripted.deleteField.deleteButton', - { defaultMessage: 'Delete' } - ); - - return ( - - - - ); - } + this.fetchFields(); + this.hideDeleteConfirmationModal(); + }; render() { const { helpers, indexPattern } = this.props; + const { fieldToDelete, deprecatedLangsInUse } = this.state; const items = this.getFilteredItems(); return ( -
+ <>
- {this.renderCallOuts()} +
this.props.helpers.redirectToRoute(field, 'edit')} + editField={field => this.props.helpers.redirectToRoute(field)} deleteField={this.startDeleteField} /> - {this.renderDeleteConfirmationModal()} - + {fieldToDelete && ( + + )} + ); } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts new file mode 100644 index 0000000000000..c1227393c561f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** @internal **/ +export interface ScriptedFieldItem { + name: string; + lang: string; + script: string; +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap similarity index 82% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap index 52fccb8607a83..a7b73624c4665 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SourceFiltersTable should add a filter 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should filter based on the query bar 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should remove a filter 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should render normally 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should should a loading indicator when saving 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should show a delete modal 1`] = ` -
+
- - - } - confirmButtonText={ - - } - onCancel={[Function]} - onConfirm={[Function]} - title={ - - } - /> - -
+ + `; exports[`SourceFiltersTable should update a filter 1`] = ` -
+
-
+ `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js deleted file mode 100644 index 2124b76b3a915..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js +++ /dev/null @@ -1,73 +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, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButton } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export class AddFilter extends Component { - static propTypes = { - onAddFilter: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - this.state = { - filter: '', - }; - } - - onAddFilter = () => { - this.props.onAddFilter(this.state.filter); - this.setState({ filter: '' }); - }; - - render() { - const { filter } = this.state; - const placeholder = i18n.translate('kbn.management.editIndexPattern.sourcePlaceholder', { - defaultMessage: - "source filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", - }); - - return ( - - - this.setState({ filter: e.target.value.trim() })} - placeholder={placeholder} - /> - - - - - - - - ); - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx similarity index 66% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx index 915d9490db045..1ebaa3eaf89f8 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx @@ -18,33 +18,30 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; -import { AddFilter } from '../add_filter'; +import { AddFilter } from './add_filter'; describe('AddFilter', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( {}} />); + test('should render normally', () => { + const component = shallow( {}} />); expect(component).toMatchSnapshot(); }); - it('should allow adding a filter', async () => { + test('should allow adding a filter', async () => { const onAddFilter = jest.fn(); - const component = shallowWithI18nProvider(); + const component = shallow(); - // Set a value in the input field - component.setState({ filter: 'tim*' }); - - // Click the button + component.find('EuiFieldText').simulate('change', { target: { value: 'tim*' } }); component.find('EuiButton').simulate('click'); component.update(); expect(onAddFilter).toBeCalledWith('tim*'); }); - it('should ignore strings with just spaces', async () => { - const component = shallowWithI18nProvider( {}} />); + test('should ignore strings with just spaces', () => { + const component = shallow( {}} />); // Set a value in the input field component.find('EuiFieldText').simulate('keypress', ' '); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx new file mode 100644 index 0000000000000..d0f397637de33 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useCallback } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButton } from '@elastic/eui'; + +interface AddFilterProps { + onAddFilter: (filter: string) => void; +} + +const sourcePlaceholder = i18n.translate('kbn.management.editIndexPattern.sourcePlaceholder', { + defaultMessage: + "source filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", +}); + +export const AddFilter = ({ onAddFilter }: AddFilterProps) => { + const [filter, setFilter] = useState(''); + + const onAddButtonClick = useCallback(() => { + onAddFilter(filter); + setFilter(''); + }, [filter, onAddFilter]); + + return ( + + + setFilter(e.target.value.trim())} + placeholder={sourcePlaceholder} + /> + + + + + + + + ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap new file mode 100644 index 0000000000000..62376b498d887 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Header should render normally 1`] = ` + + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } + /> + +`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx new file mode 100644 index 0000000000000..ac7237095e4b3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { DeleteFilterConfirmationModal } from './confirmation_modal'; + +describe('Header', () => { + test('should render normally', () => { + const component = shallow( + {}} + onDeleteFilter={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx new file mode 100644 index 0000000000000..dabfb6d8f275a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx @@ -0,0 +1,76 @@ +/* + * 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 PropTypes from 'prop-types'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; + +interface DeleteFilterConfirmationModalProps { + filterToDeleteValue: string; + onCancelConfirmationModal: ( + event?: React.KeyboardEvent | React.MouseEvent + ) => void; + onDeleteFilter: (event: React.MouseEvent) => void; +} + +export const DeleteFilterConfirmationModal = ({ + filterToDeleteValue, + onCancelConfirmationModal, + onDeleteFilter, +}: DeleteFilterConfirmationModalProps) => { + return ( + + + } + onCancel={onCancelConfirmationModal} + onConfirm={onDeleteFilter} + cancelButtonText={ + + } + buttonColor="danger" + confirmButtonText={ + + } + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + /> + + ); +}; + +DeleteFilterConfirmationModal.propTypes = { + filterToDeleteValue: PropTypes.string.isRequired, + onCancelConfirmationModal: PropTypes.func.isRequired, + onDeleteFilter: PropTypes.func.isRequired, +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts index e1195c6edfe31..e48e38b7c3dcb 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { Table } from './table'; +export { DeleteFilterConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap index a5be141a18c89..cde0de79caacd 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header should render normally 1`] = ` -
+ @@ -32,5 +32,5 @@ exports[`Header should render normally 1`] = ` -
+ `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx index 058bf99fe26fa..869bdeb55cf02 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; import { Header } from '../header'; describe('Header', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow(
); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx index 8822ca6236250..7b37f75043dd5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx @@ -20,11 +20,10 @@ import React from 'react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; export const Header = () => ( -
+ <>

(

-

+ ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts new file mode 100644 index 0000000000000..87ac13ad15f50 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { AddFilter } from './add_filter'; +export { DeleteFilterConfirmationModal } from './confirmation_modal'; +export { Header } from './header'; +export { Table } from './table'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap similarity index 77% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap index a0853b8628cc9..c70d0871bb854 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap @@ -3,14 +3,15 @@ exports[`Table editing should show a save button 1`] = `
@@ -18,27 +19,20 @@ exports[`Table editing should show a save button 1`] = ` `; exports[`Table editing should show an input field 1`] = ` - - - - - + + tim* + `; exports[`Table editing should update the matches dynamically as input value is changed 1`] = `
- - time, value - + + +
`; @@ -79,6 +73,7 @@ exports[`Table should render normally 1`] = ` items={ Array [ Object { + "clientId": "", "value": "tim*", }, ] diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx similarity index 69% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx index 7fba1fcfe4876..4705ecd2d1685 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx @@ -17,25 +17,40 @@ * under the License. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - -import { Table } from '../table'; -import { keyCodes } from '@elastic/eui'; - -const indexPattern = {}; -const items = [{ value: 'tim*' }]; +import React, { ReactElement } from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { Table, TableProps, TableState } from './table'; +import { EuiTableFieldDataColumnType, keyCodes } from '@elastic/eui'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; +import { SourceFiltersTableFilter } from '../../types'; + +const indexPattern = {} as IIndexPattern; +const items: SourceFiltersTableFilter[] = [{ value: 'tim*', clientId: '' }]; + +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); + +const getTableColumnRender = ( + component: ShallowWrapper, + index: number = 0 +) => { + const columns = component.prop>>( + 'columns' + ); + return { + render: columns[index].render as (...args: any) => ReactElement, + }; +}; describe('Table', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + test('should render normally', () => { + const component = shallow(
{}} fieldWildcardMatcher={() => {}} - saveFilter={() => {}} + saveFilter={() => undefined} isSaving={true} /> ); @@ -43,31 +58,33 @@ describe('Table', () => { expect(component).toMatchSnapshot(); }); - it('should render filter matches', async () => { - const component = shallowWithI18nProvider( + test('should render filter matches', () => { + const component = shallow
(
[{ name: 'time' }, { name: 'value' }], - }} + })} items={items} deleteFilter={() => {}} - fieldWildcardMatcher={filter => field => field.includes(filter[0])} - saveFilter={() => {}} + fieldWildcardMatcher={(filter: string) => (field: string) => field.includes(filter[0])} + saveFilter={() => undefined} isSaving={false} /> ); - const matchesTableCell = shallow(component.prop('columns')[1].render('tim', { clientId: 1 })); + const matchesTableCell = shallow( + getTableColumnRender(component, 1).render('tim', { clientId: 1 }) + ); expect(matchesTableCell).toMatchSnapshot(); }); describe('editing', () => { const saveFilter = jest.fn(); - const clientId = 1; - let component; + const clientId = '1'; + let component: ShallowWrapper; beforeEach(() => { - component = shallowWithI18nProvider( + component = shallow
(
{ ); }); - it('should show an input field', () => { + test('should show an input field', () => { // Start the editing process + const editingComponent = shallow( // Wrap in a div because: https://github.com/airbnb/enzyme/issues/1213 -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); editingComponent .find('EuiButtonIcon') @@ -92,19 +110,19 @@ describe('Table', () => { // Ensure the state change propagates component.update(); - // Ensure the table cell switches to an input - const filterNameTableCell = shallow( - component.prop('columns')[0].render('tim*', { clientId }) - ); + const cell = getTableColumnRender(component).render('tim*', { clientId }); + const filterNameTableCell = shallow(cell); + expect(filterNameTableCell).toMatchSnapshot(); }); - it('should show a save button', () => { + test('should show a save button', () => { // Start the editing process const editingComponent = shallow( // Fixes: Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(1) @@ -116,22 +134,20 @@ describe('Table', () => { // Verify save button const saveTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); expect(saveTableCell).toMatchSnapshot(); }); - it('should update the matches dynamically as input value is changed', () => { - const localComponent = shallowWithI18nProvider( + test('should update the matches dynamically as input value is changed', () => { + const localComponent = shallow(
[{ name: 'time' }, { name: 'value' }], - }} + })} items={items} deleteFilter={() => {}} - fieldWildcardMatcher={query => () => { - return query.includes('time*'); - }} + fieldWildcardMatcher={(query: string) => () => query.includes('time*')} saveFilter={saveFilter} isSaving={false} /> @@ -142,6 +158,7 @@ describe('Table', () => { // Fixes: Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`.
{localComponent.prop('columns')[2].render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(1) @@ -161,7 +178,7 @@ describe('Table', () => { expect(matchesTableCell).toMatchSnapshot(); }); - it('should exit on save', () => { + test('should exit on save', () => { // Change the value to something else component.setState({ editingFilterId: clientId, @@ -171,34 +188,37 @@ describe('Table', () => { // Click the save button const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(0) .simulate('click'); + editingComponent.update(); + // Ensure we call saveFilter properly expect(saveFilter).toBeCalledWith({ - filterId: clientId, - newFilterValue: 'ti*', + clientId, + value: 'ti*', }); // Ensure the state is properly reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); }); - it('should allow deletes', () => { + test('should allow deletes', () => { const deleteFilter = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} - saveFilter={() => {}} + saveFilter={() => undefined} isSaving={false} /> ); @@ -210,16 +230,15 @@ describe('Table', () => { ); deleteCellComponent .find('EuiButtonIcon') - .at(0) + .at(1) .simulate('click'); expect(deleteFilter).toBeCalled(); }); - it('should save when in edit mode and the enter key is pressed', () => { + test('should save when in edit mode and the enter key is pressed', () => { const saveFilter = jest.fn(); - const clientId = 1; - const component = shallowWithI18nProvider( + const component = shallow(
{ // Start the editing process const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); editingComponent .find('EuiButtonIcon') - .at(1) + .at(0) .simulate('click'); - // Ensure the state change propagates + component.update(); // Get the rendered input cell const filterNameTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[0].render('tim*', { clientId })}
+
{component.prop('columns')[0].render('tim*', { clientId: 1 })}
); // Press the enter key @@ -253,14 +272,13 @@ describe('Table', () => { expect(saveFilter).toBeCalled(); // It should reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); - it('should cancel when in edit mode and the esc key is pressed', () => { + test('should cancel when in edit mode and the esc key is pressed', () => { const saveFilter = jest.fn(); - const clientId = 1; - const component = shallowWithI18nProvider( + const component = shallow(
{ // Start the editing process const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') - .at(1) + .at(0) .simulate('click'); + // Ensure the state change propagates component.update(); // Get the rendered input cell const filterNameTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[0].render('tim*', { clientId })}
+
{component.prop('columns')[0].render('tim*', { clientId: 1 })}
); - // Press the enter key + // Press the ESCAPE key filterNameTableCell.find('EuiFieldText').simulate('keydown', { keyCode: keyCodes.ESCAPE }); expect(saveFilter).not.toBeCalled(); // It should reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx similarity index 54% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx index f16663e1cd41a..db2b74bbc9824 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx @@ -17,48 +17,94 @@ * under the License. */ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component } from 'react'; import { + keyCodes, + EuiBasicTableColumn, EuiInMemoryTable, EuiFieldText, EuiButtonIcon, - keyCodes, RIGHT_ALIGNMENT, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SourceFiltersTableFilter } from '../../types'; -export class Table extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - items: PropTypes.array.isRequired, - deleteFilter: PropTypes.func.isRequired, - fieldWildcardMatcher: PropTypes.func.isRequired, - saveFilter: PropTypes.func.isRequired, - isSaving: PropTypes.bool.isRequired, - }; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; + +const filterHeader = i18n.translate('kbn.management.editIndexPattern.source.table.filterHeader', { + defaultMessage: 'Filter', +}); + +const filterDescription = i18n.translate( + 'kbn.management.editIndexPattern.source.table.filterDescription', + { defaultMessage: 'Filter name' } +); + +const matchesHeader = i18n.translate('kbn.management.editIndexPattern.source.table.matchesHeader', { + defaultMessage: 'Matches', +}); + +const matchesDescription = i18n.translate( + 'kbn.management.editIndexPattern.source.table.matchesDescription', + { defaultMessage: 'Language used for the field' } +); + +const editAria = i18n.translate('kbn.management.editIndexPattern.source.table.editAria', { + defaultMessage: 'Edit', +}); + +const saveAria = i18n.translate('kbn.management.editIndexPattern.source.table.saveAria', { + defaultMessage: 'Save', +}); + +const deleteAria = i18n.translate('kbn.management.editIndexPattern.source.table.deleteAria', { + defaultMessage: 'Delete', +}); + +const cancelAria = i18n.translate('kbn.management.editIndexPattern.source.table.cancelAria', { + defaultMessage: 'Cancel', +}); + +export interface TableProps { + indexPattern: IIndexPattern; + items: SourceFiltersTableFilter[]; + deleteFilter: Function; + fieldWildcardMatcher: Function; + saveFilter: (filter: SourceFiltersTableFilter) => any; + isSaving: boolean; +} + +export interface TableState { + editingFilterId: string | number; + editingFilterValue: string; +} - constructor(props) { +export class Table extends Component { + constructor(props: TableProps) { super(props); this.state = { - editingFilterId: null, - editingFilterValue: null, + editingFilterId: '', + editingFilterValue: '', }; } - startEditingFilter = (id, value) => - this.setState({ editingFilterId: id, editingFilterValue: value }); - stopEditingFilter = () => this.setState({ editingFilterId: null }); - onEditingFilterChange = e => this.setState({ editingFilterValue: e.target.value }); + startEditingFilter = ( + editingFilterId: TableState['editingFilterId'], + editingFilterValue: TableState['editingFilterValue'] + ) => this.setState({ editingFilterId, editingFilterValue }); + + stopEditingFilter = () => this.setState({ editingFilterId: '' }); + onEditingFilterChange = (e: React.ChangeEvent) => + this.setState({ editingFilterValue: e.target.value }); - onEditFieldKeyDown = ({ keyCode }) => { - if (keyCodes.ENTER === keyCode) { + onEditFieldKeyDown = ({ keyCode }: React.KeyboardEvent) => { + if (keyCodes.ENTER === keyCode && this.state.editingFilterId && this.state.editingFilterValue) { this.props.saveFilter({ - filterId: this.state.editingFilterId, - newFilterValue: this.state.editingFilterValue, + clientId: this.state.editingFilterId, + value: this.state.editingFilterValue, }); this.stopEditingFilter(); } @@ -67,23 +113,18 @@ export class Table extends Component { } }; - getColumns() { + getColumns(): Array> { const { deleteFilter, fieldWildcardMatcher, indexPattern, saveFilter } = this.props; return [ { field: 'value', - name: i18n.translate('kbn.management.editIndexPattern.source.table.filterHeader', { - defaultMessage: 'Filter', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.source.table.filterDescription', - { defaultMessage: 'Filter name' } - ), + name: filterHeader, + description: filterDescription, dataType: 'string', sortable: true, render: (value, filter) => { - if (this.state.editingFilterId === filter.clientId) { + if (this.state.editingFilterId && this.state.editingFilterId === filter.clientId) { return ( { - const realtimeValue = - this.state.editingFilterId === filter.clientId ? this.state.editingFilterValue : value; - const matcher = fieldWildcardMatcher([realtimeValue]); + const wildcardMatcher = fieldWildcardMatcher([ + this.state.editingFilterId === filter.clientId ? this.state.editingFilterValue : value, + ]); const matches = indexPattern .getNonScriptedFields() - .map(f => f.name) - .filter(matcher) + .map((currentFilter: any) => currentFilter.name) + .filter(wildcardMatcher) .sort(); + if (matches.length) { return {matches.join(', ')}; } @@ -135,24 +172,21 @@ export class Table extends Component { name: '', align: RIGHT_ALIGNMENT, width: '100', - render: filter => { + render: (filter: SourceFiltersTableFilter) => { if (this.state.editingFilterId === filter.clientId) { return ( - + <> { saveFilter({ - filterId: this.state.editingFilterId, - newFilterValue: this.state.editingFilterValue, + clientId: this.state.editingFilterId, + value: this.state.editingFilterValue, }); this.stopEditingFilter(); }} iconType="checkInCircleFilled" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.saveAria', - { defaultMessage: 'Save' } - )} + aria-label={saveAria} /> - + ); } return ( - + <> deleteFilter(filter)} - iconType="trash" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.deleteAria', - { defaultMessage: 'Delete' } - )} + onClick={() => this.startEditingFilter(filter.clientId, filter.value)} + iconType="pencil" + aria-label={editAria} /> this.startEditingFilter(filter.clientId, filter.value)} - iconType="pencil" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.editAria', - { defaultMessage: 'Edit' } - )} + color="danger" + onClick={() => deleteFilter(filter)} + iconType="trash" + aria-label={deleteAria} /> - + ); }, }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx similarity index 64% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx index a39958a77abbf..1b68dd13566d3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx @@ -20,13 +20,13 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SourceFiltersTable } from '../source_filters_table'; +import { SourceFiltersTable } from './source_filters_table'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; jest.mock('@elastic/eui', () => ({ EuiButton: 'eui-button', EuiTitle: 'eui-title', EuiText: 'eui-text', - EuiButton: 'eui-button', EuiHorizontalRule: 'eui-horizontal-rule', EuiSpacer: 'eui-spacer', EuiCallOut: 'eui-call-out', @@ -39,42 +39,54 @@ jest.mock('@elastic/eui', () => ({ default: () => {}, }, })); -jest.mock('../components/header', () => ({ Header: 'header' })); -jest.mock('../components/table', () => ({ + +jest.mock('./components/header', () => ({ Header: 'header' })); +jest.mock('./components/table', () => ({ // Note: this seems to fix React complaining about non lowercase attributes Table: () => { return 'table'; }, })); -const indexPattern = { - sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], -}; +const getIndexPatternMock = (mockedFields: any = {}) => + ({ + sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], + ...mockedFields, + } as IIndexPattern); describe('SourceFiltersTable', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow( - {}} /> + {}} + filterFilter={''} + /> ); expect(component).toMatchSnapshot(); }); - it('should filter based on the query bar', async () => { + test('should filter based on the query bar', () => { const component = shallow( - {}} /> + {}} + filterFilter={''} + /> ); component.setProps({ filterFilter: 'ti' }); expect(component).toMatchSnapshot(); }); - it('should should a loading indicator when saving', async () => { + test('should should a loading indicator when saving', () => { const component = shallow( {}} /> ); @@ -83,34 +95,36 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should show a delete modal', async () => { - const component = shallow( + test('should show a delete modal', () => { + const component = shallow( {}} /> ); - component.instance().startDeleteFilter({ value: 'tim*' }); + component.instance().startDeleteFilter({ value: 'tim*', clientId: 1 }); component.update(); // We are not calling `.setState` directly so we need to re-render expect(component).toMatchSnapshot(); }); - it('should remove a filter', async () => { + test('should remove a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); - component.instance().startDeleteFilter({ value: 'tim*' }); + component.instance().startDeleteFilter({ value: 'tim*', clientId: 1 }); component.update(); // We are not calling `.setState` directly so we need to re-render await component.instance().deleteFilter(); component.update(); // We are not calling `.setState` directly so we need to re-render @@ -119,14 +133,15 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should add a filter', async () => { + test('should add a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); @@ -138,19 +153,20 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should update a filter', async () => { + test('should update a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); - await component.instance().saveFilter({ oldFilterValue: 'tim*', newFilterValue: 'ti*' }); + await component.instance().saveFilter({ clientId: 'tim*', value: 'ti*' }); component.update(); // We are not calling `.setState` directly so we need to re-render expect(save).toBeCalled(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx similarity index 56% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx index 3b485573f3821..dcf8ae9e1323f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -18,33 +18,40 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { createSelector } from 'reselect'; -import { EuiSpacer, EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; +import { AddFilter, Table, Header, DeleteFilterConfirmationModal } from './components'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; +import { SourceFiltersTableFilter } from './types'; -import { Table } from './components/table'; -import { Header } from './components/header'; -import { AddFilter } from './components/add_filter'; -import { FormattedMessage } from '@kbn/i18n/react'; +export interface SourceFiltersTableProps { + indexPattern: IIndexPattern; + filterFilter: string; + fieldWildcardMatcher: Function; + onAddOrRemoveFilter?: Function; +} -export class SourceFiltersTable extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - filterFilter: PropTypes.string, - fieldWildcardMatcher: PropTypes.func.isRequired, - onAddOrRemoveFilter: PropTypes.func, - }; +export interface SourceFiltersTableState { + filterToDelete: any; + isDeleteConfirmationModalVisible: boolean; + isSaving: boolean; + filters: SourceFiltersTableFilter[]; +} - constructor(props) { +export class SourceFiltersTable extends Component< + SourceFiltersTableProps, + SourceFiltersTableState +> { + // Source filters do not have any unique ids, only the value is stored. + // To ensure we can create a consistent and expected UX when managing + // source filters, we are assigning a unique id to each filter on the + // client side only + private clientSideId: number = 0; + + constructor(props: SourceFiltersTableProps) { super(props); - // Source filters do not have any unique ids, only the value is stored. - // To ensure we can create a consistent and expected UX when managing - // source filters, we are assigning a unique id to each filter on the - // client side only - this.clientSideId = 0; - this.state = { filterToDelete: undefined, isDeleteConfirmationModalVisible: false, @@ -58,9 +65,9 @@ export class SourceFiltersTable extends Component { } updateFilters = () => { - const sourceFilters = this.props.indexPattern.sourceFilters || []; - const filters = sourceFilters.map(filter => ({ - ...filter, + const sourceFilters = this.props.indexPattern.sourceFilters; + const filters = (sourceFilters || []).map((sourceFilter: any) => ({ + ...sourceFilter, clientId: ++this.clientSideId, })); @@ -68,8 +75,8 @@ export class SourceFiltersTable extends Component { }; getFilteredFilters = createSelector( - state => state.filters, - (state, props) => props.filterFilter, + (state: SourceFiltersTableState) => state.filters, + (state: SourceFiltersTableState, props: SourceFiltersTableProps) => props.filterFilter, (filters, filterFilter) => { if (filterFilter) { const filterFilterToLowercase = filterFilter.toLowerCase(); @@ -82,7 +89,7 @@ export class SourceFiltersTable extends Component { } ); - startDeleteFilter = filter => { + startDeleteFilter = (filter: SourceFiltersTableFilter) => { this.setState({ filterToDelete: filter, isDeleteConfirmationModalVisible: true, @@ -106,35 +113,44 @@ export class SourceFiltersTable extends Component { this.setState({ isSaving: true }); await indexPattern.save(); - onAddOrRemoveFilter && onAddOrRemoveFilter(); + + if (onAddOrRemoveFilter) { + onAddOrRemoveFilter(); + } + this.updateFilters(); this.setState({ isSaving: false }); this.hideDeleteConfirmationModal(); }; - onAddFilter = async value => { + onAddFilter = async (value: string) => { const { indexPattern, onAddOrRemoveFilter } = this.props; indexPattern.sourceFilters = [...(indexPattern.sourceFilters || []), { value }]; this.setState({ isSaving: true }); await indexPattern.save(); - onAddOrRemoveFilter && onAddOrRemoveFilter(); + + if (onAddOrRemoveFilter) { + onAddOrRemoveFilter(); + } + this.updateFilters(); this.setState({ isSaving: false }); }; - saveFilter = async ({ filterId, newFilterValue }) => { + saveFilter = async ({ clientId, value }: SourceFiltersTableFilter) => { const { indexPattern } = this.props; const { filters } = this.state; indexPattern.sourceFilters = filters.map(filter => { - if (filter.clientId === filterId) { + if (filter.clientId === clientId) { return { - value: newFilterValue, - clientId: filter.clientId, + value, + clientId, }; } + return filter; }); @@ -144,55 +160,13 @@ export class SourceFiltersTable extends Component { this.setState({ isSaving: false }); }; - renderDeleteConfirmationModal() { - const { filterToDelete } = this.state; - - if (!filterToDelete) { - return null; - } - - return ( - - - } - onCancel={this.hideDeleteConfirmationModal} - onConfirm={this.deleteFilter} - cancelButtonText={ - - } - buttonColor="danger" - confirmButtonText={ - - } - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - /> - - ); - } - render() { const { indexPattern, fieldWildcardMatcher } = this.props; - - const { isSaving } = this.state; - + const { isSaving, filterToDelete } = this.state; const filteredFilters = this.getFilteredFilters(this.state, this.props); return ( -
+ <>
@@ -205,8 +179,14 @@ export class SourceFiltersTable extends Component { saveFilter={this.saveFilter} /> - {this.renderDeleteConfirmationModal()} -
+ {filterToDelete && ( + + )} + ); } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts new file mode 100644 index 0000000000000..ee3689f017471 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** @internal **/ +export interface SourceFiltersTableFilter { + value: string; + clientId: string | number; +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.html b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.html deleted file mode 100644 index 090fb7b636685..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.html +++ /dev/null @@ -1,5 +0,0 @@ - - -
-
-
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js deleted file mode 100644 index c5901ca6ee6bf..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js +++ /dev/null @@ -1,104 +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 { savedObjectManagementRegistry } from '../../saved_object_registry'; -import objectIndexHTML from './_objects.html'; -import uiRoutes from 'ui/routes'; -import chrome from 'ui/chrome'; -import { uiModules } from 'ui/modules'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { ObjectsTable } from './components/objects_table'; -import { I18nContext } from 'ui/i18n'; -import { get } from 'lodash'; -import { npStart } from 'ui/new_platform'; -import { getIndexBreadcrumbs } from './breadcrumbs'; - -const REACT_OBJECTS_TABLE_DOM_ELEMENT_ID = 'reactSavedObjectsTable'; - -function updateObjectsTable($scope, $injector) { - const indexPatterns = npStart.plugins.data.indexPatterns; - const $http = $injector.get('$http'); - const kbnUrl = $injector.get('kbnUrl'); - const config = $injector.get('config'); - - const savedObjectsClient = npStart.core.savedObjects.client; - const services = savedObjectManagementRegistry.all().map(obj => obj.service); - const uiCapabilites = npStart.core.application.capabilities; - - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_OBJECTS_TABLE_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - { - if (object.meta.editUrl) { - kbnUrl.change(object.meta.editUrl); - $scope.$apply(); - } - }} - canGoInApp={object => { - const { inAppUrl } = object.meta; - return inAppUrl && get(uiCapabilites, inAppUrl.uiCapabilitiesPath); - }} - /> - , - node - ); - }); -} - -function destroyObjectsTable() { - const node = document.getElementById(REACT_OBJECTS_TABLE_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -uiRoutes - .when('/management/kibana/objects', { - template: objectIndexHTML, - k7Breadcrumbs: getIndexBreadcrumbs, - requireUICapability: 'management.kibana.objects', - }) - .when('/management/kibana/objects/:service', { - redirectTo: '/management/kibana/objects', - }); - -uiModules.get('apps/management').directive('kbnManagementObjects', function() { - return { - restrict: 'E', - controllerAs: 'managementObjectsController', - controller: function($scope, $injector) { - updateObjectsTable($scope, $injector); - $scope.$on('$destroy', destroyObjectsTable); - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html deleted file mode 100644 index 8bce0aabcd64a..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html +++ /dev/null @@ -1,5 +0,0 @@ - - -
-
-
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js deleted file mode 100644 index a847055b40015..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js +++ /dev/null @@ -1,85 +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 { render, unmountComponentAtNode } from 'react-dom'; -import 'angular'; -import 'angular-elastic/elastic'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; -import { I18nContext } from 'ui/i18n'; -import { npStart } from 'ui/new_platform'; -import objectViewHTML from './_view.html'; -import { getViewBreadcrumbs } from './breadcrumbs'; -import { savedObjectManagementRegistry } from '../../saved_object_registry'; -import { SavedObjectEdition } from './saved_object_view'; - -const REACT_OBJECTS_VIEW_DOM_ELEMENT_ID = 'reactSavedObjectsView'; - -uiRoutes.when('/management/kibana/objects/:service/:id', { - template: objectViewHTML, - k7Breadcrumbs: getViewBreadcrumbs, - requireUICapability: 'management.kibana.objects', -}); - -function createReactView($scope, $routeParams) { - const { service: serviceName, id: objectId, notFound } = $routeParams; - - const { savedObjects, overlays, notifications, application } = npStart.core; - - $scope.$$postDigest(() => { - const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID); - if (!node) { - return; - } - - render( - - - , - node - ); - }); -} - -function destroyReactView() { - const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID); - node && unmountComponentAtNode(node); -} - -uiModules - .get('apps/management', ['monospaced.elastic']) - .directive('kbnManagementObjectsView', function() { - return { - restrict: 'E', - controller: function($scope, $routeParams) { - createReactView($scope, $routeParams); - $scope.$on('$destroy', destroyReactView); - }, - }; - }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/breadcrumbs.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/breadcrumbs.js deleted file mode 100644 index e9082bfeb680d..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/breadcrumbs.js +++ /dev/null @@ -1,50 +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 { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { i18n } from '@kbn/i18n'; - -import { savedObjectManagementRegistry } from '../../saved_object_registry'; - -export function getIndexBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('kbn.management.savedObjects.indexBreadcrumb', { - defaultMessage: 'Saved objects', - }), - href: '#/management/kibana/objects', - }, - ]; -} - -export function getViewBreadcrumbs($routeParams) { - const serviceObj = savedObjectManagementRegistry.get($routeParams.service); - const { service } = serviceObj; - - return [ - ...getIndexBreadcrumbs(), - { - text: i18n.translate('kbn.management.savedObjects.editBreadcrumb', { - defaultMessage: 'Edit {savedObjectType}', - values: { savedObjectType: service.type }, - }), - }, - ]; -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/index.js deleted file mode 100644 index 601dea544361c..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/index.js +++ /dev/null @@ -1,20 +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. - */ - -export { ObjectsTable } from './objects_table'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js deleted file mode 100644 index 3965c42ac088d..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js +++ /dev/null @@ -1,36 +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 { i18n } from '@kbn/i18n'; -import { management } from 'ui/management'; -import './_view'; -import './_objects'; -import 'ace'; -import { uiModules } from 'ui/modules'; - -// add the module deps to this module -uiModules.get('apps/management'); - -management.getSection('kibana').register('objects', { - display: i18n.translate('kbn.management.objects.savedObjectsSectionLabel', { - defaultMessage: 'Saved Objects', - }), - order: 10, - url: '#/management/kibana/objects', -}); diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index 3880f42d52561..6e1b0b7160941 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -23,12 +23,18 @@ import _ from 'lodash'; import ChoroplethLayer from '../choropleth_layer'; import { ImageComparator } from 'test_utils/image_comparator'; import worldJson from './world.json'; -import EMS_CATALOGUE from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json'; -import EMS_FILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_files.json'; -import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json'; -import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright'; -import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated'; -import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_CATALOGUE from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_FILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_files.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_TILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_DARK_MAP from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark'; import initialPng from './initial.png'; import toiso3Png from './toiso3.png'; @@ -44,6 +50,10 @@ import { createRegionMapTypeDefinition } from '../region_map_type'; import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setInjectedVarFunc } from '../../../../../plugins/maps_legacy/public/kibana_services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ServiceSettings } from '../../../../../plugins/maps_legacy/public/map/service_settings'; const THRESHOLD = 0.45; const PIXEL_DIFF = 96; @@ -92,7 +102,31 @@ describe('RegionMapsVisualizationTests', function() { let getManifestStub; beforeEach( ngMock.inject((Private, $injector) => { - const serviceSettings = $injector.get('serviceSettings'); + setInjectedVarFunc(injectedVar => { + switch (injectedVar) { + case 'mapConfig': + return { + emsFileApiUrl: '', + emsTileApiUrl: '', + emsLandingPageUrl: '', + }; + case 'tilemapsConfig': + return { + deprecated: { + config: { + options: { + attribution: '123', + }, + }, + }, + }; + case 'version': + return '123'; + default: + return 'not found'; + } + }); + const serviceSettings = new ServiceSettings(); const uiSettings = $injector.get('config'); const regionmapsConfig = { includeElasticMapsService: true, diff --git a/src/legacy/core_plugins/region_map/public/choropleth_layer.js b/src/legacy/core_plugins/region_map/public/choropleth_layer.js index e637a217bfbc3..4ea9cc1f7bfbf 100644 --- a/src/legacy/core_plugins/region_map/public/choropleth_layer.js +++ b/src/legacy/core_plugins/region_map/public/choropleth_layer.js @@ -22,11 +22,9 @@ import L from 'leaflet'; import _ from 'lodash'; import d3 from 'd3'; import { i18n } from '@kbn/i18n'; -import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; import * as topojson from 'topojson-client'; import { toastNotifications } from 'ui/notify'; -import * as colorUtil from 'ui/vis/map/color_util'; - +import { colorUtil, KibanaMapLayer } from '../../../../plugins/maps_legacy/public'; import { truncatedColorMaps } from '../../../../plugins/charts/public'; const EMPTY_STYLE = { diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 187b27953830d..31a27c4da7fcf 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -21,9 +21,12 @@ import React, { useCallback, useMemo } from 'react'; import { EuiIcon, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { + FileLayerField, + VectorLayer, + IServiceSettings, +} from '../../../../../plugins/maps_legacy/public'; import { NumberInputOption, SelectOption, @@ -43,7 +46,7 @@ const mapFieldForOption = ({ description, name }: FileLayerField) => ({ }); export type RegionMapOptionsProps = { - serviceSettings: ServiceSettings; + serviceSettings: IServiceSettings; } & VisOptionsProps; function RegionMapOptions(props: RegionMapOptionsProps) { diff --git a/src/legacy/core_plugins/region_map/public/legacy.ts b/src/legacy/core_plugins/region_map/public/legacy.ts index 08615946affa2..b0cc767a044e8 100644 --- a/src/legacy/core_plugins/region_map/public/legacy.ts +++ b/src/legacy/core_plugins/region_map/public/legacy.ts @@ -20,21 +20,18 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { RegionMapPluginSetupDependencies, RegionMapsConfig } from './plugin'; +import { RegionMapPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; -const regionmapsConfig = npSetup.core.injectedMetadata.getInjectedVar( - 'regionmap' -) as RegionMapsConfig; - const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: npSetup.plugins.visualizations, + mapsLegacy: npSetup.plugins.mapsLegacy, // Temporary solution // It will be removed when all dependent services are migrated to the new platform. - __LEGACY: new LegacyDependenciesPlugin(regionmapsConfig), + __LEGACY: new LegacyDependenciesPlugin(), }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index cae569f8fd26d..1453c2155e2d6 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -32,10 +32,14 @@ import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim' import { createRegionMapFn } from './region_map_fn'; // @ts-ignore import { createRegionMapTypeDefinition } from './region_map_type'; +import { IServiceSettings, MapsLegacyPluginSetup } from '../../../../plugins/maps_legacy/public'; /** @private */ interface RegionMapVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; + regionmapsConfig: RegionMapsConfig; + serviceSettings: IServiceSettings; + notificationService: any; } /** @internal */ @@ -43,6 +47,7 @@ export interface RegionMapPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; __LEGACY: LegacyDependenciesPlugin; + mapsLegacy: MapsLegacyPluginSetup; } /** @internal */ @@ -61,10 +66,13 @@ export class RegionMapPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, __LEGACY }: RegionMapPluginSetupDependencies + { expressions, visualizations, mapsLegacy, __LEGACY }: RegionMapPluginSetupDependencies ) { const visualizationDependencies: Readonly = { uiSettings: core.uiSettings, + regionmapsConfig: core.injectedMetadata.getInjectedVar('regionmap') as RegionMapsConfig, + serviceSettings: mapsLegacy.serviceSettings, + notificationService: core.notifications.toasts, ...(await __LEGACY.setup()), }; diff --git a/src/legacy/core_plugins/region_map/public/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/region_map_visualization.js index 72f9d66e7d2bf..f08d53ee35c8d 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/region_map_visualization.js @@ -28,8 +28,16 @@ import { truncatedColorMaps } from '../../../../plugins/charts/public'; // TODO: reference to TILE_MAP plugin should be removed import { BaseMapsVisualizationProvider } from '../../tile_map/public/base_maps_visualization'; -export function createRegionMapVisualization({ serviceSettings, $injector, uiSettings }) { - const BaseMapsVisualization = new BaseMapsVisualizationProvider(serviceSettings); +export function createRegionMapVisualization({ + serviceSettings, + $injector, + uiSettings, + notificationService, +}) { + const BaseMapsVisualization = new BaseMapsVisualizationProvider( + serviceSettings, + notificationService + ); const tooltipFormatter = new TileMapTooltipFormatter($injector); return class RegionMapsVisualization extends BaseMapsVisualization { diff --git a/src/legacy/core_plugins/region_map/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/region_map/public/shim/legacy_dependencies_plugin.ts index c47fc40fbacd7..3a7615e83f281 100644 --- a/src/legacy/core_plugins/region_map/public/shim/legacy_dependencies_plugin.ts +++ b/src/legacy/core_plugins/region_map/public/shim/legacy_dependencies_plugin.ts @@ -19,31 +19,20 @@ import chrome from 'ui/chrome'; import { CoreStart, Plugin } from 'kibana/public'; -import 'ui/vis/map/service_settings'; -import { RegionMapsConfig } from '../plugin'; /** @internal */ export interface LegacyDependenciesPluginSetup { $injector: any; serviceSettings: any; - regionmapsConfig: RegionMapsConfig; } export class LegacyDependenciesPlugin implements Plugin, void> { - constructor(private readonly regionmapsConfig: RegionMapsConfig) {} - public async setup() { const $injector = await chrome.dangerouslyGetActiveInjector(); return { $injector, - regionmapsConfig: this.regionmapsConfig, - // Settings for EMSClient. - // EMSClient, which currently lives in the tile_map vis, - // will probably end up being exposed from the future vis_type_maps plugin, - // which would register both the tile_map and the region_map vis plugins. - serviceSettings: $injector.get('serviceSettings'), } as LegacyDependenciesPluginSetup; } diff --git a/src/legacy/core_plugins/region_map/public/types.ts b/src/legacy/core_plugins/region_map/public/types.ts index 2097aebd27ce0..8585bf720e0cf 100644 --- a/src/legacy/core_plugins/region_map/public/types.ts +++ b/src/legacy/core_plugins/region_map/public/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VectorLayer, FileLayerField } from 'ui/vis/map/service_settings'; +import { VectorLayer, FileLayerField } from '../../../../plugins/maps_legacy/public'; import { WMSOptions } from '../../tile_map/public/types'; export interface RegionMapVisParams { diff --git a/src/legacy/core_plugins/region_map/public/util.ts b/src/legacy/core_plugins/region_map/public/util.ts index 69a7a1815bc8e..24c721da1f31a 100644 --- a/src/legacy/core_plugins/region_map/public/util.ts +++ b/src/legacy/core_plugins/region_map/public/util.ts @@ -17,7 +17,7 @@ * under the License. */ -import { FileLayer, VectorLayer } from 'ui/vis/map/service_settings'; +import { FileLayer, VectorLayer } from '../../../../plugins/maps_legacy/public'; // TODO: reference to TILE_MAP plugin should be removed import { ORIGIN } from '../../../../legacy/core_plugins/tile_map/common/origin'; diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index 2c142b19d9096..3904c43707906 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -25,12 +25,18 @@ import initial from './initial.png'; import blues from './blues.png'; import shadedGeohashGrid from './shadedGeohashGrid.png'; import heatmapRaw from './heatmap_raw.png'; -import EMS_CATALOGUE from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json'; -import EMS_FILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_files.json'; -import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json'; -import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright'; -import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated'; -import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_CATALOGUE from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_FILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_files.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_TILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import EMS_STYLE_DARK_MAP from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark'; import { createTileMapVisualization } from '../tile_map_visualization'; import { createTileMapTypeDefinition } from '../tile_map_type'; @@ -38,6 +44,15 @@ import { createTileMapTypeDefinition } from '../tile_map_type'; import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +import { + getPrecision, + getZoomPrecision, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps_legacy/public/map/precision'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ServiceSettings } from '../../../../../plugins/maps_legacy/public/map/service_settings'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setInjectedVarFunc } from '../../../../../plugins/maps_legacy/public/kibana_services'; function mockRawData() { const stack = [dummyESResponse]; @@ -75,13 +90,39 @@ describe('CoordinateMapsVisualizationTest', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject((Private, $injector) => { - const serviceSettings = $injector.get('serviceSettings'); + setInjectedVarFunc(injectedVar => { + switch (injectedVar) { + case 'mapConfig': + return { + emsFileApiUrl: '', + emsTileApiUrl: '', + emsLandingPageUrl: '', + }; + case 'tilemapsConfig': + return { + deprecated: { + config: { + options: { + attribution: '123', + }, + }, + }, + }; + case 'version': + return '123'; + default: + return 'not found'; + } + }); + const serviceSettings = new ServiceSettings(); const uiSettings = $injector.get('config'); dependencies = { serviceSettings, uiSettings, $injector, + getPrecision, + getZoomPrecision, }; visType = new BaseVisType(createTileMapTypeDefinition(dependencies)); diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js b/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js index 857432079e376..fc029d6bccb6e 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js @@ -18,13 +18,13 @@ */ import expect from '@kbn/expect'; -import { KibanaMap } from 'ui/vis/map/kibana_map'; import { GeohashLayer } from '../geohash_layer'; // import heatmapPng from './heatmap.png'; import scaledCircleMarkersPng from './scaledCircleMarkers.png'; // import shadedCircleMarkersPng from './shadedCircleMarkers.png'; import { ImageComparator } from 'test_utils/image_comparator'; import GeoHashSampleData from './dummy_es_response.json'; +import { KibanaMap } from '../../../../../plugins/maps_legacy/public'; describe('geohash_layer', function() { let domNode; diff --git a/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js index d38159c91ef9f..1dac4607280cc 100644 --- a/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js @@ -19,22 +19,25 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { KibanaMap } from 'ui/vis/map/kibana_map'; +import { KibanaMap } from '../../../../plugins/maps_legacy/public'; import * as Rx from 'rxjs'; import { filter, first } from 'rxjs/operators'; -import 'ui/vis/map/service_settings'; import { toastNotifications } from 'ui/notify'; import chrome from 'ui/chrome'; const WMS_MINZOOM = 0; const WMS_MAXZOOM = 22; //increase this to 22. Better for WMS -export function BaseMapsVisualizationProvider(serviceSettings) { +export function BaseMapsVisualizationProvider(mapServiceSettings, notificationService) { /** * Abstract base class for a visualization consisting of a map with a single baselayer. * @class BaseMapsVisualization * @constructor */ + + const serviceSettings = mapServiceSettings; + const toastService = notificationService; + return class BaseMapsVisualization { constructor(element, vis) { this.vis = vis; @@ -94,8 +97,9 @@ export function BaseMapsVisualizationProvider(serviceSettings) { const centerFromUIState = uiState.get('mapCenter'); options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.params.mapZoom; options.center = centerFromUIState ? centerFromUIState : this.vis.params.mapCenter; + const services = { toastService }; - this._kibanaMap = new KibanaMap(this._container, options); + this._kibanaMap = new KibanaMap(this._container, options, services); this._kibanaMap.setMinZoom(WMS_MINZOOM); //use a default this._kibanaMap.setMaxZoom(WMS_MAXZOOM); //use a default diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index b8535e72e8818..e74c260d3b8e5 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -21,8 +21,7 @@ import React, { useMemo } from 'react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { TmsLayer } from 'ui/vis/map/service_settings'; +import { TmsLayer } from '../../../../../plugins/maps_legacy/public'; import { Vis } from '../../../../../plugins/visualizations/public'; import { RegionMapVisParams } from '../../../region_map/public/types'; import { SelectOption, SwitchOption } from '../../../../../plugins/charts/public'; diff --git a/src/legacy/core_plugins/tile_map/public/geohash_layer.js b/src/legacy/core_plugins/tile_map/public/geohash_layer.js index a604e02be7c8c..b9acf1a15208f 100644 --- a/src/legacy/core_plugins/tile_map/public/geohash_layer.js +++ b/src/legacy/core_plugins/tile_map/public/geohash_layer.js @@ -20,8 +20,7 @@ import L from 'leaflet'; import { min, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; - -import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; +import { KibanaMapLayer } from '../../../../plugins/maps_legacy/public'; import { HeatmapMarkers } from './markers/heatmap'; import { ScaledCirclesMarkers } from './markers/scaled_circles'; import { ShadedCirclesMarkers } from './markers/shaded_circles'; diff --git a/src/legacy/core_plugins/tile_map/public/legacy.ts b/src/legacy/core_plugins/tile_map/public/legacy.ts index 7b1f916076f61..741e118750f32 100644 --- a/src/legacy/core_plugins/tile_map/public/legacy.ts +++ b/src/legacy/core_plugins/tile_map/public/legacy.ts @@ -27,6 +27,7 @@ import { plugin } from '.'; const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: npSetup.plugins.visualizations, + mapsLegacy: npSetup.plugins.mapsLegacy, // Temporary solution // It will be removed when all dependent services are migrated to the new platform. diff --git a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js index 88d6db82946c7..f39de6ca7d179 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js +++ b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js @@ -22,8 +22,7 @@ import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { EventEmitter } from 'events'; -import * as colorUtil from 'ui/vis/map/color_util'; - +import { colorUtil } from '../../../../../plugins/maps_legacy/public'; import { truncatedColorMaps } from '../../../../../plugins/charts/public'; export class ScaledCirclesMarkers extends EventEmitter { diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index f2addbe3ab872..2b97407b17b38 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -32,16 +32,22 @@ import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim' import { createTileMapFn } from './tile_map_fn'; // @ts-ignore import { createTileMapTypeDefinition } from './tile_map_type'; +import { IServiceSettings, MapsLegacyPluginSetup } from '../../../../plugins/maps_legacy/public'; /** @private */ interface TileMapVisualizationDependencies extends LegacyDependenciesPluginSetup { + serviceSettings: IServiceSettings; uiSettings: IUiSettingsClient; + getZoomPrecision: any; + getPrecision: any; + notificationService: any; } /** @internal */ export interface TileMapPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; + mapsLegacy: MapsLegacyPluginSetup; __LEGACY: LegacyDependenciesPlugin; } @@ -55,9 +61,14 @@ export class TileMapPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, __LEGACY }: TileMapPluginSetupDependencies + { expressions, visualizations, mapsLegacy, __LEGACY }: TileMapPluginSetupDependencies ) { + const { getZoomPrecision, getPrecision, serviceSettings } = mapsLegacy; const visualizationDependencies: Readonly = { + serviceSettings, + getZoomPrecision, + getPrecision, + notificationService: core.notifications.toasts, uiSettings: core.uiSettings, ...(await __LEGACY.setup()), }; diff --git a/src/legacy/core_plugins/tile_map/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/tile_map/public/shim/legacy_dependencies_plugin.ts index 063b12bf0a2db..5296e98b09efe 100644 --- a/src/legacy/core_plugins/tile_map/public/shim/legacy_dependencies_plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/shim/legacy_dependencies_plugin.ts @@ -18,12 +18,12 @@ */ import chrome from 'ui/chrome'; -import 'ui/vis/map/service_settings'; import { CoreStart, Plugin } from 'kibana/public'; +// TODO: Determine why visualizations don't populate without this +import 'angular-sanitize'; /** @internal */ export interface LegacyDependenciesPluginSetup { - serviceSettings: any; $injector: any; } @@ -34,11 +34,6 @@ export class LegacyDependenciesPlugin return { $injector, - // Settings for EMSClient. - // EMSClient, which currently lives in the tile_map vis, - // will probably end up being exposed from the future vis_type_maps plugin, - // which would register both the tile_map and the region_map vis plugins. - serviceSettings: $injector.get('serviceSettings'), } as LegacyDependenciesPluginSetup; } diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_fn.js b/src/legacy/core_plugins/tile_map/public/tile_map_fn.js index 2f54d23590c33..5ad4a2c33db25 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_fn.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_fn.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; +import { convertToGeoJson } from '../../../../plugins/maps_legacy/public'; import { i18n } from '@kbn/i18n'; export const createTileMapFn = () => ({ diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index fe82ad5c7352b..ae3a839b600e9 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -19,9 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; - -import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; - +import { convertToGeoJson } from '../../../../plugins/maps_legacy/public'; import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { createTileMapVisualization } from './tile_map_visualization'; import { TileMapOptions } from './components/tile_map_options'; diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js index 910def8a0c78e..fdce8bc51fe86 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js @@ -23,15 +23,19 @@ import { BaseMapsVisualizationProvider } from './base_maps_visualization'; import { TileMapTooltipFormatterProvider } from './editors/_tooltip_formatter'; import { npStart } from 'ui/new_platform'; import { getFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; -import { - scaleBounds, - zoomPrecision, - getPrecision, - geoContains, -} from '../../../ui/public/vis/map/decode_geo_hash'; +import { scaleBounds, geoContains } from '../../../../plugins/maps_legacy/public'; -export const createTileMapVisualization = ({ serviceSettings, $injector }) => { - const BaseMapsVisualization = new BaseMapsVisualizationProvider(serviceSettings); +export const createTileMapVisualization = ({ + serviceSettings, + $injector, + getZoomPrecision, + getPrecision, + notificationService, +}) => { + const BaseMapsVisualization = new BaseMapsVisualizationProvider( + serviceSettings, + notificationService + ); const tooltipFormatter = new TileMapTooltipFormatterProvider($injector); return class CoordinateMapsVisualization extends BaseMapsVisualization { @@ -59,6 +63,7 @@ export const createTileMapVisualization = ({ serviceSettings, $injector }) => { updateVarsObject.data.boundingBox = geohashAgg.aggConfigParams.boundingBox; } // todo: autoPrecision should be vis parameter, not aggConfig one + const zoomPrecision = getZoomPrecision(); updateVarsObject.data.precision = geohashAgg.aggConfigParams.autoPrecision ? zoomPrecision[this.vis.getUiState().get('mapZoom')] : getPrecision(geohashAgg.aggConfigParams.precision); diff --git a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js b/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js index 0913d6fc92e8a..6da37f4c5ef86 100644 --- a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js +++ b/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js @@ -22,7 +22,7 @@ import { functionWrapper } from '../../../../plugins/expressions/common/expressi import { createTileMapFn } from './tile_map_fn'; jest.mock('ui/new_platform'); -jest.mock('ui/vis/map/convert_to_geojson', () => ({ +jest.mock('../../../../plugins/maps_legacy/public', () => ({ convertToGeoJson: jest.fn().mockReturnValue({ featureCollection: { type: 'FeatureCollection', @@ -37,7 +37,7 @@ jest.mock('ui/vis/map/convert_to_geojson', () => ({ }), })); -import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; +import { convertToGeoJson } from '../../../../plugins/maps_legacy/public'; describe('interpreter/functions#tilemap', () => { const fn = functionWrapper(createTileMapFn()); diff --git a/src/legacy/core_plugins/tile_map/public/types.ts b/src/legacy/core_plugins/tile_map/public/types.ts index 5f1c3f9b03c9e..e1b4c27319123 100644 --- a/src/legacy/core_plugins/tile_map/public/types.ts +++ b/src/legacy/core_plugins/tile_map/public/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TmsLayer } from 'ui/vis/map/service_settings'; +import { TmsLayer } from '../../../../plugins/maps_legacy/public'; import { MapTypes } from './map_types'; export interface WMSOptions { diff --git a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts index 9de8477e3978c..8fadf223e1807 100644 --- a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts +++ b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts @@ -21,7 +21,6 @@ import 'ngreact'; import 'brace/mode/hjson'; import 'brace/ext/searchbox'; import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; -import 'ui/vis/map/service_settings'; import { once } from 'lodash'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index c7fbc0815b07c..6412d8a569b2a 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -49,6 +49,10 @@ import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_ty // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; import { setInjectedVars } from '../services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setInjectedVarFunc } from '../../../../../plugins/maps_legacy/public/kibana_services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ServiceSettings } from '../../../../../plugins/maps_legacy/public/map/service_settings'; const THRESHOLD = 0.1; const PIXEL_DIFF = 30; @@ -69,9 +73,34 @@ describe('VegaVisualizations', () => { beforeEach(ngMock.module('kibana')); beforeEach( - ngMock.inject($injector => { + ngMock.inject(() => { + setInjectedVarFunc(injectedVar => { + switch (injectedVar) { + case 'mapConfig': + return { + emsFileApiUrl: '', + emsTileApiUrl: '', + emsLandingPageUrl: '', + }; + case 'tilemapsConfig': + return { + deprecated: { + config: { + options: { + attribution: '123', + }, + }, + }, + }; + case 'version': + return '123'; + default: + return 'not found'; + } + }); + const serviceSettings = new ServiceSettings(); vegaVisualizationDependencies = { - serviceSettings: $injector.get('serviceSettings'), + serviceSettings, core: { uiSettings: npStart.core.uiSettings, }, diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts index b2c73894d978d..450af4a6f253e 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts @@ -20,16 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { VegaPluginSetupDependencies, VegaPluginStartDependencies } from './plugin'; -import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const setupPlugins: Readonly = { ...npSetup.plugins, visualizations: npSetup.plugins.visualizations, - - // Temporary solution - // It will be removed when all dependent services are migrated to the new platform. - __LEGACY: new LegacyDependenciesPlugin(), + mapsLegacy: npSetup.plugins.mapsLegacy, }; const startPlugins: Readonly = { diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts deleted file mode 100644 index b868321d6310f..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore -export { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; -// @ts-ignore -export { KibanaMap } from 'ui/vis/map/kibana_map'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index 38b92a40cd99a..9fa77d28fbbfa 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -17,7 +17,6 @@ * under the License. */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; @@ -32,13 +31,15 @@ import { import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; import { VisTypeVegaSetup } from '../../../../plugins/vis_type_vega/public'; +import { IServiceSettings } from '../../../../plugins/maps_legacy/public'; /** @internal */ -export interface VegaVisualizationDependencies extends LegacyDependenciesPluginSetup { +export interface VegaVisualizationDependencies { core: CoreSetup; plugins: { data: ReturnType; }; + serviceSettings: IServiceSettings; } /** @internal */ @@ -47,7 +48,7 @@ export interface VegaPluginSetupDependencies { visualizations: VisualizationsSetup; data: ReturnType; visTypeVega: VisTypeVegaSetup; - __LEGACY: LegacyDependenciesPlugin; + mapsLegacy: any; } /** @internal */ @@ -65,7 +66,7 @@ export class VegaPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { data, expressions, visualizations, visTypeVega, __LEGACY }: VegaPluginSetupDependencies + { data, expressions, visualizations, visTypeVega, mapsLegacy }: VegaPluginSetupDependencies ) { setInjectedVars({ enableExternalUrls: visTypeVega.config.enableExternalUrls, @@ -79,7 +80,7 @@ export class VegaPlugin implements Plugin, void> { plugins: { data, }, - ...(await __LEGACY.setup()), + serviceSettings: mapsLegacy.serviceSettings, }; expressions.registerFunction(() => createVegaFn(visualizationDependencies)); diff --git a/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts deleted file mode 100644 index 8925f76cffa43..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts +++ /dev/null @@ -1,49 +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. - */ - -// TODO remove this file as soon as serviceSettings is exposed in the new platform -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import chrome from 'ui/chrome'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import 'ui/vis/map/service_settings'; -import { CoreStart, Plugin } from 'kibana/public'; - -/** @internal */ -export interface LegacyDependenciesPluginSetup { - serviceSettings: any; -} - -export class LegacyDependenciesPlugin - implements Plugin, void> { - public async setup() { - const $injector = await chrome.dangerouslyGetActiveInjector(); - - return { - // Settings for EMSClient. - // EMSClient, which currently lives in the tile_map vis, - // will probably end up being exposed from the future vis_type_maps plugin, - // which would register both the tile_map and the region_map vis plugins. - serviceSettings: $injector.get('serviceSettings'), - } as LegacyDependenciesPluginSetup; - } - - public start(core: CoreStart) { - // nothing to do here yet - } -} diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js index 38540e9f218fb..d43eb9c3351ea 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js @@ -19,7 +19,7 @@ import L from 'leaflet'; import 'leaflet-vega'; -import { KibanaMapLayer } from '../legacy_imports'; +import { KibanaMapLayer } from '../../../../../plugins/maps_legacy/public'; export class VegaMapLayer extends KibanaMapLayer { constructor(spec, options) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js index 487c90d01ada3..03aef29dc5739 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js @@ -21,10 +21,15 @@ import * as vega from 'vega-lib'; import { i18n } from '@kbn/i18n'; import { VegaBaseView } from './vega_base_view'; import { VegaMapLayer } from './vega_map_layer'; -import { KibanaMap } from '../legacy_imports'; +import { KibanaMap } from '../../../../../plugins/maps_legacy/public'; import { getEmsTileLayerId, getUISettings } from '../services'; export class VegaMapView extends VegaBaseView { + constructor(opts, services) { + super(opts); + this.services = services; + } + async _initViewCustomizations() { const mapConfig = this._parser.mapConfig; let baseMapOpts; @@ -102,14 +107,18 @@ export class VegaMapView extends VegaBaseView { // maxBounds = L.latLngBounds(L.latLng(b[1], b[0]), L.latLng(b[3], b[2])); // } - this._kibanaMap = new KibanaMap(this._$container.get(0), { - zoom, - minZoom, - maxZoom, - center: [mapConfig.latitude, mapConfig.longitude], - zoomControl: mapConfig.zoomControl, - scrollWheelZoom: mapConfig.scrollWheelZoom, - }); + this._kibanaMap = new KibanaMap( + this._$container.get(0), + { + zoom, + minZoom, + maxZoom, + center: [mapConfig.latitude, mapConfig.longitude], + zoomControl: mapConfig.zoomControl, + scrollWheelZoom: mapConfig.scrollWheelZoom, + }, + this.services + ); if (baseMapOpts) { this._kibanaMap.setBaseLayer({ diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js index 96835ef3b10bc..a6e911de7f0cb 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js @@ -116,7 +116,8 @@ export const createVegaVisualization = ({ serviceSettings }) => }; if (vegaParser.useMap) { - this._vegaView = new VegaMapView(vegaViewParams); + const services = { toastService: getNotifications().toasts }; + this._vegaView = new VegaMapView(vegaViewParams, services); } else { this._vegaView = new VegaView(vegaViewParams); } diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index bcf766231dc9c..3e71e1989ae7a 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -78,13 +78,8 @@ export function savedObjectsMixin(kbnServer, server) { const provider = kbnServer.newPlatform.__internals.savedObjectsClientProvider; - const importAndExportableTypes = typeRegistry - .getImportableAndExportableTypes() - .map(type => type.name); - const service = { types: visibleTypes, - importAndExportableTypes, SavedObjectsClient, SavedObjectsRepository, getSavedObjectsRepository: createRepository, diff --git a/src/legacy/ui/public/field_editor/field_editor.js b/src/legacy/ui/public/field_editor/field_editor.js index 43461c4c689be..e90cb110ac304 100644 --- a/src/legacy/ui/public/field_editor/field_editor.js +++ b/src/legacy/ui/public/field_editor/field_editor.js @@ -66,7 +66,7 @@ import { ScriptingHelpFlyout } from './components/scripting_help'; import { FieldFormatEditor } from './components/field_format_editor'; import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants'; -import { copyField, executeScript, isScriptValid } from './lib'; +import { executeScript, isScriptValid } from './lib'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -100,7 +100,6 @@ export class FieldEditor extends PureComponent { indexPattern: PropTypes.object.isRequired, field: PropTypes.object.isRequired, helpers: PropTypes.shape({ - Field: PropTypes.func.isRequired, getConfig: PropTypes.func.isRequired, $http: PropTypes.func.isRequired, fieldFormatEditors: PropTypes.object.isRequired, @@ -111,11 +110,7 @@ export class FieldEditor extends PureComponent { constructor(props) { super(props); - const { - field, - indexPattern, - helpers: { Field }, - } = props; + const { field, indexPattern } = props; this.state = { isReady: false, @@ -125,7 +120,7 @@ export class FieldEditor extends PureComponent { fieldTypes: [], fieldTypeFormats: [], existingFieldNames: indexPattern.fields.map(f => f.name), - field: copyField(field, indexPattern, Field), + field: { ...field, format: field.format }, fieldFormatId: undefined, fieldFormatParams: {}, showScriptingHelp: false, @@ -730,7 +725,7 @@ export class FieldEditor extends PureComponent { }; saveField = async () => { - const field = this.state.field.toActualField(); + const field = this.state.field; const { indexPattern } = this.props; const { fieldFormatId } = this.state; diff --git a/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js b/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js deleted file mode 100644 index 2cee45742ab81..0000000000000 --- a/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js +++ /dev/null @@ -1,48 +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 { copyField } from '../copy_field'; - -const field = { - name: 'test_field', - scripted: true, - type: 'number', - lang: 'painless', -}; - -describe('copyField', () => { - it('should copy a field', () => { - const copiedField = copyField(field, {}, {}); - copiedField.name = 'test_name_change'; - - // Check that copied field has `toActualField()` method - expect(typeof copiedField.toActualField).toEqual('function'); - - // Check that we did not modify the original field object when - // modifying copied field - expect(field.toActualField).toEqual(undefined); - expect(field.name).toEqual('test_field'); - - expect(copiedField).not.toEqual(field); - expect(copiedField.name).toEqual('test_name_change'); - expect(copiedField.scripted).toEqual(field.scripted); - expect(copiedField.type).toEqual(field.type); - expect(copiedField.lang).toEqual(field.lang); - }); -}); diff --git a/src/legacy/ui/public/field_editor/lib/copy_field.js b/src/legacy/ui/public/field_editor/lib/copy_field.js deleted file mode 100644 index bfc1cb8480d5d..0000000000000 --- a/src/legacy/ui/public/field_editor/lib/copy_field.js +++ /dev/null @@ -1,82 +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 { has } from 'lodash'; - -/** - * Fully clones a Field object, so that modifications can be performed on - * the copy without affecting original field. Field objects contain - * enumerable and non-eumerable properties that may or may not be writable. - * The function copies all properties as property descriptors into - * `newFieldProps`, overrides getter and setter, and returns a new object - * created from that. - * - * @param {object} field - Field object to copy - * @param {object} indexPattern - index pattern object the field belongs to - * @param {object} Field - Field object type - * @return {object} the cloned object - */ -export const copyField = (field, indexPattern, Field) => { - const changes = {}; - const newFieldProps = { - // When we are ready to save the copied field back into the index pattern, - // we use `toActualField()` to retrieve an actual `Field` type object, using - // its original properties with our "changes" applied. - toActualField: { - value: () => { - return new Field(indexPattern, { - ...field.$$spec, - ...changes, - }); - }, - }, - }; - - // Index pattern `Field` objects are created with custom property - // descriptors using `ObjDefine`. - // - // Each property of a `Field` type object could be enumerable/non-enumerable, - // writable/not writable, configurable/not configurable, and have custom - // getter and setter. We can't use the original `field` object directly for - // creating a new field or editing a new field, since we need all the - // properties to be editable. - // - // A normal copy of `field` (i.e. `const newField = { ...field }`) will only - // copy enumerable properties and copy each property's descriptors (not - // writable, etc). - // - // So we copy `field`'s **property descriptors** into `newFieldProps` - // and modify them so that they are "writable" with a getter/setter that - // stores and retrieves changes into/from another object (`changes`). - Object.getOwnPropertyNames(field).forEach(function(prop) { - const desc = Object.getOwnPropertyDescriptor(field, prop); - - newFieldProps[prop] = { - enumerable: desc.enumerable, - get: function() { - return has(changes, prop) ? changes[prop] : field[prop]; - }, - set: function(v) { - changes[prop] = v; - }, - }; - }); - - return Object.create(null, newFieldProps); -}; diff --git a/src/legacy/ui/public/field_editor/lib/index.js b/src/legacy/ui/public/field_editor/lib/index.js index c74bb0cc2ef8a..c9dd9d03b74f7 100644 --- a/src/legacy/ui/public/field_editor/lib/index.js +++ b/src/legacy/ui/public/field_editor/lib/index.js @@ -17,5 +17,4 @@ * under the License. */ -export { copyField } from './copy_field'; export { executeScript, isScriptValid } from './validate_script'; 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 f56386a2cbf43..f577a29ce90b9 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 @@ -309,6 +309,12 @@ export const npSetup = { registerAlias: sinon.fake(), hideTypes: sinon.fake(), }, + + mapsLegacy: { + serviceSettings: sinon.fake(), + getPrecision: sinon.fake(), + getZoomPrecision: sinon.fake(), + }, }, }; diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index cdd7e1a994912..21b80e827e4c2 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -68,6 +68,7 @@ import { VisualizationsSetup, VisualizationsStart, } from '../../../../plugins/visualizations/public'; +import { MapsLegacyPluginSetup } from '../../../../plugins/maps_legacy/public'; export interface PluginsSetup { bfetch: BfetchPublicSetup; @@ -90,6 +91,7 @@ export interface PluginsSetup { visualizations: VisualizationsSetup; telemetry?: TelemetryPluginSetup; savedObjectsManagement: SavedObjectsManagementPluginSetup; + mapsLegacy: MapsLegacyPluginSetup; indexPatternManagement: IndexPatternManagementSetup; } diff --git a/src/legacy/ui/public/scripting_languages/index.js b/src/legacy/ui/public/scripting_languages/index.ts similarity index 83% rename from src/legacy/ui/public/scripting_languages/index.js rename to src/legacy/ui/public/scripting_languages/index.ts index 2f43a44d66068..283a3273a2a5d 100644 --- a/src/legacy/ui/public/scripting_languages/index.js +++ b/src/legacy/ui/public/scripting_languages/index.ts @@ -17,23 +17,25 @@ * under the License. */ +import { IHttpService } from 'angular'; +import { i18n } from '@kbn/i18n'; + import chrome from '../chrome'; import { toastNotifications } from '../notify'; -import { i18n } from '@kbn/i18n'; -export function getSupportedScriptingLanguages() { +export function getSupportedScriptingLanguages(): string[] { return ['painless']; } -export function getDeprecatedScriptingLanguages() { +export function getDeprecatedScriptingLanguages(): string[] { return []; } -export function GetEnabledScriptingLanguagesProvider($http) { +export function GetEnabledScriptingLanguagesProvider($http: IHttpService) { return () => { return $http .get(chrome.addBasePath('/api/kibana/scripts/languages')) - .then(res => res.data) + .then((res: any) => res.data) .catch(() => { toastNotifications.addDanger( i18n.translate('common.ui.scriptingLanguages.errorFetchingToastDescription', { diff --git a/src/legacy/ui/public/vis/map/service_settings.js b/src/legacy/ui/public/vis/map/service_settings.js deleted file mode 100644 index a014aeb182c67..0000000000000 --- a/src/legacy/ui/public/vis/map/service_settings.js +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiModules } from '../../modules'; -import _ from 'lodash'; -import MarkdownIt from 'markdown-it'; -import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; -import { EMSClient } from '@elastic/ems-client'; -import { i18n } from '@kbn/i18n'; -import 'angular-sanitize'; - -const markdownIt = new MarkdownIt({ - html: false, - linkify: true, -}); - -const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; - -uiModules - .get('kibana', ['ngSanitize']) - .service('serviceSettings', function($sanitize, mapConfig, tilemapsConfig, kbnVersion) { - const attributionFromConfig = $sanitize( - markdownIt.render(tilemapsConfig.deprecated.config.options.attribution || '') - ); - const tmsOptionsFromConfig = _.assign({}, tilemapsConfig.deprecated.config.options, { - attribution: attributionFromConfig, - }); - - class ServiceSettings { - constructor() { - this._showZoomMessage = true; - this._emsClient = new EMSClient({ - language: i18n.getLocale(), - appVersion: kbnVersion, - appName: 'kibana', - fileApiUrl: mapConfig.emsFileApiUrl, - tileApiUrl: mapConfig.emsTileApiUrl, - htmlSanitizer: $sanitize, - landingPageUrl: mapConfig.emsLandingPageUrl, - // Wrap to avoid errors passing window fetch - fetchFunction: function(...args) { - return fetch(...args); - }, - }); - } - - shouldShowZoomMessage({ origin }) { - return origin === ORIGIN.EMS && this._showZoomMessage; - } - - disableZoomMessage() { - this._showZoomMessage = false; - } - - __debugStubManifestCalls(manifestRetrieval) { - const oldGetManifest = this._emsClient.getManifest; - this._emsClient.getManifest = manifestRetrieval; - return { - removeStub: () => { - delete this._emsClient.getManifest; - //not strictly necessary since this is prototype method - if (this._emsClient.getManifest !== oldGetManifest) { - this._emsClient.getManifest = oldGetManifest; - } - }, - }; - } - - async getFileLayers() { - if (!mapConfig.includeElasticMapsService) { - return []; - } - - const fileLayers = await this._emsClient.getFileLayers(); - return fileLayers.map(fileLayer => { - //backfill to older settings - const format = fileLayer.getDefaultFormatType(); - const meta = fileLayer.getDefaultFormatMeta(); - - return { - name: fileLayer.getDisplayName(), - origin: fileLayer.getOrigin(), - id: fileLayer.getId(), - created_at: fileLayer.getCreatedAt(), - attribution: fileLayer.getHTMLAttribution(), - fields: fileLayer.getFieldsInLanguage(), - format: format, //legacy: format and meta are split up - meta: meta, //legacy, format and meta are split up - }; - }); - } - - /** - * Returns all the services published by EMS (if configures) - * It also includes the service configured in tilemap (override) - */ - async getTMSServices() { - let allServices = []; - if (tilemapsConfig.deprecated.isOverridden) { - //use tilemap.* settings from yml - const tmsService = _.cloneDeep(tmsOptionsFromConfig); - tmsService.id = TMS_IN_YML_ID; - tmsService.origin = ORIGIN.KIBANA_YML; - allServices.push(tmsService); - } - - if (mapConfig.includeElasticMapsService) { - const servicesFromManifest = await this._emsClient.getTMSServices(); - const strippedServiceFromManifest = await Promise.all( - servicesFromManifest - .filter(tmsService => tmsService.getId() === mapConfig.emsTileLayerId.bright) - .map(async tmsService => { - //shim for compatibility - const shim = { - origin: tmsService.getOrigin(), - id: tmsService.getId(), - minZoom: await tmsService.getMinZoom(), - maxZoom: await tmsService.getMaxZoom(), - attribution: tmsService.getHTMLAttribution(), - }; - return shim; - }) - ); - allServices = allServices.concat(strippedServiceFromManifest); - } - - return allServices; - } - - /** - * Add optional query-parameters to all requests - * - * @param additionalQueryParams - */ - addQueryParams(additionalQueryParams) { - this._emsClient.addQueryParams(additionalQueryParams); - } - - async getEMSHotLink(fileLayerConfig) { - const fileLayers = await this._emsClient.getFileLayers(); - const layer = fileLayers.find(fileLayer => { - const hasIdByName = fileLayer.hasId(fileLayerConfig.name); //legacy - const hasIdById = fileLayer.hasId(fileLayerConfig.id); - return hasIdByName || hasIdById; - }); - return layer ? layer.getEMSHotLink() : null; - } - - async _getAttributesForEMSTMSLayer(isDesaturated, isDarkMode) { - const tmsServices = await this._emsClient.getTMSServices(); - const emsTileLayerId = mapConfig.emsTileLayerId; - let serviceId; - if (isDarkMode) { - serviceId = emsTileLayerId.dark; - } else { - if (isDesaturated) { - serviceId = emsTileLayerId.desaturated; - } else { - serviceId = emsTileLayerId.bright; - } - } - const tmsService = tmsServices.find(service => { - return service.getId() === serviceId; - }); - return { - url: await tmsService.getUrlTemplate(), - minZoom: await tmsService.getMinZoom(), - maxZoom: await tmsService.getMaxZoom(), - attribution: await tmsService.getHTMLAttribution(), - origin: ORIGIN.EMS, - }; - } - - async getAttributesForTMSLayer(tmsServiceConfig, isDesaturated, isDarkMode) { - if (tmsServiceConfig.origin === ORIGIN.EMS) { - return this._getAttributesForEMSTMSLayer(isDesaturated, isDarkMode); - } else if (tmsServiceConfig.origin === ORIGIN.KIBANA_YML) { - const config = tilemapsConfig.deprecated.config; - const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); - return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; - } else { - //this is an older config. need to resolve this dynamically. - if (tmsServiceConfig.id === TMS_IN_YML_ID) { - const config = tilemapsConfig.deprecated.config; - const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); - return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; - } else { - //assume ems - return this._getAttributesForEMSTMSLayer(isDesaturated, isDarkMode); - } - } - } - - async _getFileUrlFromEMS(fileLayerConfig) { - const fileLayers = await this._emsClient.getFileLayers(); - const layer = fileLayers.find(fileLayer => { - const hasIdByName = fileLayer.hasId(fileLayerConfig.name); //legacy - const hasIdById = fileLayer.hasId(fileLayerConfig.id); - return hasIdByName || hasIdById; - }); - - if (layer) { - return layer.getDefaultFormatUrl(); - } else { - throw new Error(`File ${fileLayerConfig.name} not recognized`); - } - } - - async getUrlForRegionLayer(fileLayerConfig) { - let url; - if (fileLayerConfig.origin === ORIGIN.EMS) { - url = this._getFileUrlFromEMS(fileLayerConfig); - } else if ( - fileLayerConfig.layerId && - fileLayerConfig.layerId.startsWith(`${ORIGIN.EMS}.`) - ) { - //fallback for older saved objects - url = this._getFileUrlFromEMS(fileLayerConfig); - } else if ( - fileLayerConfig.layerId && - fileLayerConfig.layerId.startsWith(`${ORIGIN.KIBANA_YML}.`) - ) { - //fallback for older saved objects - url = fileLayerConfig.url; - } else { - //generic fallback - url = fileLayerConfig.url; - } - return url; - } - - async getJsonForRegionLayer(fileLayerConfig) { - const url = await this.getUrlForRegionLayer(fileLayerConfig); - const response = await fetch(url); - return await response.json(); - } - } - - return new ServiceSettings(); - }); diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt b/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt index f9a4bcb85034d..398a0fdeab61f 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt @@ -25,3 +25,9 @@ GET index_1/type1/1/_source?_source_include=f DELETE index_2 + +POST /_sql?format=txt +{ + "query": "SELECT prenom FROM claude_index WHERE prenom = 'claude' ", + "fetch_size": 1 +} diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js index 6afc03df13b4c..34b4cad7fbb6b 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js @@ -470,6 +470,18 @@ curl -XGET "http://localhost:9200/_stats?level=shards" curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application/json' -d' { "f": 1 +}'`.trim() + ); + + multiReqCopyAsCurlTest( + 'with single quotes', + editorInput1, + { start: { lineNumber: 29 }, end: { lineNumber: 33 } }, + ` +curl -XPOST "http://localhost:9200/_sql?format=txt" -H 'Content-Type: application/json' -d' +{ + "query": "SELECT prenom FROM claude_index WHERE prenom = '\\''claude'\\'' ", + "fetch_size": 1 }'`.trim() ); }); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index 9bcd3a6872196..d326543bbe00b 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -484,8 +484,9 @@ export class SenseEditor { if (esData && esData.length) { ret += " -H 'Content-Type: application/json' -d'\n"; const dataAsString = collapseLiteralStrings(esData.join('\n')); - // since Sense doesn't allow single quote json string any single qoute is within a string. - ret += dataAsString.replace(/'/g, '\\"'); + + // We escape single quoted strings that that are wrapped in single quoted strings + ret += dataAsString.replace(/'/g, "'\\''"); if (esData.length > 1) { ret += '\n'; } // end with a new line diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 1554565d1403e..6370dcdf2db6f 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -45,9 +45,6 @@ export class Field implements IFieldType { subType?: IFieldSubType; displayName?: string; format: any; - routes: Record = { - edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}', - }; $$spec: FieldSpec; constructor( @@ -146,7 +143,3 @@ export class Field implements IFieldType { return obj.create(); } } - -Field.prototype.routes = { - edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}', -}; diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index 03214a8c96427..d6067280fd7b6 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -70,11 +70,12 @@ export class FieldList extends Array implements IFieldList { this.splice(fieldIndex, 1); }; - update = (field: Field) => { - const index = this.findIndex(f => f.name === field.name); - this.splice(index, 1, field); - this.setByName(field); - this.removeByGroup(field); - this.setByGroup(field); + update = (field: FieldSpec) => { + const newField = new Field(this.indexPattern, field, this.shortDotsEnable); + const index = this.findIndex(f => f.name === newField.name); + this.splice(index, 1, newField); + this.setByName(newField); + this.removeByGroup(newField); + this.setByGroup(newField); }; } diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index ea1c27550867e..2d43cae79ac98 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -62,6 +62,7 @@ const createStartContract = (): Start => { }, }), get: jest.fn().mockReturnValue(Promise.resolve({})), + clearCache: jest.fn(), } as unknown) as IndexPatternsContract, }; }; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index cef82b27b1b5b..62967a7071d82 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -947,8 +947,6 @@ export class IndexPatternField implements IFieldType { // (undocumented) name: string; // (undocumented) - routes: Record; - // (undocumented) script?: string; // (undocumented) scripted?: boolean; @@ -978,7 +976,7 @@ export class IndexPatternFieldList extends Array implements I // (undocumented) remove: (field: IFieldType) => void; // (undocumented) - update: (field: IndexPatternField) => void; + update: (field: Record) => void; } // Warning: (ae-missing-release-tag) "indexPatterns" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 36dcd4a00c05e..5550ea16c22df 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { EuiButtonEmpty, EuiOverlayMask, @@ -63,6 +63,7 @@ export function SaveQueryForm({ showTimeFilterOption = true, }: Props) { const [title, setTitle] = useState(savedQuery ? savedQuery.title : ''); + const [enabledSaveButton, setEnabledSaveButton] = useState(Boolean(savedQuery)); const [description, setDescription] = useState(savedQuery ? savedQuery.description : ''); const [savedQueries, setSavedQueries] = useState([]); const [shouldIncludeFilters, setShouldIncludeFilters] = useState( @@ -76,49 +77,31 @@ export function SaveQueryForm({ ); const [formErrors, setFormErrors] = useState([]); - useEffect(() => { - const fetchQueries = async () => { - const allSavedQueries = await savedQueryService.getAllSavedQueries(); - const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title') as SavedQuery[]; - setSavedQueries(sortedAllSavedQueries); - }; - fetchQueries(); - }, [savedQueryService]); - - const savedQueryDescriptionText = i18n.translate( - 'data.search.searchBar.savedQueryDescriptionText', - { - defaultMessage: 'Save query text and filters that you want to use again.', - } - ); - const titleConflictErrorText = i18n.translate( 'data.search.searchBar.savedQueryForm.titleConflictText', { defaultMessage: 'Name conflicts with an existing saved query', } ); - const titleMissingErrorText = i18n.translate( - 'data.search.searchBar.savedQueryForm.titleMissingText', - { - defaultMessage: 'Name is required', - } - ); - const whitespaceErrorText = i18n.translate( - 'data.search.searchBar.savedQueryForm.whitespaceErrorText', + + const savedQueryDescriptionText = i18n.translate( + 'data.search.searchBar.savedQueryDescriptionText', { - defaultMessage: 'Name cannot contain leading or trailing whitespace', + defaultMessage: 'Save query text and filters that you want to use again.', } ); - const validate = () => { + useEffect(() => { + const fetchQueries = async () => { + const allSavedQueries = await savedQueryService.getAllSavedQueries(); + const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title') as SavedQuery[]; + setSavedQueries(sortedAllSavedQueries); + }; + fetchQueries(); + }, [savedQueryService]); + + const validate = useCallback(() => { const errors = []; - if (!title.length) { - errors.push(titleMissingErrorText); - } - if (title.length > title.trim().length) { - errors.push(whitespaceErrorText); - } if ( !!savedQueries.find( existingSavedQuery => !savedQuery && existingSavedQuery.attributes.title === title @@ -129,14 +112,37 @@ export function SaveQueryForm({ if (!isEqual(errors, formErrors)) { setFormErrors(errors); + return false; } - }; - const hasErrors = formErrors.length > 0; + return !formErrors.length; + }, [savedQueries, savedQuery, title, titleConflictErrorText, formErrors]); + + const onClickSave = useCallback(() => { + if (validate()) { + onSave({ + title, + description, + shouldIncludeFilters, + shouldIncludeTimefilter, + }); + } + }, [validate, onSave, title, description, shouldIncludeFilters, shouldIncludeTimefilter]); + + const onInputChange = useCallback(event => { + setEnabledSaveButton(Boolean(event.target.value)); + setFormErrors([]); + setTitle(event.target.value); + }, []); + + const autoTrim = useCallback(() => { + const trimmedTitle = title.trim(); + if (title.length > trimmedTitle.length) { + setTitle(trimmedTitle); + } + }, [title]); - if (hasErrors) { - validate(); - } + const hasErrors = formErrors.length > 0; const saveQueryForm = ( @@ -157,12 +163,10 @@ export function SaveQueryForm({ disabled={!!savedQuery} value={title} name="title" - onChange={event => { - setTitle(event.target.value); - }} + onChange={onInputChange} data-test-subj="saveQueryFormTitle" isInvalid={hasErrors} - onBlur={validate} + onBlur={autoTrim} /> @@ -235,17 +239,10 @@ export function SaveQueryForm({ - onSave({ - title, - description, - shouldIncludeFilters, - shouldIncludeTimefilter, - }) - } + onClick={onClickSave} fill data-test-subj="savedQueryFormSaveButton" - disabled={hasErrors} + disabled={hasErrors || !enabledSaveButton} > {i18n.translate('data.search.searchBar.savedQueryFormSaveButtonText', { defaultMessage: 'Save', diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index bb05e3d412001..218c59b5db07b 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -37,6 +37,9 @@ const createStartContract = (): Start => { docViews: { DocViewer: jest.fn(() => null), }, + savedSearches: { + createLoader: jest.fn(), + }, }; return startContract; }; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index d2797586bfdfb..aa54823e6ec4d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -21,12 +21,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { auto } from 'angular'; import { CoreSetup, Plugin } from 'kibana/public'; +import { SavedObjectLoader, SavedObjectKibanaServices } from '../../saved_objects/public'; import { DocViewInput, DocViewInputFn, DocViewRenderProps } from './doc_views/doc_views_types'; import { DocViewsRegistry } from './doc_views/doc_views_registry'; import { DocViewTable } from './components/table/table'; import { JsonCodeBlock } from './components/json_code_block/json_code_block'; import { DocViewer } from './components/doc_viewer/doc_viewer'; import { setDocViewsRegistry } from './services'; +import { createSavedSearchesLoader } from './saved_searches'; import './index.scss'; @@ -62,6 +64,13 @@ export interface DiscoverStart { */ DocViewer: React.ComponentType; }; + savedSearches: { + /** + * Create a {@link SavedObjectLoader | loader} to handle the saved searches type. + * @param services + */ + createLoader(services: SavedObjectKibanaServices): SavedObjectLoader; + }; } /** @@ -105,6 +114,9 @@ export class DiscoverPlugin implements Plugin { docViews: { DocViewer, }, + savedSearches: { + createLoader: createSavedSearchesLoader, + }, }; } } diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 6099a2cc32afc..82789d3c3f55f 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -18,12 +18,21 @@ */ import { ManagementSetup, ManagementStart } from '../types'; +import { ManagementSection } from '../management_section'; + +const createManagementSectionMock = (): jest.Mocked> => { + return { + registerApp: jest.fn(), + getApp: jest.fn(), + getAppsEnabled: jest.fn().mockReturnValue([]), + }; +}; const createSetupContract = (): DeeplyMockedKeys => ({ sections: { register: jest.fn(), - getSection: jest.fn(), - getAllSections: jest.fn(), + getSection: jest.fn().mockReturnValue(createManagementSectionMock()), + getAllSections: jest.fn().mockReturnValue([]), }, }); diff --git a/src/plugins/maps_legacy/kibana.json b/src/plugins/maps_legacy/kibana.json new file mode 100644 index 0000000000000..d66be2b156bb9 --- /dev/null +++ b/src/plugins/maps_legacy/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "mapsLegacy", + "version": "8.0.0", + "kibanaVersion": "kibana", + "ui": true +} diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_files.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_files.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright_vector.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright_vector.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright_vector_source.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright_vector_source.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated.json diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json b/src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json rename to src/plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json diff --git a/src/legacy/ui/public/vis/__tests__/map/kibana_map.js b/src/plugins/maps_legacy/public/__tests__/map/kibana_map.js similarity index 100% rename from src/legacy/ui/public/vis/__tests__/map/kibana_map.js rename to src/plugins/maps_legacy/public/__tests__/map/kibana_map.js diff --git a/src/legacy/ui/public/vis/__tests__/map/service_settings.js b/src/plugins/maps_legacy/public/__tests__/map/service_settings.js similarity index 99% rename from src/legacy/ui/public/vis/__tests__/map/service_settings.js rename to src/plugins/maps_legacy/public/__tests__/map/service_settings.js index 61925760457c6..a9272ea396639 100644 --- a/src/legacy/ui/public/vis/__tests__/map/service_settings.js +++ b/src/plugins/maps_legacy/public/__tests__/map/service_settings.js @@ -26,7 +26,7 @@ import EMS_TILES from './ems_mocks/sample_tiles.json'; import EMS_STYLE_ROAD_MAP_BRIGHT from './ems_mocks/sample_style_bright'; import EMS_STYLE_ROAD_MAP_DESATURATED from './ems_mocks/sample_style_desaturated'; import EMS_STYLE_DARK_MAP from './ems_mocks/sample_style_dark'; -import { ORIGIN } from '../../../../../core_plugins/tile_map/common/origin'; +import { ORIGIN } from '../../common/origin'; describe('service_settings (FKA tilemaptest)', function() { let serviceSettings; diff --git a/src/legacy/ui/public/vis/_index.scss b/src/plugins/maps_legacy/public/_index.scss similarity index 100% rename from src/legacy/ui/public/vis/_index.scss rename to src/plugins/maps_legacy/public/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/index.js b/src/plugins/maps_legacy/public/common/origin.ts similarity index 90% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/index.js rename to src/plugins/maps_legacy/public/common/origin.ts index 522b1ce83a6b6..fdf74cae4ba68 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/index.js +++ b/src/plugins/maps_legacy/public/common/origin.ts @@ -17,4 +17,7 @@ * under the License. */ -export { Relationships } from './relationships'; +export const ORIGIN = { + EMS: 'elastic_maps_service', + KIBANA_YML: 'self_hosted', +}; diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts new file mode 100644 index 0000000000000..861f67006ad83 --- /dev/null +++ b/src/plugins/maps_legacy/public/index.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MapsLegacyPlugin } from './plugin'; +// @ts-ignore +import * as colorUtil from './map/color_util'; +// @ts-ignore +import { KibanaMap } from './map/kibana_map'; +// @ts-ignore +import { KibanaMapLayer } from './map/kibana_map_layer'; +// @ts-ignore +import { convertToGeoJson } from './map/convert_to_geojson'; +// @ts-ignore +import { scaleBounds, getPrecision, geoContains } from './map/decode_geo_hash'; +// @ts-ignore +import { + VectorLayer, + FileLayerField, + FileLayer, + TmsLayer, + IServiceSettings, +} from './map/service_settings'; + +export function plugin() { + return new MapsLegacyPlugin(); +} + +/** @public */ +export { + scaleBounds, + getPrecision, + geoContains, + colorUtil, + convertToGeoJson, + IServiceSettings, + KibanaMap, + KibanaMapLayer, + VectorLayer, + FileLayerField, + FileLayer, + TmsLayer, +}; + +export type MapsLegacyPluginSetup = ReturnType; +export type MapsLegacyPluginStart = ReturnType; diff --git a/src/plugins/maps_legacy/public/kibana_services.js b/src/plugins/maps_legacy/public/kibana_services.js new file mode 100644 index 0000000000000..815c6f9e5651f --- /dev/null +++ b/src/plugins/maps_legacy/public/kibana_services.js @@ -0,0 +1,30 @@ +/* + * 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. + */ + +let toast; +export const setToasts = notificationToast => (toast = notificationToast); +export const getToasts = () => toast; + +let uiSettings; +export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); +export const getUiSettings = () => uiSettings; + +let getInjectedVar; +export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); +export const getInjectedVarFunc = () => getInjectedVar; diff --git a/src/legacy/ui/public/vis/map/_index.scss b/src/plugins/maps_legacy/public/map/_index.scss similarity index 100% rename from src/legacy/ui/public/vis/map/_index.scss rename to src/plugins/maps_legacy/public/map/_index.scss diff --git a/src/legacy/ui/public/vis/map/_leaflet_overrides.scss b/src/plugins/maps_legacy/public/map/_leaflet_overrides.scss similarity index 100% rename from src/legacy/ui/public/vis/map/_leaflet_overrides.scss rename to src/plugins/maps_legacy/public/map/_leaflet_overrides.scss diff --git a/src/legacy/ui/public/vis/map/_legend.scss b/src/plugins/maps_legacy/public/map/_legend.scss similarity index 100% rename from src/legacy/ui/public/vis/map/_legend.scss rename to src/plugins/maps_legacy/public/map/_legend.scss diff --git a/src/legacy/ui/public/vis/map/color_util.js b/src/plugins/maps_legacy/public/map/color_util.js similarity index 100% rename from src/legacy/ui/public/vis/map/color_util.js rename to src/plugins/maps_legacy/public/map/color_util.js diff --git a/src/legacy/ui/public/vis/map/convert_to_geojson.js b/src/plugins/maps_legacy/public/map/convert_to_geojson.js similarity index 100% rename from src/legacy/ui/public/vis/map/convert_to_geojson.js rename to src/plugins/maps_legacy/public/map/convert_to_geojson.js diff --git a/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts b/src/plugins/maps_legacy/public/map/decode_geo_hash.test.ts similarity index 100% rename from src/legacy/ui/public/vis/map/decode_geo_hash.test.ts rename to src/plugins/maps_legacy/public/map/decode_geo_hash.test.ts diff --git a/src/legacy/ui/public/vis/map/decode_geo_hash.ts b/src/plugins/maps_legacy/public/map/decode_geo_hash.ts similarity index 79% rename from src/legacy/ui/public/vis/map/decode_geo_hash.ts rename to src/plugins/maps_legacy/public/map/decode_geo_hash.ts index 3f8430b8628d7..8c39ada03a46b 100644 --- a/src/legacy/ui/public/vis/map/decode_geo_hash.ts +++ b/src/plugins/maps_legacy/public/map/decode_geo_hash.ts @@ -17,11 +17,8 @@ * under the License. */ -import chrome from 'ui/chrome'; import _ from 'lodash'; -const config = chrome.getUiSettingsClient(); - interface DecodedGeoHash { latitude: number[]; longitude: number[]; @@ -74,6 +71,10 @@ function refineInterval(interval: number[], cd: number, mask: number) { } } +export function geohashColumns(precision: number): number { + return geohashCells(precision, 0); +} + /** * Get the number of geohash cells for a given precision * @@ -90,51 +91,6 @@ function geohashCells(precision: number, axis: number) { return cells; } -/** - * Get the number of geohash columns (world-wide) for a given precision - * @param precision the geohash precision - * @returns {number} the number of columns - */ -export function geohashColumns(precision: number): number { - return geohashCells(precision, 0); -} - -const defaultPrecision = 2; -const maxPrecision = parseInt(config.get('visualization:tileMap:maxPrecision'), 10) || 12; -/** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ -export const zoomPrecision: any = {}; -const minGeohashPixels = 16; - -for (let zoom = 0; zoom <= 21; zoom += 1) { - const worldPixels = 256 * Math.pow(2, zoom); - zoomPrecision[zoom] = 1; - for (let precision = 2; precision <= maxPrecision; precision += 1) { - const columns = geohashColumns(precision); - if (worldPixels / columns >= minGeohashPixels) { - zoomPrecision[zoom] = precision; - } else { - break; - } - } -} - -export function getPrecision(val: string) { - let precision = parseInt(val, 10); - - if (Number.isNaN(precision)) { - precision = defaultPrecision; - } - - if (precision > maxPrecision) { - return maxPrecision; - } - - return precision; -} - interface GeoBoundingBoxCoordinate { lat: number; lon: number; diff --git a/src/legacy/ui/public/vis/map/grid_dimensions.js b/src/plugins/maps_legacy/public/map/grid_dimensions.js similarity index 100% rename from src/legacy/ui/public/vis/map/grid_dimensions.js rename to src/plugins/maps_legacy/public/map/grid_dimensions.js diff --git a/src/legacy/ui/public/vis/map/kibana_map.js b/src/plugins/maps_legacy/public/map/kibana_map.js similarity index 96% rename from src/legacy/ui/public/vis/map/kibana_map.js rename to src/plugins/maps_legacy/public/map/kibana_map.js index bc581b1a8fbaf..1c4d0882cb7da 100644 --- a/src/legacy/ui/public/vis/map/kibana_map.js +++ b/src/plugins/maps_legacy/public/map/kibana_map.js @@ -24,7 +24,7 @@ import $ from 'jquery'; import _ from 'lodash'; import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; -import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; +import { ORIGIN } from '../common/origin'; function makeFitControl(fitContainer, kibanaMap) { const FitControl = L.Control.extend({ @@ -39,7 +39,7 @@ function makeFitControl(fitContainer, kibanaMap) { onAdd: function(leafletMap) { this._leafletMap = leafletMap; const fitDatBoundsLabel = i18n.translate( - 'common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel', + 'maps_legacy.kibanaMap.leaflet.fitDataBoundsAriaLabel', { defaultMessage: 'Fit Data Bounds' } ); $(this._fitContainer) @@ -101,7 +101,7 @@ function makeLegendControl(container, kibanaMap, position) { * Serves as simple abstraction for leaflet as well. */ export class KibanaMap extends EventEmitter { - constructor(containerNode, options) { + constructor(containerNode, options, services) { super(); this._containerNode = containerNode; this._leafletBaseLayer = null; @@ -116,6 +116,7 @@ export class KibanaMap extends EventEmitter { this._layers = []; this._listeners = []; this._showTooltip = false; + this.toastService = services ? services.toastService : null; const leafletOptions = { minZoom: options.minZoom, @@ -482,15 +483,21 @@ export class KibanaMap extends EventEmitter { } _addMaxZoomMessage = layer => { - const zoomWarningMsg = createZoomWarningMsg(this.getZoomLevel, this.getMaxZoomLevel); + if (this.toastService) { + const zoomWarningMsg = createZoomWarningMsg( + this.toastService, + this.getZoomLevel, + this.getMaxZoomLevel + ); - this._leafletMap.on('zoomend', zoomWarningMsg); - this._containerNode.setAttribute('data-test-subj', 'zoomWarningEnabled'); + this._leafletMap.on('zoomend', zoomWarningMsg); + this._containerNode.setAttribute('data-test-subj', 'zoomWarningEnabled'); - layer.on('remove', () => { - this._leafletMap.off('zoomend', zoomWarningMsg); - this._containerNode.removeAttribute('data-test-subj'); - }); + layer.on('remove', () => { + this._leafletMap.off('zoomend', zoomWarningMsg); + this._containerNode.removeAttribute('data-test-subj'); + }); + } }; setLegendPosition(position) { diff --git a/src/legacy/ui/public/vis/map/kibana_map_layer.js b/src/plugins/maps_legacy/public/map/kibana_map_layer.js similarity index 100% rename from src/legacy/ui/public/vis/map/kibana_map_layer.js rename to src/plugins/maps_legacy/public/map/kibana_map_layer.js diff --git a/src/legacy/ui/public/vis/map/map_messages.js b/src/plugins/maps_legacy/public/map/map_messages.js similarity index 93% rename from src/legacy/ui/public/vis/map/map_messages.js rename to src/plugins/maps_legacy/public/map/map_messages.js index 211796d734958..7422fa71280fb 100644 --- a/src/legacy/ui/public/vis/map/map_messages.js +++ b/src/plugins/maps_legacy/public/map/map_messages.js @@ -17,11 +17,10 @@ * under the License. */ -import { toastNotifications } from 'ui/notify'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; -import { toMountPoint } from '../../../../../plugins/kibana_react/public'; +import { toMountPoint } from '../../../kibana_react/public'; export const createZoomWarningMsg = (function() { let disableZoomMsg = false; @@ -40,7 +39,7 @@ export const createZoomWarningMsg = (function() {

= minGeohashPixels) { + zoomPrecision[zoom] = precision; + } else { + break; + } + } + } + return zoomPrecision; +} + +export function getPrecision(val: string) { + let precision = parseInt(val, 10); + const maxPrecision = getMaxPrecision(); + + if (Number.isNaN(precision)) { + precision = DEFAULT_PRECISION; + } + + if (precision > maxPrecision) { + return maxPrecision; + } + + return precision; +} diff --git a/src/legacy/ui/public/vis/map/service_settings.d.ts b/src/plugins/maps_legacy/public/map/service_settings.d.ts similarity index 97% rename from src/legacy/ui/public/vis/map/service_settings.d.ts rename to src/plugins/maps_legacy/public/map/service_settings.d.ts index 6766000861e47..e265accaeb8fd 100644 --- a/src/legacy/ui/public/vis/map/service_settings.d.ts +++ b/src/plugins/maps_legacy/public/map/service_settings.d.ts @@ -44,7 +44,7 @@ export interface VectorLayer extends FileLayer { isEMS: boolean; } -export interface ServiceSettings { +export interface IServiceSettings { getEMSHotLink(layer: FileLayer): Promise; getTMSServices(): Promise; getFileLayers(): Promise; diff --git a/src/plugins/maps_legacy/public/map/service_settings.js b/src/plugins/maps_legacy/public/map/service_settings.js new file mode 100644 index 0000000000000..11c853d39e107 --- /dev/null +++ b/src/plugins/maps_legacy/public/map/service_settings.js @@ -0,0 +1,254 @@ +/* + * 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 MarkdownIt from 'markdown-it'; +import { EMSClient } from '@elastic/ems-client'; +import { i18n } from '@kbn/i18n'; +import { getInjectedVarFunc } from '../kibana_services'; +import { ORIGIN } from '../common/origin'; + +const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; + +export class ServiceSettings { + constructor() { + const getInjectedVar = getInjectedVarFunc(); + this.mapConfig = getInjectedVar('mapConfig'); + this.tilemapsConfig = getInjectedVar('tilemapsConfig'); + const kbnVersion = getInjectedVar('version'); + + this._showZoomMessage = true; + this._emsClient = new EMSClient({ + language: i18n.getLocale(), + appVersion: kbnVersion, + appName: 'kibana', + fileApiUrl: this.mapConfig.emsFileApiUrl, + tileApiUrl: this.mapConfig.emsTileApiUrl, + landingPageUrl: this.mapConfig.emsLandingPageUrl, + // Wrap to avoid errors passing window fetch + fetchFunction: function(...args) { + return fetch(...args); + }, + }); + this.getTMSOptions(); + } + + getTMSOptions() { + const markdownIt = new MarkdownIt({ + html: false, + linkify: true, + }); + + // TMS attribution + const attributionFromConfig = _.escape( + markdownIt.render(this.tilemapsConfig.deprecated.config.options.attribution || '') + ); + // TMS Options + this.tmsOptionsFromConfig = _.assign({}, this.tilemapsConfig.deprecated.config.options, { + attribution: attributionFromConfig, + }); + } + + shouldShowZoomMessage({ origin }) { + return origin === ORIGIN.EMS && this._showZoomMessage; + } + + disableZoomMessage() { + this._showZoomMessage = false; + } + + __debugStubManifestCalls(manifestRetrieval) { + const oldGetManifest = this._emsClient.getManifest; + this._emsClient.getManifest = manifestRetrieval; + return { + removeStub: () => { + delete this._emsClient.getManifest; + //not strictly necessary since this is prototype method + if (this._emsClient.getManifest !== oldGetManifest) { + this._emsClient.getManifest = oldGetManifest; + } + }, + }; + } + + async getFileLayers() { + if (!this.mapConfig.includeElasticMapsService) { + return []; + } + + const fileLayers = await this._emsClient.getFileLayers(); + return fileLayers.map(fileLayer => { + //backfill to older settings + const format = fileLayer.getDefaultFormatType(); + const meta = fileLayer.getDefaultFormatMeta(); + + return { + name: fileLayer.getDisplayName(), + origin: fileLayer.getOrigin(), + id: fileLayer.getId(), + created_at: fileLayer.getCreatedAt(), + attribution: fileLayer.getHTMLAttribution(), + fields: fileLayer.getFieldsInLanguage(), + format: format, //legacy: format and meta are split up + meta: meta, //legacy, format and meta are split up + }; + }); + } + + /** + * Returns all the services published by EMS (if configures) + * It also includes the service configured in tilemap (override) + */ + async getTMSServices() { + let allServices = []; + if (this.tilemapsConfig.deprecated.isOverridden) { + //use tilemap.* settings from yml + const tmsService = _.cloneDeep(this.tmsOptionsFromConfig); + tmsService.id = TMS_IN_YML_ID; + tmsService.origin = ORIGIN.KIBANA_YML; + allServices.push(tmsService); + } + + if (this.mapConfig.includeElasticMapsService) { + const servicesFromManifest = await this._emsClient.getTMSServices(); + const strippedServiceFromManifest = await Promise.all( + servicesFromManifest + .filter(tmsService => tmsService.getId() === this.mapConfig.emsTileLayerId.bright) + .map(async tmsService => { + //shim for compatibility + return { + origin: tmsService.getOrigin(), + id: tmsService.getId(), + minZoom: await tmsService.getMinZoom(), + maxZoom: await tmsService.getMaxZoom(), + attribution: tmsService.getHTMLAttribution(), + }; + }) + ); + allServices = allServices.concat(strippedServiceFromManifest); + } + + return allServices; + } + + /** + * Add optional query-parameters to all requests + * + * @param additionalQueryParams + */ + addQueryParams(additionalQueryParams) { + this._emsClient.addQueryParams(additionalQueryParams); + } + + async getEMSHotLink(fileLayerConfig) { + const fileLayers = await this._emsClient.getFileLayers(); + const layer = fileLayers.find(fileLayer => { + const hasIdByName = fileLayer.hasId(fileLayerConfig.name); //legacy + const hasIdById = fileLayer.hasId(fileLayerConfig.id); + return hasIdByName || hasIdById; + }); + return layer ? layer.getEMSHotLink() : null; + } + + async _getAttributesForEMSTMSLayer(isDesaturated, isDarkMode) { + const tmsServices = await this._emsClient.getTMSServices(); + const emsTileLayerId = this.mapConfig.emsTileLayerId; + let serviceId; + if (isDarkMode) { + serviceId = emsTileLayerId.dark; + } else { + if (isDesaturated) { + serviceId = emsTileLayerId.desaturated; + } else { + serviceId = emsTileLayerId.bright; + } + } + const tmsService = tmsServices.find(service => { + return service.getId() === serviceId; + }); + return { + url: await tmsService.getUrlTemplate(), + minZoom: await tmsService.getMinZoom(), + maxZoom: await tmsService.getMaxZoom(), + attribution: await tmsService.getHTMLAttribution(), + origin: ORIGIN.EMS, + }; + } + + async getAttributesForTMSLayer(tmsServiceConfig, isDesaturated, isDarkMode) { + if (tmsServiceConfig.origin === ORIGIN.EMS) { + return this._getAttributesForEMSTMSLayer(isDesaturated, isDarkMode); + } else if (tmsServiceConfig.origin === ORIGIN.KIBANA_YML) { + const config = this.tilemapsConfig.deprecated.config; + const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); + return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; + } else { + //this is an older config. need to resolve this dynamically. + if (tmsServiceConfig.id === TMS_IN_YML_ID) { + const config = this.tilemapsConfig.deprecated.config; + const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); + return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; + } else { + //assume ems + return this._getAttributesForEMSTMSLayer(isDesaturated, isDarkMode); + } + } + } + + async _getFileUrlFromEMS(fileLayerConfig) { + const fileLayers = await this._emsClient.getFileLayers(); + const layer = fileLayers.find(fileLayer => { + const hasIdByName = fileLayer.hasId(fileLayerConfig.name); //legacy + const hasIdById = fileLayer.hasId(fileLayerConfig.id); + return hasIdByName || hasIdById; + }); + + if (layer) { + return layer.getDefaultFormatUrl(); + } else { + throw new Error(`File ${fileLayerConfig.name} not recognized`); + } + } + + async getUrlForRegionLayer(fileLayerConfig) { + let url; + if (fileLayerConfig.origin === ORIGIN.EMS) { + url = this._getFileUrlFromEMS(fileLayerConfig); + } else if (fileLayerConfig.layerId && fileLayerConfig.layerId.startsWith(`${ORIGIN.EMS}.`)) { + //fallback for older saved objects + url = this._getFileUrlFromEMS(fileLayerConfig); + } else if ( + fileLayerConfig.layerId && + fileLayerConfig.layerId.startsWith(`${ORIGIN.KIBANA_YML}.`) + ) { + //fallback for older saved objects + url = fileLayerConfig.url; + } else { + //generic fallback + url = fileLayerConfig.url; + } + return url; + } + + async getJsonForRegionLayer(fileLayerConfig) { + const url = await this.getUrlForRegionLayer(fileLayerConfig); + const response = await fetch(url); + return await response.json(); + } +} diff --git a/src/legacy/ui/public/vis/map/zoom_to_precision.ts b/src/plugins/maps_legacy/public/map/zoom_to_precision.ts similarity index 100% rename from src/legacy/ui/public/vis/map/zoom_to_precision.ts rename to src/plugins/maps_legacy/public/map/zoom_to_precision.ts diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts new file mode 100644 index 0000000000000..751be65e1dbf6 --- /dev/null +++ b/src/plugins/maps_legacy/public/plugin.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. + */ + +// @ts-ignore +import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +// @ts-ignore +import { setToasts, setUiSettings, setInjectedVarFunc } from './kibana_services'; +// @ts-ignore +import { ServiceSettings } from './map/service_settings'; +// @ts-ignore +import { getPrecision, getZoomPrecision } from './map/precision'; +import { MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; + +/** + * These are the interfaces with your public contracts. You should export these + * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. + * @public + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface MapsLegacySetupDependencies {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface MapsLegacyStartDependencies {} + +export class MapsLegacyPlugin implements Plugin { + constructor() {} + + public setup(core: CoreSetup, plugins: MapsLegacySetupDependencies) { + setToasts(core.notifications.toasts); + setUiSettings(core.uiSettings); + setInjectedVarFunc(core.injectedMetadata.getInjectedVar); + + return { + serviceSettings: new ServiceSettings(), + getZoomPrecision, + getPrecision, + }; + } + + public start(core: CoreStart, plugins: MapsLegacyStartDependencies) {} +} diff --git a/src/plugins/saved_objects_management/common/index.ts b/src/plugins/saved_objects_management/common/index.ts new file mode 100644 index 0000000000000..67c3ae6d934ab --- /dev/null +++ b/src/plugins/saved_objects_management/common/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts b/src/plugins/saved_objects_management/common/types.ts similarity index 74% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts rename to src/plugins/saved_objects_management/common/types.ts index 6a89142bc9798..be52d8e6486e2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts +++ b/src/plugins/saved_objects_management/common/types.ts @@ -17,8 +17,12 @@ * under the License. */ -import { SavedObject, SavedObjectReference } from 'src/core/public'; +import { SavedObject } from 'src/core/types'; +/** + * The metadata injected into a {@link SavedObject | saved object} when returning + * {@link SavedObjectWithMetadata | enhanced objects} from the plugin API endpoints. + */ export interface SavedObjectMetadata { icon?: string; title?: string; @@ -26,31 +30,19 @@ export interface SavedObjectMetadata { inAppUrl?: { path: string; uiCapabilitiesPath: string }; } +/** + * A {@link SavedObject | saved object} enhanced with meta properties used by the client-side plugin. + */ export type SavedObjectWithMetadata = SavedObject & { meta: SavedObjectMetadata; }; +/** + * Represents a relation between two {@link SavedObject | saved object} + */ export interface SavedObjectRelation { id: string; type: string; relationship: 'child' | 'parent'; meta: SavedObjectMetadata; } - -export interface ObjectField { - type: FieldType; - name: string; - value: any; -} - -export type FieldType = 'text' | 'number' | 'boolean' | 'array' | 'json'; - -export interface FieldState { - value?: any; - invalid?: boolean; -} - -export interface SubmittedFormData { - attributes: any; - references: SavedObjectReference[]; -} diff --git a/src/plugins/saved_objects_management/kibana.json b/src/plugins/saved_objects_management/kibana.json index e1f14b0e3c59d..22135ce4558ae 100644 --- a/src/plugins/saved_objects_management/kibana.json +++ b/src/plugins/saved_objects_management/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["home"] + "requiredPlugins": ["home", "management", "data"], + "optionalPlugins": ["dashboard", "visualizations", "discover"] } diff --git a/src/plugins/saved_objects_management/public/index.ts b/src/plugins/saved_objects_management/public/index.ts index 7fb2f137d7d84..b20b320bc6645 100644 --- a/src/plugins/saved_objects_management/public/index.ts +++ b/src/plugins/saved_objects_management/public/index.ts @@ -22,10 +22,14 @@ import { SavedObjectsManagementPlugin } from './plugin'; export { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart } from './plugin'; export { - ISavedObjectsManagementActionRegistry, + SavedObjectsManagementActionServiceSetup, + SavedObjectsManagementActionServiceStart, SavedObjectsManagementAction, SavedObjectsManagementRecord, + ISavedObjectsManagementServiceRegistry, + SavedObjectsManagementServiceRegistryEntry, } from './services'; +export { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new SavedObjectsManagementPlugin(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts b/src/plugins/saved_objects_management/public/lib/case_conversion.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts rename to src/plugins/saved_objects_management/public/lib/case_conversion.test.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts b/src/plugins/saved_objects_management/public/lib/case_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts rename to src/plugins/saved_objects_management/public/lib/case_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.test.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.test.ts rename to src/plugins/saved_objects_management/public/lib/create_field_list.test.ts index 345716f91ea88..e7d6754ac4d05 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.test.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts @@ -17,8 +17,8 @@ * under the License. */ -import { SimpleSavedObject, SavedObjectReference } from '../../../../../../../../core/public'; -import { savedObjectsServiceMock } from '../../../../../../../../core/public/mocks'; +import { SimpleSavedObject, SavedObjectReference } from '../../../../core/public'; +import { savedObjectsServiceMock } from '../../../../core/public/mocks'; import { createFieldList } from './create_field_list'; const savedObjectClientMock = savedObjectsServiceMock.createStartContract().client; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.ts rename to src/plugins/saved_objects_management/public/lib/create_field_list.ts index 88a1184d5d70f..5d87c11a87198 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.ts @@ -18,10 +18,10 @@ */ import { forOwn, indexBy, isNumber, isBoolean, isPlainObject, isString } from 'lodash'; -import { SimpleSavedObject } from '../../../../../../../../core/public'; -import { castEsToKbnFieldTypeName } from '../../../../../../../../plugins/data/public'; -import { ObjectField } from '../types'; -import { SavedObjectLoader } from '../../../../../../../../plugins/saved_objects/public'; +import { SimpleSavedObject } from '../../../../core/public'; +import { castEsToKbnFieldTypeName } from '../../../data/public'; +import { ObjectField } from '../management_section/types'; +import { SavedObjectLoader } from '../../../saved_objects/public'; const maxRecursiveIterations = 20; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.test.ts b/src/plugins/saved_objects_management/public/lib/extract_export_details.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.test.ts rename to src/plugins/saved_objects_management/public/lib/extract_export_details.test.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.ts b/src/plugins/saved_objects_management/public/lib/extract_export_details.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.ts rename to src/plugins/saved_objects_management/public/lib/extract_export_details.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.ts b/src/plugins/saved_objects_management/public/lib/fetch_export_by_type_and_search.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.ts rename to src/plugins/saved_objects_management/public/lib/fetch_export_by_type_and_search.ts index d3e527b9f96b7..e0f005fab2a3b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.ts +++ b/src/plugins/saved_objects_management/public/lib/fetch_export_by_type_and_search.ts @@ -17,16 +17,15 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; +import { HttpStart } from 'src/core/public'; export async function fetchExportByTypeAndSearch( + http: HttpStart, types: string[], search: string | undefined, includeReferencesDeep: boolean = false ): Promise { - return await kfetch({ - method: 'POST', - pathname: '/api/saved_objects/_export', + return http.post('/api/saved_objects/_export', { body: JSON.stringify({ type: types, search, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_objects.ts b/src/plugins/saved_objects_management/public/lib/fetch_export_objects.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_objects.ts rename to src/plugins/saved_objects_management/public/lib/fetch_export_objects.ts index 744f8ef38af47..745d3758371a3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/fetch_export_objects.ts @@ -17,15 +17,14 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; +import { HttpStart } from 'src/core/public'; export async function fetchExportObjects( + http: HttpStart, objects: any[], includeReferencesDeep: boolean = false ): Promise { - return await kfetch({ - method: 'POST', - pathname: '/api/saved_objects/_export', + return http.post('/api/saved_objects/_export', { body: JSON.stringify({ objects, includeReferencesDeep, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.ts b/src/plugins/saved_objects_management/public/lib/find_objects.ts similarity index 57% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.ts rename to src/plugins/saved_objects_management/public/lib/find_objects.ts index 24e08f0524f62..5a77d3ae2f663 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/find_objects.ts @@ -17,16 +17,27 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; -import { SavedObjectsFindOptions } from 'src/core/public'; +import { HttpStart, SavedObjectsFindOptions } from 'src/core/public'; import { keysToCamelCaseShallow } from './case_conversion'; +import { SavedObjectWithMetadata } from '../types'; -export async function findObjects(findOptions: SavedObjectsFindOptions) { - const response = await kfetch({ - method: 'GET', - pathname: '/api/kibana/management/saved_objects/_find', - query: findOptions as Record, - }); +interface SavedObjectsFindResponse { + total: number; + page: number; + perPage: number; + savedObjects: SavedObjectWithMetadata[]; +} + +export async function findObjects( + http: HttpStart, + findOptions: SavedObjectsFindOptions +): Promise { + const response = await http.get>( + '/api/kibana/management/saved_objects/_find', + { + query: findOptions as Record, + } + ); - return keysToCamelCaseShallow(response); + return keysToCamelCaseShallow(response) as SavedObjectsFindResponse; } diff --git a/test/functional/config.edge.js b/src/plugins/saved_objects_management/public/lib/get_allowed_types.ts similarity index 72% rename from test/functional/config.edge.js rename to src/plugins/saved_objects_management/public/lib/get_allowed_types.ts index ed68b41e8c89a..7d952ebf2ca14 100644 --- a/test/functional/config.edge.js +++ b/src/plugins/saved_objects_management/public/lib/get_allowed_types.ts @@ -17,18 +17,15 @@ * under the License. */ -export default async function({ readConfigFile }) { - const defaultConfig = await readConfigFile(require.resolve('./config')); +import { HttpStart } from 'src/core/public'; - return { - ...defaultConfig.getAll(), - - browser: { - type: 'msedge', - }, +interface GetAllowedTypesResponse { + types: string[]; +} - junit: { - reportName: 'MS Chromium Edge UI Functional Tests', - }, - }; +export async function getAllowedTypes(http: HttpStart) { + const response = await http.get( + '/api/kibana/management/saved_objects/_allowed_types' + ); + return response.types; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_default_title.ts b/src/plugins/saved_objects_management/public/lib/get_default_title.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_default_title.ts rename to src/plugins/saved_objects_management/public/lib/get_default_title.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.test.ts b/src/plugins/saved_objects_management/public/lib/get_relationships.test.ts similarity index 67% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.test.ts rename to src/plugins/saved_objects_management/public/lib/get_relationships.test.ts index b45b51b4de293..d79447378dde5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.test.ts +++ b/src/plugins/saved_objects_management/public/lib/get_relationships.test.ts @@ -17,44 +17,43 @@ * under the License. */ +import { httpServiceMock } from '../../../../core/public/mocks'; import { getRelationships } from './get_relationships'; describe('getRelationships', () => { - it('should make an http request', async () => { - const $http = jest.fn() as any; - const basePath = 'test'; + let httpMock: ReturnType; - await getRelationships('dashboard', '1', ['search', 'index-pattern'], $http, basePath); - expect($http.mock.calls.length).toBe(1); + beforeEach(() => { + httpMock = httpServiceMock.createSetupContract(); + }); + + it('should make an http request', async () => { + await getRelationships(httpMock, 'dashboard', '1', ['search', 'index-pattern']); + expect(httpMock.get).toHaveBeenCalledTimes(1); }); it('should handle successful responses', async () => { - const $http = jest.fn().mockImplementation(() => ({ data: [1, 2] })) as any; - const basePath = 'test'; - - const response = await getRelationships( - 'dashboard', - '1', - ['search', 'index-pattern'], - $http, - basePath - ); + httpMock.get.mockResolvedValue([1, 2]); + + const response = await getRelationships(httpMock, 'dashboard', '1', [ + 'search', + 'index-pattern', + ]); expect(response).toEqual([1, 2]); }); it('should handle errors', async () => { - const $http = jest.fn().mockImplementation(() => { + httpMock.get.mockImplementation(() => { const err = new Error(); (err as any).data = { error: 'Test error', statusCode: 500, }; throw err; - }) as any; - const basePath = 'test'; + }); await expect( - getRelationships('dashboard', '1', ['search', 'index-pattern'], $http, basePath) + getRelationships(httpMock, 'dashboard', '1', ['search', 'index-pattern']) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Test error"`); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.ts b/src/plugins/saved_objects_management/public/lib/get_relationships.ts similarity index 67% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.ts rename to src/plugins/saved_objects_management/public/lib/get_relationships.ts index 07bdf2db68fa2..bf2e651aa6593 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_relationships.ts +++ b/src/plugins/saved_objects_management/public/lib/get_relationships.ts @@ -17,36 +17,30 @@ * under the License. */ -import { IHttpService } from 'angular'; +import { HttpStart } from 'src/core/public'; import { get } from 'lodash'; import { SavedObjectRelation } from '../types'; export async function getRelationships( + http: HttpStart, type: string, id: string, - savedObjectTypes: string[], - $http: IHttpService, - basePath: string + savedObjectTypes: string[] ): Promise { - const url = `${basePath}/api/kibana/management/saved_objects/relationships/${encodeURIComponent( + const url = `/api/kibana/management/saved_objects/relationships/${encodeURIComponent( type )}/${encodeURIComponent(id)}`; - const options = { - method: 'GET', - url, - params: { - savedObjectTypes, - }, - }; - try { - const response = await $http(options); - return response?.data; - } catch (resp) { - const respBody = get(resp, 'data', {}) as any; - const err = new Error(respBody.message || respBody.error || `${resp.status} Response`); + return await http.get(url, { + query: { + savedObjectTypes, + }, + }); + } catch (respError) { + const respBody = get(respError, 'data', {}) as any; + const err = new Error(respBody.message || respBody.error || `${respError.status} Response`); - (err as any).statusCode = respBody.statusCode || resp.status; + (err as any).statusCode = respBody.statusCode || respError.status; (err as any).body = respBody; throw err; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_saved_object_counts.ts b/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts similarity index 72% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_saved_object_counts.ts rename to src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts index d4dda1190bc43..dcf59142e73e3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_saved_object_counts.ts +++ b/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts @@ -17,18 +17,15 @@ * under the License. */ -import { IHttpService } from 'angular'; -import chrome from 'ui/chrome'; +import { HttpStart } from 'src/core/public'; -const apiBase = chrome.addBasePath('/api/kibana/management/saved_objects/scroll'); export async function getSavedObjectCounts( - $http: IHttpService, + http: HttpStart, typesToInclude: string[], - searchString: string + searchString?: string ): Promise> { - const results = await $http.post>(`${apiBase}/counts`, { - typesToInclude, - searchString, - }); - return results.data; + return await http.post>( + `/api/kibana/management/saved_objects/scroll/counts`, + { body: JSON.stringify({ typesToInclude, searchString }) } + ); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_saved_object_label.ts b/src/plugins/saved_objects_management/public/lib/get_saved_object_label.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/get_saved_object_label.ts rename to src/plugins/saved_objects_management/public/lib/get_saved_object_label.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_file.ts b/src/plugins/saved_objects_management/public/lib/import_file.ts similarity index 75% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_file.ts rename to src/plugins/saved_objects_management/public/lib/import_file.ts index 9bd5fbeed3a4c..96263452253ba 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_file.ts +++ b/src/plugins/saved_objects_management/public/lib/import_file.ts @@ -17,14 +17,18 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; +import { HttpStart, SavedObjectsImportError } from 'src/core/public'; -export async function importFile(file: Blob, overwriteAll: boolean = false) { +interface ImportResponse { + success: boolean; + successCount: number; + errors?: SavedObjectsImportError[]; +} + +export async function importFile(http: HttpStart, file: File, overwriteAll: boolean = false) { const formData = new FormData(); formData.append('file', file); - return await kfetch({ - method: 'POST', - pathname: '/api/saved_objects/_import', + return await http.post('/api/saved_objects/_import', { body: formData, headers: { // Important to be undefined, it forces proper headers to be set for FormData diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_legacy_file.test.ts b/src/plugins/saved_objects_management/public/lib/import_legacy_file.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_legacy_file.test.ts rename to src/plugins/saved_objects_management/public/lib/import_legacy_file.test.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_legacy_file.ts b/src/plugins/saved_objects_management/public/lib/import_legacy_file.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/import_legacy_file.ts rename to src/plugins/saved_objects_management/public/lib/import_legacy_file.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.test.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.test.ts rename to src/plugins/saved_objects_management/public/lib/in_app_url.test.ts index c0d6716391a1f..09e08e6ec333b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.test.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Capabilities } from '../../../../../../../../core/public'; +import { Capabilities } from '../../../../core/public'; import { canViewInApp } from './in_app_url'; const createCapabilities = (sections: Record): Capabilities => { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.ts rename to src/plugins/saved_objects_management/public/lib/in_app_url.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.ts b/src/plugins/saved_objects_management/public/lib/index.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.ts rename to src/plugins/saved_objects_management/public/lib/index.ts index ecdfa6549a54e..7021744095651 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.ts +++ b/src/plugins/saved_objects_management/public/lib/index.ts @@ -43,3 +43,5 @@ export { export { getDefaultTitle } from './get_default_title'; export { findObjects } from './find_objects'; export { extractExportDetails, SavedObjectsExportResultDetails } from './extract_export_details'; +export { createFieldList } from './create_field_list'; +export { getAllowedTypes } from './get_allowed_types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/log_legacy_import.ts b/src/plugins/saved_objects_management/public/lib/log_legacy_import.ts similarity index 81% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/log_legacy_import.ts rename to src/plugins/saved_objects_management/public/lib/log_legacy_import.ts index 9bbafe3e69c98..9ec3c85b91c22 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/log_legacy_import.ts +++ b/src/plugins/saved_objects_management/public/lib/log_legacy_import.ts @@ -17,11 +17,8 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; +import { HttpStart } from 'src/core/public'; -export async function logLegacyImport() { - return await kfetch({ - method: 'POST', - pathname: '/api/saved_objects/_log_legacy_import', - }); +export async function logLegacyImport(http: HttpStart) { + return http.post('/api/saved_objects/_log_legacy_import'); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts b/src/plugins/saved_objects_management/public/lib/numeric.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts rename to src/plugins/saved_objects_management/public/lib/numeric.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.test.ts b/src/plugins/saved_objects_management/public/lib/parse_query.test.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.test.ts rename to src/plugins/saved_objects_management/public/lib/parse_query.test.ts index 77b34eccd9c6f..f62234eaf4e94 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.test.ts +++ b/src/plugins/saved_objects_management/public/lib/parse_query.test.ts @@ -25,6 +25,6 @@ describe('getQueryText', () => { getTermClauses: () => [{ value: 'foo' }, { value: 'bar' }], getFieldClauses: () => [{ value: 'lala' }, { value: 'lolo' }], }; - expect(parseQuery({ ast })).toEqual({ queryText: 'foo bar', visibleTypes: 'lala' }); + expect(parseQuery({ ast } as any)).toEqual({ queryText: 'foo bar', visibleTypes: 'lala' }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.ts b/src/plugins/saved_objects_management/public/lib/parse_query.ts similarity index 77% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.ts rename to src/plugins/saved_objects_management/public/lib/parse_query.ts index 9b33deedafd95..f5b7b69ea049c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/parse_query.ts +++ b/src/plugins/saved_objects_management/public/lib/parse_query.ts @@ -17,9 +17,16 @@ * under the License. */ -export function parseQuery(query: any) { - let queryText; - let visibleTypes; +import { Query } from '@elastic/eui'; + +interface ParsedQuery { + queryText?: string; + visibleTypes?: string[]; +} + +export function parseQuery(query: Query): ParsedQuery { + let queryText: string | undefined; + let visibleTypes: string[] | undefined; if (query) { if (query.ast.getTermClauses().length) { @@ -29,7 +36,7 @@ export function parseQuery(query: any) { .join(' '); } if (query.ast.getFieldClauses('type')) { - visibleTypes = query.ast.getFieldClauses('type')[0].value; + visibleTypes = query.ast.getFieldClauses('type')[0].value as string[]; } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.test.ts b/src/plugins/saved_objects_management/public/lib/process_import_response.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.test.ts rename to src/plugins/saved_objects_management/public/lib/process_import_response.test.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts b/src/plugins/saved_objects_management/public/lib/process_import_response.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/process_import_response.ts rename to src/plugins/saved_objects_management/public/lib/process_import_response.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.test.ts b/src/plugins/saved_objects_management/public/lib/resolve_import_errors.test.ts similarity index 90% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.test.ts rename to src/plugins/saved_objects_management/public/lib/resolve_import_errors.test.ts index b94b0a9d1291f..86eebad7ae787 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.test.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_import_errors.test.ts @@ -17,14 +17,10 @@ * under the License. */ -jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); - import { SavedObjectsImportUnknownError } from 'src/core/public'; -import { kfetch } from 'ui/kfetch'; +import { httpServiceMock } from '../../../../core/public/mocks'; import { resolveImportErrors } from './resolve_import_errors'; -const kfetchMock = kfetch as jest.Mock; - function getFormData(form: Map) { const formData: Record = {}; for (const [key, val] of form.entries()) { @@ -39,13 +35,20 @@ function getFormData(form: Map) { describe('resolveImportErrors', () => { const getConflictResolutions = jest.fn(); + let httpMock: ReturnType; beforeEach(() => { + httpMock = httpServiceMock.createSetupContract(); jest.resetAllMocks(); }); + const extractBodyFromCall = (index: number): Map => { + return (httpMock.post.mock.calls[index] as any)[1].body; + }; + test('works with empty import failures', async () => { const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -62,6 +65,7 @@ Object { test(`doesn't retry if only unknown failures are passed in`, async () => { const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -98,7 +102,7 @@ Object { }); test('resolves conflicts', async () => { - kfetchMock.mockResolvedValueOnce({ + httpMock.post.mockResolvedValueOnce({ success: true, successCount: 1, }); @@ -107,6 +111,7 @@ Object { 'a:2': false, }); const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -139,7 +144,8 @@ Object { "status": "success", } `); - const formData = getFormData(kfetchMock.mock.calls[0][0].body); + + const formData = getFormData(extractBodyFromCall(0)); expect(formData).toMatchInlineSnapshot(` Object { "file": "undefined", @@ -156,12 +162,13 @@ Object { }); test('resolves missing references', async () => { - kfetchMock.mockResolvedValueOnce({ + httpMock.post.mockResolvedValueOnce({ success: true, successCount: 2, }); getConflictResolutions.mockResolvedValueOnce({}); const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -203,7 +210,7 @@ Object { "status": "success", } `); - const formData = getFormData(kfetchMock.mock.calls[0][0].body); + const formData = getFormData(extractBodyFromCall(0)); expect(formData).toMatchInlineSnapshot(` Object { "file": "undefined", @@ -232,6 +239,7 @@ Object { test(`doesn't resolve missing references if newIndexPatternId isn't defined`, async () => { getConflictResolutions.mockResolvedValueOnce({}); const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -276,7 +284,7 @@ Object { }); test('handles missing references then conflicts on the same errored objects', async () => { - kfetchMock.mockResolvedValueOnce({ + httpMock.post.mockResolvedValueOnce({ success: false, successCount: 0, errors: [ @@ -289,7 +297,7 @@ Object { }, ], }); - kfetchMock.mockResolvedValueOnce({ + httpMock.post.mockResolvedValueOnce({ success: true, successCount: 1, }); @@ -298,6 +306,7 @@ Object { 'a:1': true, }); const result = await resolveImportErrors({ + http: httpMock, getConflictResolutions, state: { importCount: 0, @@ -334,7 +343,7 @@ Object { "status": "success", } `); - const formData1 = getFormData(kfetchMock.mock.calls[0][0].body); + const formData1 = getFormData(extractBodyFromCall(0)); expect(formData1).toMatchInlineSnapshot(` Object { "file": "undefined", @@ -354,7 +363,7 @@ Object { ], } `); - const formData2 = getFormData(kfetchMock.mock.calls[1][0].body); + const formData2 = getFormData(extractBodyFromCall(1)); expect(formData2).toMatchInlineSnapshot(` Object { "file": "undefined", diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.ts b/src/plugins/saved_objects_management/public/lib/resolve_import_errors.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.ts rename to src/plugins/saved_objects_management/public/lib/resolve_import_errors.ts index dcc282402147d..0aea7114bad1c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_import_errors.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_import_errors.ts @@ -17,7 +17,7 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; +import { HttpStart } from 'src/core/public'; import { FailedImport } from './process_import_response'; interface RetryObject { @@ -27,13 +27,11 @@ interface RetryObject { replaceReferences?: any[]; } -async function callResolveImportErrorsApi(file: File, retries: any) { +async function callResolveImportErrorsApi(http: HttpStart, file: File, retries: any) { const formData = new FormData(); formData.append('file', file); formData.append('retries', JSON.stringify(retries)); - return await kfetch({ - method: 'POST', - pathname: '/api/saved_objects/_resolve_import_errors', + return http.post('/api/saved_objects/_resolve_import_errors', { headers: { // Important to be undefined, it forces proper headers to be set for FormData 'Content-Type': undefined, @@ -100,9 +98,11 @@ function mapImportFailureToRetryObject({ } export async function resolveImportErrors({ + http, getConflictResolutions, state, }: { + http: HttpStart; getConflictResolutions: (objects: any[]) => Promise>; state: { importCount: number; failedImports?: FailedImport[] } & Record; }) { @@ -170,7 +170,7 @@ export async function resolveImportErrors({ } // Call API - const response = await callResolveImportErrorsApi(file, retries); + const response = await callResolveImportErrorsApi(http, file, retries); successImportCount += response.successCount; importFailures = []; for (const { error, ...obj } of response.errors || []) { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts rename to src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts index dc6d2643145ff..23c2b75169555 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts @@ -23,11 +23,8 @@ import { saveObjects, saveObject, } from './resolve_saved_objects'; -import { - SavedObject, - SavedObjectLoader, -} from '../../../../../../../../plugins/saved_objects/public'; -import { IndexPatternsContract } from '../../../../../../../../plugins/data/public'; +import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; +import { IndexPatternsContract } from '../../../data/public'; class SavedObjectNotFound extends Error { constructor(options: Record) { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts rename to src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index d9473367f7502..15e03ed39d88c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -20,15 +20,8 @@ import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; import { OverlayStart, SavedObjectReference } from 'src/core/public'; -import { - SavedObject, - SavedObjectLoader, -} from '../../../../../../../../plugins/saved_objects/public'; -import { - IndexPatternsContract, - IIndexPattern, - createSearchSource, -} from '../../../../../../../../plugins/data/public'; +import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; +import { IndexPatternsContract, IIndexPattern, createSearchSource } from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -55,7 +48,7 @@ function addJsonFieldToIndexPattern( target[fieldName] = JSON.parse(sourceString); } catch (error) { throw new Error( - i18n.translate('kbn.management.objects.parsingFieldErrorMessage', { + i18n.translate('savedObjectsManagement.parsingFieldErrorMessage', { defaultMessage: 'Error encountered parsing {fieldName} for index pattern {indexName}: {errorMessage}', values: { @@ -103,18 +96,21 @@ async function importIndexPattern( if (!newId) { // We can override and we want to prompt for confirmation const isConfirmed = await openConfirm( - i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', { + i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { values: { title }, defaultMessage: "Are you sure you want to overwrite '{title}'?", }), { - title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', { + title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', { defaultMessage: 'Overwrite {type}?', values: { type }, }), - confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', { - defaultMessage: 'Overwrite', - }), + confirmButtonText: i18n.translate( + 'savedObjectsManagement.indexPattern.confirmOverwriteButton', + { + defaultMessage: 'Overwrite', + } + ), } ); diff --git a/src/legacy/core_plugins/vis_type_vega/public/shim/index.ts b/src/plugins/saved_objects_management/public/management_section/index.ts similarity index 93% rename from src/legacy/core_plugins/vis_type_vega/public/shim/index.ts rename to src/plugins/saved_objects_management/public/management_section/index.ts index cfc7b62ff4f86..1bccb2102f3b4 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/shim/index.ts +++ b/src/plugins/saved_objects_management/public/management_section/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './legacy_dependencies_plugin'; +export { mountManagementSection } from './mount_section'; diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx new file mode 100644 index 0000000000000..6f03f97079bb6 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -0,0 +1,211 @@ +/* + * 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, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route, useParams, useLocation } from 'react-router-dom'; +import { parse } from 'query-string'; +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { CoreSetup, CoreStart, ChromeBreadcrumb, Capabilities } from 'src/core/public'; +import { ManagementAppMountParams } from '../../../management/public'; +import { DataPublicPluginStart } from '../../../data/public'; +import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin'; +import { + ISavedObjectsManagementServiceRegistry, + SavedObjectsManagementActionServiceStart, +} from '../services'; +import { SavedObjectsTable } from './objects_table'; +import { SavedObjectEdition } from './object_view'; +import { getAllowedTypes } from './../lib'; + +interface MountParams { + core: CoreSetup; + serviceRegistry: ISavedObjectsManagementServiceRegistry; + mountParams: ManagementAppMountParams; +} + +let allowedObjectTypes: string[] | undefined; + +export const mountManagementSection = async ({ + core, + mountParams, + serviceRegistry, +}: MountParams) => { + const [coreStart, { data }, pluginStart] = await core.getStartServices(); + const { element, basePath, setBreadcrumbs } = mountParams; + if (allowedObjectTypes === undefined) { + allowedObjectTypes = await getAllowedTypes(coreStart.http); + } + + const capabilities = coreStart.application.capabilities; + + ReactDOM.render( + + + + + + + + + + + + + + + + , + element + ); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; + +const RedirectToHomeIfUnauthorized: React.FunctionComponent<{ + capabilities: Capabilities; +}> = ({ children, capabilities }) => { + const allowed = capabilities?.management?.kibana?.objects ?? false; + if (!allowed) { + window.location.hash = '/home'; + return null; + } + return children! as React.ReactElement; +}; + +const SavedObjectsEditionPage = ({ + coreStart, + serviceRegistry, + setBreadcrumbs, +}: { + coreStart: CoreStart; + serviceRegistry: ISavedObjectsManagementServiceRegistry; + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; +}) => { + const { service: serviceName, id } = useParams<{ service: string; id: string }>(); + const capabilities = coreStart.application.capabilities; + + const { search } = useLocation(); + const query = parse(search); + const service = serviceRegistry.get(serviceName); + + useEffect(() => { + setBreadcrumbs([ + { + text: i18n.translate('savedObjectsManagement.breadcrumb.index', { + defaultMessage: 'Saved objects', + }), + href: '#/management/kibana/objects', + }, + { + text: i18n.translate('savedObjectsManagement.breadcrumb.edit', { + defaultMessage: 'Edit {savedObjectType}', + values: { savedObjectType: service?.service.type ?? 'object' }, + }), + }, + ]); + }, [setBreadcrumbs, service]); + + return ( + + ); +}; + +const SavedObjectsTablePage = ({ + coreStart, + dataStart, + allowedTypes, + serviceRegistry, + actionRegistry, + setBreadcrumbs, +}: { + coreStart: CoreStart; + dataStart: DataPublicPluginStart; + allowedTypes: string[]; + serviceRegistry: ISavedObjectsManagementServiceRegistry; + actionRegistry: SavedObjectsManagementActionServiceStart; + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; +}) => { + const capabilities = coreStart.application.capabilities; + const itemsPerPage = coreStart.uiSettings.get('savedObjects:perPage', 50); + + useEffect(() => { + setBreadcrumbs([ + { + text: i18n.translate('savedObjectsManagement.breadcrumb.index', { + defaultMessage: 'Saved objects', + }), + href: '#/management/kibana/objects', + }, + ]); + }, [setBreadcrumbs]); + + return ( + { + const { editUrl } = savedObject.meta; + if (editUrl) { + // previously, kbnUrl.change(object.meta.editUrl); was used. + // using direct access to location.hash seems the only option for now, + // as using react-router-dom will prefix the url with the router's basename + // which should be ignored there. + window.location.hash = editUrl; + } + }} + canGoInApp={savedObject => { + const { inAppUrl } = savedObject.meta; + return inAppUrl ? get(capabilities, inAppUrl.uiCapabilitiesPath) : false; + }} + /> + ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap similarity index 96% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap rename to src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index 7e1f7ea12b014..d56776c2be9d7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -23,7 +23,7 @@ exports[`Intro component renders correctly 1`] = ` > } @@ -37,7 +37,7 @@ exports[`Intro component renders correctly 1`] = ` > Proceed with caution! @@ -53,7 +53,7 @@ exports[`Intro component renders correctly 1`] = `

Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be. diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap rename to src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap index ac565a000813e..d5372fd5b18d9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap @@ -10,7 +10,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] = title={ } @@ -39,7 +39,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] = > There is a problem with this saved object @@ -55,7 +55,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
The index pattern associated with this object no longer exists. @@ -64,7 +64,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
If you know what this error means, go ahead and fix it — otherwise click the delete button above. @@ -87,7 +87,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type title={ } @@ -116,7 +116,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type > There is a problem with this saved object @@ -132,7 +132,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
A field associated with this object no longer exists in the index pattern. @@ -141,7 +141,7 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
If you know what this error means, go ahead and fix it — otherwise click the delete button above. @@ -164,7 +164,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = ` title={ } @@ -193,7 +193,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = ` > There is a problem with this saved object @@ -209,7 +209,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
The saved search associated with this object no longer exists. @@ -218,7 +218,7 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
If you know what this error means, go ahead and fix it — otherwise click the delete button above. @@ -241,7 +241,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = ` title={ } @@ -270,7 +270,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = ` > There is a problem with this saved object @@ -287,7 +287,7 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
If you know what this error means, go ahead and fix it — otherwise click the delete button above. diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.test.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx index 1ed0b57e400b8..1b69eb4240d68 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx @@ -104,9 +104,9 @@ export class Field extends PureComponent { id={this.fieldId} label={ !!currentValue ? ( - + ) : ( - + ) } checked={!!currentValue} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx index 7270d41eef529..04be7ee3ce207 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx @@ -29,15 +29,11 @@ import { import { cloneDeep, set } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - SimpleSavedObject, - SavedObjectsClientContract, -} from '../../../../../../../../../core/public'; - -import { SavedObjectLoader } from '../../../../../../../../../plugins/saved_objects/public'; +import { SimpleSavedObject, SavedObjectsClientContract } from '../../../../../../core/public'; +import { SavedObjectLoader } from '../../../../../saved_objects/public'; import { Field } from './field'; import { ObjectField, FieldState, SubmittedFormData } from '../../types'; -import { createFieldList } from '../../lib/create_field_list'; +import { createFieldList } from '../../../lib'; interface FormProps { object: SimpleSavedObject; @@ -96,7 +92,7 @@ export class Form extends Component { { data-test-subj="savedObjectEditSave" > @@ -117,14 +113,14 @@ export class Form extends Component { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.test.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx index 641493e0cbaa8..305d953c4990b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx @@ -52,7 +52,7 @@ export const Header = ({ {canEdit ? (

@@ -60,7 +60,7 @@ export const Header = ({ ) : (

@@ -79,7 +79,7 @@ export const Header = ({ data-test-subj="savedObjectEditViewInApp" > @@ -96,7 +96,7 @@ export const Header = ({ data-test-subj="savedObjectEditDelete" > diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts b/src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts rename to src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.test.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx index 098ad71345d49..920a5fcbcb02e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx @@ -26,7 +26,7 @@ export const Intro = () => { } @@ -35,7 +35,7 @@ export const Intro = () => { >
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.test.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx similarity index 87% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx index c3d18855f6c9a..1a63f7eaf4819 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx @@ -31,21 +31,21 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => { case 'search': return ( ); case 'index-pattern': return ( ); case 'index-pattern-field': return ( ); @@ -58,7 +58,7 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => { } @@ -68,7 +68,7 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
{getMessage()}
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/index.js b/src/plugins/saved_objects_management/public/management_section/object_view/index.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/index.js rename to src/plugins/saved_objects_management/public/management_section/object_view/index.ts index cdeebdbf7b63a..a823923536d31 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/index.js +++ b/src/plugins/saved_objects_management/public/management_section/object_view/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { Flyout } from './flyout'; +export { SavedObjectEdition } from './saved_object_view'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx rename to src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 4984fe3e6d6b8..f714970a5cac3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -26,16 +26,16 @@ import { OverlayStart, NotificationsStart, SimpleSavedObject, -} from '../../../../../../../core/public'; -import { ISavedObjectsManagementRegistry } from '../../saved_object_registry'; -import { Header, NotFoundErrors, Intro, Form } from './components/object_view'; -import { canViewInApp } from './lib/in_app_url'; -import { SubmittedFormData } from './types'; +} from '../../../../../core/public'; +import { ISavedObjectsManagementServiceRegistry } from '../../services'; +import { Header, NotFoundErrors, Intro, Form } from './components'; +import { canViewInApp } from '../../lib'; +import { SubmittedFormData } from '../types'; interface SavedObjectEditionProps { id: string; serviceName: string; - serviceRegistry: ISavedObjectsManagementRegistry; + serviceRegistry: ISavedObjectsManagementServiceRegistry; capabilities: Capabilities; overlays: OverlayStart; notifications: NotificationsStart; @@ -135,17 +135,17 @@ export class SavedObjectEdition extends Component< const { type, object } = this.state; const confirmed = await overlays.openConfirm( - i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { + i18n.translate('savedObjectsManagement.deleteConfirm.modalDescription', { defaultMessage: 'This action permanently removes the object from Kibana.', }), { confirmButtonText: i18n.translate( - 'kbn.management.objects.confirmModalOptions.deleteButtonLabel', + 'savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel', { defaultMessage: 'Delete', } ), - title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', { + title: i18n.translate('savedObjectsManagement.deleteConfirm.modalTitle', { defaultMessage: `Delete '{title}'?`, values: { title: object?.attributes?.title || 'saved Kibana object', diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap similarity index 73% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap rename to src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 2c0a5d8f6b8f1..fe64df6ff51d1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ObjectsTable delete should show a confirm modal 1`] = ` +exports[`SavedObjectsTable delete should show a confirm modal 1`] = ` } confirmButtonText={ } @@ -23,7 +23,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` title={ } @@ -31,7 +31,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `

@@ -58,12 +58,10 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` Array [ Object { "id": "1", - "title": "Title 1", "type": "index-pattern", }, Object { "id": "3", - "title": "Title 2", "type": "dashboard", }, ] @@ -76,7 +74,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
`; -exports[`ObjectsTable export should allow the user to choose when exporting all 1`] = ` +exports[`SavedObjectsTable export should allow the user to choose when exporting all 1`] = ` @@ -84,7 +82,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all } @@ -149,7 +147,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all label={ } @@ -173,7 +171,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all > @@ -187,7 +185,7 @@ exports[`ObjectsTable export should allow the user to choose when exporting all > @@ -199,23 +197,87 @@ exports[`ObjectsTable export should allow the user to choose when exporting all `; -exports[`ObjectsTable import should show the flyout 1`] = ` +exports[`SavedObjectsTable import should show the flyout 1`] = ` `; -exports[`ObjectsTable relationships should show the flyout 1`] = ` +exports[`SavedObjectsTable relationships should show the flyout 1`] = ` `; -exports[`ObjectsTable should render normally 1`] = ` +exports[`SavedObjectsTable should render normally 1`] = ` @@ -251,7 +313,23 @@ exports[`ObjectsTable should render normally 1`] = ` size="xs" />

@@ -36,7 +36,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` title={ } @@ -44,7 +44,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `

, @@ -131,7 +131,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` > @@ -148,7 +148,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` > @@ -164,6 +164,30 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = ` Array [ Object { "getConflictResolutions": [Function], + "http": Object { + "addLoadingCountSource": [MockFunction], + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + "serverBasePath": "", + }, + "delete": [MockFunction], + "fetch": [MockFunction], + "get": [MockFunction], + "getLoadingCount$": [MockFunction], + "head": [MockFunction], + "intercept": [MockFunction], + "options": [MockFunction], + "patch": [MockFunction], + "post": [MockFunction], + "put": [MockFunction], + }, "state": Object { "conflictedIndexPatterns": undefined, "conflictedSavedObjectsLinkedToSavedSearches": undefined, @@ -243,7 +267,7 @@ exports[`Flyout conflicts should handle errors 1`] = ` title={ } @@ -251,7 +275,7 @@ exports[`Flyout conflicts should handle errors 1`] = `

} @@ -280,7 +304,7 @@ exports[`Flyout errors should display unsupported type errors properly 1`] = `

@@ -331,7 +355,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` title={ } @@ -339,7 +363,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `

@@ -356,7 +380,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` title={ } @@ -364,7 +388,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `

, @@ -462,7 +486,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` > @@ -479,7 +503,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` > @@ -498,7 +522,7 @@ Array [ title={ } @@ -506,7 +530,7 @@ Array [

@@ -518,7 +542,7 @@ Array [ title={ } @@ -526,7 +550,7 @@ Array [

, @@ -548,7 +572,7 @@ Array [ title={ } @@ -578,7 +602,7 @@ exports[`Flyout should render import step 1`] = `

@@ -595,7 +619,7 @@ exports[`Flyout should render import step 1`] = ` label={ } @@ -607,7 +631,7 @@ exports[`Flyout should render import step 1`] = ` initialPromptText={ } @@ -628,7 +652,7 @@ exports[`Flyout should render import step 1`] = ` label={ } @@ -651,7 +675,7 @@ exports[`Flyout should render import step 1`] = ` > @@ -668,7 +692,7 @@ exports[`Flyout should render import step 1`] = ` > diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/__snapshots__/header.test.js.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/header.test.tsx.snap similarity index 88% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/__snapshots__/header.test.js.snap rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/header.test.tsx.snap index 51bd51a5e2e58..642a5030e4ec0 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/__snapshots__/header.test.js.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/header.test.tsx.snap @@ -13,7 +13,7 @@ exports[`Header should render normally 1`] = `

@@ -38,7 +38,7 @@ exports[`Header should render normally 1`] = ` > @@ -73,7 +73,7 @@ exports[`Header should render normally 1`] = ` > @@ -93,7 +93,7 @@ exports[`Header should render normally 1`] = ` > diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap similarity index 99% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap index 728944f3ccbfe..a8bb691cd54e9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap @@ -202,7 +202,7 @@ exports[`Relationships should render errors 1`] = ` title={ } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap similarity index 93% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap index a4dcfb9c38184..d09dd6f8b868b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap @@ -36,7 +36,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` > , @@ -51,7 +51,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` > @@ -72,7 +72,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` label={ } @@ -83,7 +83,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` label={ } @@ -106,7 +106,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` > @@ -171,6 +171,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` items={ Array [ Object { + "attributes": Object {}, "id": "1", "meta": Object { "editUrl": "#/management/kibana/index_patterns/1", @@ -181,6 +182,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` }, "title": "MyIndexPattern*", }, + "references": Array [], "type": "index-pattern", }, ] @@ -249,7 +251,7 @@ exports[`Table should render normally 1`] = ` > , @@ -264,7 +266,7 @@ exports[`Table should render normally 1`] = ` > @@ -285,7 +287,7 @@ exports[`Table should render normally 1`] = ` label={ } @@ -296,7 +298,7 @@ exports[`Table should render normally 1`] = ` label={ } @@ -319,7 +321,7 @@ exports[`Table should render normally 1`] = ` > @@ -384,6 +386,7 @@ exports[`Table should render normally 1`] = ` items={ Array [ Object { + "attributes": Object {}, "id": "1", "meta": Object { "editUrl": "#/management/kibana/index_patterns/1", @@ -394,6 +397,7 @@ exports[`Table should render normally 1`] = ` }, "title": "MyIndexPattern*", }, + "references": Array [], "type": "index-pattern", }, ] diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.mocks.ts b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.mocks.ts new file mode 100644 index 0000000000000..b5361d212954f --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.mocks.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const importFileMock = jest.fn(); +jest.doMock('../../../lib/import_file', () => ({ + importFile: importFileMock, +})); + +export const resolveImportErrorsMock = jest.fn(); +jest.doMock('../../../lib/resolve_import_errors', () => ({ + resolveImportErrors: resolveImportErrorsMock, +})); + +export const importLegacyFileMock = jest.fn(); +jest.doMock('../../../lib/import_legacy_file', () => ({ + importLegacyFile: importLegacyFileMock, +})); + +export const resolveSavedObjectsMock = jest.fn(); +export const resolveSavedSearchesMock = jest.fn(); +export const resolveIndexPatternConflictsMock = jest.fn(); +export const saveObjectsMock = jest.fn(); +jest.doMock('../../../lib/resolve_saved_objects', () => ({ + resolveSavedObjects: resolveSavedObjectsMock, + resolveSavedSearches: resolveSavedSearchesMock, + resolveIndexPatternConflicts: resolveIndexPatternConflictsMock, + saveObjects: saveObjectsMock, +})); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx similarity index 75% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx index 0d16e0ae35dd6..5d713ff044f24 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx @@ -17,68 +17,62 @@ * under the License. */ +import { + importFileMock, + importLegacyFileMock, + resolveImportErrorsMock, + resolveIndexPatternConflictsMock, + resolveSavedObjectsMock, + resolveSavedSearchesMock, + saveObjectsMock, +} from './flyout.test.mocks'; + import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../../../../plugins/index_pattern_management/public/mocks'; -import { Flyout } from '../flyout'; - -jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); - -jest.mock('../../../../../lib/import_file', () => ({ - importFile: jest.fn(), -})); - -jest.mock('../../../../../lib/resolve_import_errors', () => ({ - resolveImportErrors: jest.fn(), -})); - -jest.mock('ui/chrome', () => ({ - addBasePath: () => {}, - getInjected: () => ['index-pattern', 'visualization', 'dashboard', 'search'], -})); - -jest.mock('../../../../../lib/import_legacy_file', () => ({ - importLegacyFile: jest.fn(), -})); - -jest.mock('../../../../../lib/resolve_saved_objects', () => ({ - resolveSavedObjects: jest.fn(), - resolveSavedSearches: jest.fn(), - resolveIndexPatternConflicts: jest.fn(), - saveObjects: jest.fn(), -})); - -jest.mock('../../../../../../../../../../../../plugins/index_pattern_management/public', () => ({ - setup: mockManagementPlugin.createSetupContract(), - start: mockManagementPlugin.createStartContract(), -})); - -jest.mock('ui/notify', () => ({})); - -const defaultProps = { - close: jest.fn(), - done: jest.fn(), - services: [], - newIndexPatternUrl: '', - getConflictResolutions: jest.fn(), - confirmModalPromise: jest.fn(), - indexPatterns: { - getFields: jest.fn().mockImplementation(() => [{ id: '1' }, { id: '2' }]), - }, -}; - -const mockFile = { +import { coreMock } from '../../../../../../core/public/mocks'; +import { serviceRegistryMock } from '../../../services/service_registry.mock'; +import { Flyout, FlyoutProps, FlyoutState } from './flyout'; +import { ShallowWrapper } from 'enzyme'; + +const mockFile = ({ name: 'foo.ndjson', path: '/home/foo.ndjson', -}; -const legacyMockFile = { +} as unknown) as File; +const legacyMockFile = ({ name: 'foo.json', path: '/home/foo.json', -}; +} as unknown) as File; describe('Flyout', () => { + let defaultProps: FlyoutProps; + + const shallowRender = (props: FlyoutProps) => { + return (shallowWithI18nProvider() as unknown) as ShallowWrapper< + FlyoutProps, + FlyoutState, + Flyout + >; + }; + + beforeEach(() => { + const { http, overlays } = coreMock.createStart(); + + defaultProps = { + close: jest.fn(), + done: jest.fn(), + newIndexPatternUrl: '', + indexPatterns: { + getFields: jest.fn().mockImplementation(() => [{ id: '1' }, { id: '2' }]), + } as any, + overlays, + http, + allowedTypes: ['search', 'index-pattern', 'visualization'], + serviceRegistry: serviceRegistryMock.create(), + }; + }); + it('should render import step', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -89,7 +83,7 @@ describe('Flyout', () => { }); it('should toggle the overwrite all control', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -102,7 +96,7 @@ describe('Flyout', () => { }); it('should allow picking a file', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -115,7 +109,7 @@ describe('Flyout', () => { }); it('should allow removing a file', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await Promise.resolve(); @@ -130,22 +124,21 @@ describe('Flyout', () => { }); it('should handle invalid files', async () => { - const { importLegacyFile } = require('../../../../../lib/import_legacy_file'); - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); - importLegacyFile.mockImplementation(() => { + importLegacyFileMock.mockImplementation(() => { throw new Error('foobar'); }); await component.instance().legacyImport(); expect(component.state('error')).toBe('The file could not be processed.'); - importLegacyFile.mockImplementation(() => ({ + importLegacyFileMock.mockImplementation(() => ({ invalid: true, })); @@ -156,11 +149,8 @@ describe('Flyout', () => { }); describe('conflicts', () => { - const { importFile } = require('../../../../../lib/import_file'); - const { resolveImportErrors } = require('../../../../../lib/resolve_import_errors'); - beforeEach(() => { - importFile.mockImplementation(() => ({ + importFileMock.mockImplementation(() => ({ success: false, successCount: 0, errors: [ @@ -180,7 +170,7 @@ describe('Flyout', () => { }, ], })); - resolveImportErrors.mockImplementation(() => ({ + resolveImportErrorsMock.mockImplementation(() => ({ status: 'success', importCount: 1, failedImports: [], @@ -188,7 +178,7 @@ describe('Flyout', () => { }); it('should figure out unmatchedReferences', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -198,7 +188,7 @@ describe('Flyout', () => { component.setState({ file: mockFile, isLegacyFile: false }); await component.instance().import(); - expect(importFile).toHaveBeenCalledWith(mockFile, true); + expect(importFileMock).toHaveBeenCalledWith(defaultProps.http, mockFile, true); expect(component.state()).toMatchObject({ conflictedIndexPatterns: undefined, conflictedSavedObjectsLinkedToSavedSearches: undefined, @@ -223,7 +213,7 @@ describe('Flyout', () => { }); it('should allow conflict resolution', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -239,7 +229,7 @@ describe('Flyout', () => { // Ensure we can change the resolution component.instance().onIndexChanged('MyIndexPattern*', { target: { value: '2' } }); - expect(component.state('unmatchedReferences')[0].newIndexPatternId).toBe('2'); + expect(component.state('unmatchedReferences')![0].newIndexPatternId).toBe('2'); // Let's resolve now await component @@ -247,18 +237,18 @@ describe('Flyout', () => { .simulate('click'); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); - expect(resolveImportErrors).toMatchSnapshot(); + expect(resolveImportErrorsMock).toMatchSnapshot(); }); it('should handle errors', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); - resolveImportErrors.mockImplementation(() => ({ + resolveImportErrorsMock.mockImplementation(() => ({ status: 'success', importCount: 0, failedImports: [ @@ -303,18 +293,15 @@ describe('Flyout', () => { }); describe('errors', () => { - const { importFile } = require('../../../../../lib/import_file'); - const { resolveImportErrors } = require('../../../../../lib/resolve_import_errors'); - it('should display unsupported type errors properly', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await Promise.resolve(); // Ensure the state changes are reflected component.update(); - importFile.mockImplementation(() => ({ + importFileMock.mockImplementation(() => ({ success: false, successCount: 0, errors: [ @@ -328,7 +315,7 @@ describe('Flyout', () => { }, ], })); - resolveImportErrors.mockImplementation(() => ({ + resolveImportErrorsMock.mockImplementation(() => ({ status: 'success', importCount: 0, failedImports: [ @@ -372,14 +359,6 @@ describe('Flyout', () => { }); describe('legacy conflicts', () => { - const { importLegacyFile } = require('../../../../../lib/import_legacy_file'); - const { - resolveSavedObjects, - resolveSavedSearches, - resolveIndexPatternConflicts, - saveObjects, - } = require('../../../../../lib/resolve_saved_objects'); - const mockData = [ { _id: '1', @@ -406,7 +385,7 @@ describe('Flyout', () => { }, obj: { searchSource: { - getOwnField: field => { + getOwnField: (field: string) => { if (field === 'index') { return 'MyIndexPattern*'; } @@ -426,8 +405,8 @@ describe('Flyout', () => { const mockConflictedSearchDocs = [3]; beforeEach(() => { - importLegacyFile.mockImplementation(() => mockData); - resolveSavedObjects.mockImplementation(() => ({ + importLegacyFileMock.mockImplementation(() => mockData); + resolveSavedObjectsMock.mockImplementation(() => ({ conflictedIndexPatterns: mockConflictedIndexPatterns, conflictedSavedObjectsLinkedToSavedSearches: mockConflictedSavedObjectsLinkedToSavedSearches, conflictedSearchDocs: mockConflictedSearchDocs, @@ -437,7 +416,7 @@ describe('Flyout', () => { }); it('should figure out unmatchedReferences', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -447,14 +426,14 @@ describe('Flyout', () => { component.setState({ file: legacyMockFile, isLegacyFile: true }); await component.instance().legacyImport(); - expect(importLegacyFile).toHaveBeenCalledWith(legacyMockFile); + expect(importLegacyFileMock).toHaveBeenCalledWith(legacyMockFile); // Remove the last element from data since it should be filtered out - expect(resolveSavedObjects).toHaveBeenCalledWith( + expect(resolveSavedObjectsMock).toHaveBeenCalledWith( mockData.slice(0, 2).map(doc => ({ ...doc, _migrationVersion: {} })), true, - defaultProps.services, + defaultProps.serviceRegistry.all().map(s => s.service), defaultProps.indexPatterns, - defaultProps.confirmModalPromise + defaultProps.overlays.openConfirm ); expect(component.state()).toMatchObject({ @@ -492,7 +471,7 @@ describe('Flyout', () => { }); it('should allow conflict resolution', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -508,7 +487,7 @@ describe('Flyout', () => { // Ensure we can change the resolution component.instance().onIndexChanged('MyIndexPattern*', { target: { value: '2' } }); - expect(component.state('unmatchedReferences')[0].newIndexPatternId).toBe('2'); + expect(component.state('unmatchedReferences')![0].newIndexPatternId).toBe('2'); // Let's resolve now await component @@ -516,33 +495,33 @@ describe('Flyout', () => { .simulate('click'); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); - expect(resolveIndexPatternConflicts).toHaveBeenCalledWith( + expect(resolveIndexPatternConflictsMock).toHaveBeenCalledWith( component.instance().resolutions, mockConflictedIndexPatterns, true, defaultProps.indexPatterns ); - expect(saveObjects).toHaveBeenCalledWith( + expect(saveObjectsMock).toHaveBeenCalledWith( mockConflictedSavedObjectsLinkedToSavedSearches, true ); - expect(resolveSavedSearches).toHaveBeenCalledWith( + expect(resolveSavedSearchesMock).toHaveBeenCalledWith( mockConflictedSearchDocs, - defaultProps.services, + defaultProps.serviceRegistry.all().map(s => s.service), defaultProps.indexPatterns, true ); }); it('should handle errors', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(defaultProps); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); - resolveIndexPatternConflicts.mockImplementation(() => { + resolveIndexPatternConflictsMock.mockImplementation(() => { throw new Error('foobar'); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx similarity index 77% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index da2221bb54203..45788dcb601ae 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -18,7 +18,6 @@ */ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { take, get as getField } from 'lodash'; import { EuiFlyout, @@ -32,6 +31,7 @@ import { EuiForm, EuiFormRow, EuiSwitch, + // @ts-ignore EuiFilePicker, EuiInMemoryTable, EuiSelect, @@ -47,34 +47,62 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - +import { OverlayStart, HttpStart } from 'src/core/public'; +import { IndexPatternsContract, IIndexPattern } from '../../../../../data/public'; import { importFile, importLegacyFile, resolveImportErrors, logLegacyImport, getDefaultTitle, -} from '../../../../lib'; -import { processImportResponse } from '../../../../lib/process_import_response'; + processImportResponse, + ProcessedImportResponse, +} from '../../../lib'; import { resolveSavedObjects, resolveSavedSearches, resolveIndexPatternConflicts, saveObjects, -} from '../../../../lib/resolve_saved_objects'; -import { POSSIBLE_TYPES } from '../../objects_table'; - -export class Flyout extends Component { - static propTypes = { - close: PropTypes.func.isRequired, - done: PropTypes.func.isRequired, - services: PropTypes.array.isRequired, - newIndexPatternUrl: PropTypes.string.isRequired, - indexPatterns: PropTypes.object.isRequired, - confirmModalPromise: PropTypes.func.isRequired, - }; +} from '../../../lib/resolve_saved_objects'; +import { ISavedObjectsManagementServiceRegistry } from '../../../services'; + +export interface FlyoutProps { + serviceRegistry: ISavedObjectsManagementServiceRegistry; + allowedTypes: string[]; + close: () => void; + done: () => void; + newIndexPatternUrl: string; + indexPatterns: IndexPatternsContract; + overlays: OverlayStart; + http: HttpStart; +} + +export interface FlyoutState { + conflictedIndexPatterns?: any[]; + conflictedSavedObjectsLinkedToSavedSearches?: any[]; + conflictedSearchDocs?: any[]; + unmatchedReferences?: ProcessedImportResponse['unmatchedReferences']; + failedImports?: ProcessedImportResponse['failedImports']; + conflictingRecord?: ConflictingRecord; + error?: string; + file?: File; + importCount: number; + indexPatterns?: IIndexPattern[]; + isOverwriteAllChecked: boolean; + loadingMessage?: string; + isLegacyFile: boolean; + status: string; +} + +interface ConflictingRecord { + id: string; + type: string; + title: string; + done: (success: boolean) => void; +} - constructor(props) { +export class Flyout extends Component { + constructor(props: FlyoutProps) { super(props); this.state = { @@ -100,7 +128,7 @@ export class Flyout extends Component { fetchIndexPatterns = async () => { const indexPatterns = await this.props.indexPatterns.getFields(['id', 'title']); - this.setState({ indexPatterns }); + this.setState({ indexPatterns } as any); }; changeOverwriteAll = () => { @@ -109,11 +137,12 @@ export class Flyout extends Component { })); }; - setImportFile = ([file]) => { - if (!file) { + setImportFile = (files: FileList | null) => { + if (!files || !files[0]) { this.setState({ file: undefined, isLegacyFile: false }); return; } + const file = files[0]; this.setState({ file, isLegacyFile: /\.json$/i.test(file.name) || file.type === 'application/json', @@ -126,30 +155,29 @@ export class Flyout extends Component { * Does the initial import of a file, resolveImportErrors then handles errors and retries */ import = async () => { + const { http } = this.props; const { file, isOverwriteAllChecked } = this.state; this.setState({ status: 'loading', error: undefined }); // Import the file - let response; try { - response = await importFile(file, isOverwriteAllChecked); + const response = await importFile(http, file!, isOverwriteAllChecked); + this.setState(processImportResponse(response), () => { + // Resolve import errors right away if there's no index patterns to match + // This will ask about overwriting each object, etc + if (this.state.unmatchedReferences?.length === 0) { + this.resolveImportErrors(); + } + }); } catch (e) { this.setState({ status: 'error', - error: i18n.translate('kbn.management.objects.objectsTable.flyout.importFileErrorMessage', { + error: i18n.translate('savedObjectsManagement.objectsTable.flyout.importFileErrorMessage', { defaultMessage: 'The file could not be processed.', }), }); return; } - - this.setState(processImportResponse(response), () => { - // Resolve import errors right away if there's no index patterns to match - // This will ask about overwriting each object, etc - if (this.state.unmatchedReferences.length === 0) { - this.resolveImportErrors(); - } - }); }; /** @@ -160,10 +188,10 @@ export class Flyout extends Component { * @param {array} objects List of objects to request the user if they wish to overwrite it * @return {Promise} An object with the key being "type:id" and value the resolution chosen by the user */ - getConflictResolutions = async objects => { - const resolutions = {}; + getConflictResolutions = async (objects: any[]) => { + const resolutions: Record = {}; for (const { type, id, title } of objects) { - const overwrite = await new Promise(resolve => { + const overwrite = await new Promise(resolve => { this.setState({ conflictingRecord: { id, @@ -193,6 +221,7 @@ export class Flyout extends Component { try { const updatedState = await resolveImportErrors({ + http: this.props.http, state: this.state, getConflictResolutions: this.getConflictResolutions, }); @@ -201,7 +230,7 @@ export class Flyout extends Component { this.setState({ status: 'error', error: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.resolveImportErrorsFileErrorMessage', + 'savedObjectsManagement.objectsTable.flyout.resolveImportErrorsFileErrorMessage', { defaultMessage: 'The file could not be processed.' } ), }); @@ -209,22 +238,22 @@ export class Flyout extends Component { }; legacyImport = async () => { - const { services, indexPatterns, confirmModalPromise } = this.props; + const { serviceRegistry, indexPatterns, overlays, http, allowedTypes } = this.props; const { file, isOverwriteAllChecked } = this.state; this.setState({ status: 'loading', error: undefined }); // Log warning on server, don't wait for response - logLegacyImport(); + logLegacyImport(http); let contents; try { - contents = await importLegacyFile(file); + contents = await importLegacyFile(file!); } catch (e) { this.setState({ status: 'error', error: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.importLegacyFileErrorMessage', + 'savedObjectsManagement.objectsTable.flyout.importLegacyFileErrorMessage', { defaultMessage: 'The file could not be processed.' } ), }); @@ -235,7 +264,7 @@ export class Flyout extends Component { this.setState({ status: 'error', error: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage', + 'savedObjectsManagement.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage', { defaultMessage: 'Saved objects file format is invalid and cannot be imported.' } ), }); @@ -243,7 +272,7 @@ export class Flyout extends Component { } contents = contents - .filter(content => POSSIBLE_TYPES.includes(content._type)) + .filter(content => allowedTypes.includes(content._type)) .map(doc => ({ ...doc, // The server assumes that documents with no migrationVersion are up to date. @@ -263,18 +292,18 @@ export class Flyout extends Component { } = await resolveSavedObjects( contents, isOverwriteAllChecked, - services, + serviceRegistry.all().map(e => e.service), indexPatterns, - confirmModalPromise + overlays.openConfirm ); - const byId = {}; + const byId: Record = {}; conflictedIndexPatterns .map(({ doc, obj }) => { return { doc, obj: obj._serialize() }; }) .forEach(({ doc, obj }) => - obj.references.forEach(ref => { + obj.references.forEach((ref: Record) => { byId[ref.id] = byId[ref.id] != null ? byId[ref.id].concat({ doc, obj }) : [{ doc, obj }]; }) ); @@ -291,7 +320,7 @@ export class Flyout extends Component { }); return accum; }, - [] + [] as any[] ); this.setState({ @@ -305,12 +334,12 @@ export class Flyout extends Component { }); }; - get hasUnmatchedReferences() { + public get hasUnmatchedReferences() { return this.state.unmatchedReferences && this.state.unmatchedReferences.length > 0; } - get resolutions() { - return this.state.unmatchedReferences.reduce( + public get resolutions() { + return this.state.unmatchedReferences!.reduce( (accum, { existingIndexPatternId, newIndexPatternId }) => { if (newIndexPatternId) { accum.push({ @@ -320,7 +349,7 @@ export class Flyout extends Component { } return accum; }, - [] + [] as Array<{ oldId: string; newId: string }> ); } @@ -333,7 +362,7 @@ export class Flyout extends Component { failedImports, } = this.state; - const { services, indexPatterns } = this.props; + const { serviceRegistry, indexPatterns } = this.props; this.setState({ error: undefined, @@ -350,48 +379,48 @@ export class Flyout extends Component { // Do not Promise.all these calls as the order matters this.setState({ loadingMessage: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage', + 'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage', { defaultMessage: 'Resolving conflicts…' } ), }); if (resolutions.length) { importCount += await resolveIndexPatternConflicts( resolutions, - conflictedIndexPatterns, + conflictedIndexPatterns!, isOverwriteAllChecked, - this.props.indexPatterns + indexPatterns ); } this.setState({ loadingMessage: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage', + 'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage', { defaultMessage: 'Saving conflicts…' } ), }); importCount += await saveObjects( - conflictedSavedObjectsLinkedToSavedSearches, + conflictedSavedObjectsLinkedToSavedSearches!, isOverwriteAllChecked ); this.setState({ loadingMessage: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage', + 'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage', { defaultMessage: 'Ensure saved searches are linked properly…' } ), }); importCount += await resolveSavedSearches( - conflictedSearchDocs, - services, + conflictedSearchDocs!, + serviceRegistry.all().map(e => e.service), indexPatterns, isOverwriteAllChecked ); this.setState({ loadingMessage: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage', + 'savedObjectsManagement.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage', { defaultMessage: 'Retrying failed objects…' } ), }); importCount += await saveObjects( - failedImports.map(({ obj }) => obj), + failedImports!.map(({ obj }) => obj) as any[], isOverwriteAllChecked ); } catch (e) { @@ -407,26 +436,26 @@ export class Flyout extends Component { this.setState({ status: 'success', importCount }); }; - onIndexChanged = (id, e) => { + onIndexChanged = (id: string, e: any) => { const value = e.target.value; this.setState(state => { - const conflictIndex = state.unmatchedReferences.findIndex( + const conflictIndex = state.unmatchedReferences?.findIndex( conflict => conflict.existingIndexPatternId === id ); - if (conflictIndex === -1) { + if (conflictIndex === undefined || conflictIndex === -1) { return state; } return { unmatchedReferences: [ - ...state.unmatchedReferences.slice(0, conflictIndex), + ...state.unmatchedReferences!.slice(0, conflictIndex), { - ...state.unmatchedReferences[conflictIndex], + ...state.unmatchedReferences![conflictIndex], newIndexPatternId: value, }, - ...state.unmatchedReferences.slice(conflictIndex + 1), + ...state.unmatchedReferences!.slice(conflictIndex + 1), ], - }; + } as any; }); }; @@ -441,11 +470,11 @@ export class Flyout extends Component { { field: 'existingIndexPatternId', name: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdName', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnIdName', { defaultMessage: 'ID' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdDescription', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnIdDescription', { defaultMessage: 'ID of the index pattern' } ), sortable: true, @@ -453,28 +482,28 @@ export class Flyout extends Component { { field: 'list', name: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountName', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnCountName', { defaultMessage: 'Count' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountDescription', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnCountDescription', { defaultMessage: 'How many affected objects' } ), - render: list => { + render: (list: any[]) => { return {list.length}; }, }, { field: 'list', name: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName', { defaultMessage: 'Sample of affected objects' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription', { defaultMessage: 'Sample of affected objects' } ), - render: list => { + render: (list: any[]) => { return (
    {take(list, 3).map((obj, key) => ( @@ -487,15 +516,18 @@ export class Flyout extends Component { { field: 'existingIndexPatternId', name: i18n.translate( - 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnNewIndexPatternName', + 'savedObjectsManagement.objectsTable.flyout.renderConflicts.columnNewIndexPatternName', { defaultMessage: 'New index pattern' } ), - render: id => { - const options = this.state.indexPatterns.map(indexPattern => ({ - text: indexPattern.title, - value: indexPattern.id, - ['data-test-subj']: `indexPatternOption-${indexPattern.title}`, - })); + render: (id: string) => { + const options = this.state.indexPatterns!.map( + indexPattern => + ({ + text: indexPattern.title, + value: indexPattern.id, + 'data-test-subj': `indexPatternOption-${indexPattern.title}`, + } as { text: string; value: string; 'data-test-subj'?: string }) + ); options.unshift({ text: '-- Skip Import --', @@ -518,7 +550,11 @@ export class Flyout extends Component { }; return ( - + ); } @@ -534,7 +570,7 @@ export class Flyout extends Component { } @@ -581,7 +617,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsFailedWarning" title={ } @@ -590,7 +626,7 @@ export class Flyout extends Component { >

    { return i18n.translate( - 'kbn.management.objects.objectsTable.flyout.importFailedMissingReference', + 'savedObjectsManagement.objectsTable.flyout.importFailedMissingReference', { defaultMessage: '{type} [id={id}] could not locate {refType} [id={refId}]', values: { @@ -618,7 +654,7 @@ export class Flyout extends Component { }); } else if (error.type === 'unsupported_type') { return i18n.translate( - 'kbn.management.objects.objectsTable.flyout.importFailedUnsupportedType', + 'savedObjectsManagement.objectsTable.flyout.importFailedUnsupportedType', { defaultMessage: '{type} [id={id}] unsupported type', values: { @@ -628,7 +664,7 @@ export class Flyout extends Component { } ); } - return getField(error, 'body.message', error.message || ''); + return getField(error, 'body.message', (error as any).message ?? ''); }) .join(' ')}

    @@ -643,7 +679,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsSuccessNoneImported" title={ } @@ -657,7 +693,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsSuccess" title={ } @@ -666,7 +702,7 @@ export class Flyout extends Component { >

    @@ -684,7 +720,7 @@ export class Flyout extends Component { } @@ -692,7 +728,7 @@ export class Flyout extends Component { } @@ -704,7 +740,7 @@ export class Flyout extends Component { name="overwriteAll" label={ } @@ -727,7 +763,7 @@ export class Flyout extends Component { confirmButton = ( @@ -742,7 +778,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsConfirmBtn" > @@ -757,7 +793,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsImportBtn" > @@ -769,7 +805,7 @@ export class Flyout extends Component { @@ -791,7 +827,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsLegacyWarning" title={ } @@ -800,7 +836,7 @@ export class Flyout extends Component { >

    @@ -815,7 +851,7 @@ export class Flyout extends Component { data-test-subj="importSavedObjectsConflictsWarning" title={ } @@ -824,7 +860,7 @@ export class Flyout extends Component { >

    @@ -867,11 +903,11 @@ export class Flyout extends Component { } overwriteConfirmed() { - this.state.conflictingRecord.done(true); + this.state.conflictingRecord!.done(true); } overwriteSkipped() { - this.state.conflictingRecord.done(false); + this.state.conflictingRecord!.done(false); } render() { @@ -883,18 +919,18 @@ export class Flyout extends Component {

    diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/header.test.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/header.test.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/header.test.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/header.test.tsx index 1f501b5751224..891190d0bb24b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__jest__/header.test.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/header.test.tsx @@ -19,8 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; - -import { Header } from '../header'; +import { Header } from './header'; describe('Header', () => { it('should render normally', () => { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/header.tsx similarity index 83% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/header.tsx index 0bec8a0cf2daf..7a9584f08d632 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/header.tsx @@ -18,8 +18,6 @@ */ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; - import { EuiSpacer, EuiTitle, @@ -31,14 +29,24 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => ( +export const Header = ({ + onExportAll, + onImport, + onRefresh, + filteredCount, +}: { + onExportAll: () => void; + onImport: () => void; + onRefresh: () => void; + filteredCount: number; +}) => (

    @@ -55,7 +63,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => ( onClick={onExportAll} > ( onClick={onImport} > @@ -79,7 +87,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => ( @@ -92,7 +100,7 @@ export const Header = ({ onExportAll, onImport, onRefresh, filteredCount }) => (

    ); - -Header.propTypes = { - onExportAll: PropTypes.func.isRequired, - onImport: PropTypes.func.isRequired, - onRefresh: PropTypes.func.isRequired, - filteredCount: PropTypes.number.isRequired, -}; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts b/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts new file mode 100644 index 0000000000000..9c8736a9011eb --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { Header } from './header'; +export { Table } from './table'; +export { Flyout } from './flyout'; +export { Relationships } from './relationships'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index 479726e8785d8..347f2d977015c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx @@ -19,27 +19,23 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { httpServiceMock } from '../../../../../../core/public/mocks'; +import { Relationships, RelationshipsProps } from './relationships'; -jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); - -jest.mock('ui/chrome', () => ({ - addBasePath: () => '', -})); - -jest.mock('../../../../../lib/fetch_export_by_type_and_search', () => ({ +jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({ fetchExportByTypeAndSearch: jest.fn(), })); -jest.mock('../../../../../lib/fetch_export_objects', () => ({ +jest.mock('../../../lib/fetch_export_objects', () => ({ fetchExportObjects: jest.fn(), })); -import { Relationships } from '../relationships'; - describe('Relationships', () => { it('should render index patterns normally', async () => { - const props = { + const props: RelationshipsProps = { goInspectObject: () => {}, + canGoInApp: () => true, + basePath: httpServiceMock.createSetupContract().basePath, getRelationships: jest.fn().mockImplementation(() => [ { type: 'search', @@ -73,6 +69,8 @@ describe('Relationships', () => { savedObject: { id: '1', type: 'index-pattern', + attributes: {}, + references: [], meta: { title: 'MyIndexPattern*', icon: 'indexPatternApp', @@ -101,8 +99,10 @@ describe('Relationships', () => { }); it('should render searches normally', async () => { - const props = { + const props: RelationshipsProps = { goInspectObject: () => {}, + canGoInApp: () => true, + basePath: httpServiceMock.createSetupContract().basePath, getRelationships: jest.fn().mockImplementation(() => [ { type: 'index-pattern', @@ -136,6 +136,8 @@ describe('Relationships', () => { savedObject: { id: '1', type: 'search', + attributes: {}, + references: [], meta: { title: 'MySearch', icon: 'search', @@ -164,8 +166,10 @@ describe('Relationships', () => { }); it('should render visualizations normally', async () => { - const props = { + const props: RelationshipsProps = { goInspectObject: () => {}, + canGoInApp: () => true, + basePath: httpServiceMock.createSetupContract().basePath, getRelationships: jest.fn().mockImplementation(() => [ { type: 'dashboard', @@ -199,6 +203,8 @@ describe('Relationships', () => { savedObject: { id: '1', type: 'visualization', + attributes: {}, + references: [], meta: { title: 'MyViz', icon: 'visualizeApp', @@ -227,8 +233,10 @@ describe('Relationships', () => { }); it('should render dashboards normally', async () => { - const props = { + const props: RelationshipsProps = { goInspectObject: () => {}, + canGoInApp: () => true, + basePath: httpServiceMock.createSetupContract().basePath, getRelationships: jest.fn().mockImplementation(() => [ { type: 'visualization', @@ -262,6 +270,8 @@ describe('Relationships', () => { savedObject: { id: '1', type: 'dashboard', + attributes: {}, + references: [], meta: { title: 'MyDashboard', icon: 'dashboardApp', @@ -290,14 +300,18 @@ describe('Relationships', () => { }); it('should render errors', async () => { - const props = { + const props: RelationshipsProps = { goInspectObject: () => {}, + canGoInApp: () => true, + basePath: httpServiceMock.createSetupContract().basePath, getRelationships: jest.fn().mockImplementation(() => { throw new Error('foo'); }), savedObject: { id: '1', type: 'dashboard', + attributes: {}, + references: [], meta: { title: 'MyDashboard', icon: 'dashboardApp', diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx similarity index 75% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx index ce3415ad2f0e7..ddb262138d565 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx @@ -18,8 +18,6 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - import { EuiTitle, EuiFlyout, @@ -34,25 +32,34 @@ import { EuiText, EuiSpacer, } from '@elastic/eui'; -import chrome from 'ui/chrome'; +import { FilterConfig } from '@elastic/eui/src/components/search_bar/filters/filters'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getDefaultTitle, getSavedObjectLabel } from '../../../../lib'; +import { IBasePath } from 'src/core/public'; +import { getDefaultTitle, getSavedObjectLabel } from '../../../lib'; +import { SavedObjectWithMetadata, SavedObjectRelation } from '../../../types'; + +export interface RelationshipsProps { + basePath: IBasePath; + getRelationships: (type: string, id: string) => Promise; + savedObject: SavedObjectWithMetadata; + close: () => void; + goInspectObject: (obj: SavedObjectWithMetadata) => void; + canGoInApp: (obj: SavedObjectWithMetadata) => boolean; +} -export class Relationships extends Component { - static propTypes = { - getRelationships: PropTypes.func.isRequired, - savedObject: PropTypes.object.isRequired, - close: PropTypes.func.isRequired, - goInspectObject: PropTypes.func.isRequired, - canGoInApp: PropTypes.func.isRequired, - }; +export interface RelationshipsState { + relationships: SavedObjectRelation[]; + isLoading: boolean; + error?: string; +} - constructor(props) { +export class Relationships extends Component { + constructor(props: RelationshipsProps) { super(props); this.state = { - relationships: undefined, + relationships: [], isLoading: false, error: undefined, }; @@ -62,7 +69,7 @@ export class Relationships extends Component { this.getRelationshipData(); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: RelationshipsProps) { if (nextProps.savedObject.id !== this.props.savedObject.id) { this.getRelationshipData(); } @@ -92,7 +99,7 @@ export class Relationships extends Component { } @@ -104,7 +111,7 @@ export class Relationships extends Component { } renderRelationships() { - const { goInspectObject, savedObject } = this.props; + const { goInspectObject, savedObject, basePath } = this.props; const { relationships, isLoading, error } = this.state; if (error) { @@ -118,17 +125,17 @@ export class Relationships extends Component { const columns = [ { field: 'type', - name: i18n.translate('kbn.management.objects.objectsTable.relationships.columnTypeName', { + name: i18n.translate('savedObjectsManagement.objectsTable.relationships.columnTypeName', { defaultMessage: 'Type', }), width: '50px', align: 'center', description: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.columnTypeDescription', + 'savedObjectsManagement.objectsTable.relationships.columnTypeDescription', { defaultMessage: 'Type of the saved object' } ), sortable: false, - render: (type, object) => { + render: (type: string, object: SavedObjectWithMetadata) => { return ( { + render: (relationship: string) => { if (relationship === 'parent') { return ( @@ -166,7 +173,7 @@ export class Relationships extends Component { return ( @@ -176,17 +183,17 @@ export class Relationships extends Component { }, { field: 'meta.title', - name: i18n.translate('kbn.management.objects.objectsTable.relationships.columnTitleName', { + name: i18n.translate('savedObjectsManagement.objectsTable.relationships.columnTitleName', { defaultMessage: 'Title', }), description: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.columnTitleDescription', + 'savedObjectsManagement.objectsTable.relationships.columnTitleDescription', { defaultMessage: 'Title of the saved object' } ), dataType: 'string', sortable: false, - render: (title, object) => { - const { path } = object.meta.inAppUrl || {}; + render: (title: string, object: SavedObjectWithMetadata) => { + const { path = '' } = object.meta.inAppUrl || {}; const canGoInApp = this.props.canGoInApp(object); if (!canGoInApp) { return ( @@ -196,7 +203,7 @@ export class Relationships extends Component { ); } return ( - + {title || getDefaultTitle(object)} ); @@ -204,24 +211,24 @@ export class Relationships extends Component { }, { name: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.columnActionsName', + 'savedObjectsManagement.objectsTable.relationships.columnActionsName', { defaultMessage: 'Actions' } ), actions: [ { name: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.columnActions.inspectActionName', + 'savedObjectsManagement.objectsTable.relationships.columnActions.inspectActionName', { defaultMessage: 'Inspect' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.columnActions.inspectActionDescription', + 'savedObjectsManagement.objectsTable.relationships.columnActions.inspectActionDescription', { defaultMessage: 'Inspect this saved object' } ), type: 'icon', icon: 'inspect', 'data-test-subj': 'relationshipsTableAction-inspect', - onClick: object => goInspectObject(object), - available: object => !!object.meta.editUrl, + onClick: (object: SavedObjectWithMetadata) => goInspectObject(object), + available: (object: SavedObjectWithMetadata) => !!object.meta.editUrl, }, ], }, @@ -244,7 +251,7 @@ export class Relationships extends Component { type: 'field_value_selection', field: 'relationship', name: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.search.filters.relationship.name', + 'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.name', { defaultMessage: 'Direct relationship' } ), multiSelect: 'or', @@ -253,7 +260,7 @@ export class Relationships extends Component { value: 'parent', name: 'parent', view: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.search.filters.relationship.parentAsValue.view', + 'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.parentAsValue.view', { defaultMessage: 'Parent' } ), }, @@ -261,7 +268,7 @@ export class Relationships extends Component { value: 'child', name: 'child', view: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.search.filters.relationship.childAsValue.view', + 'savedObjectsManagement.objectsTable.relationships.search.filters.relationship.childAsValue.view', { defaultMessage: 'Child' } ), }, @@ -271,13 +278,13 @@ export class Relationships extends Component { type: 'field_value_selection', field: 'type', name: i18n.translate( - 'kbn.management.objects.objectsTable.relationships.search.filters.type.name', + 'savedObjectsManagement.objectsTable.relationships.search.filters.type.name', { defaultMessage: 'Type' } ), multiSelect: 'or', options: [...filterTypesMap.values()], }, - ], + ] as FilterConfig[], }; return ( @@ -285,7 +292,7 @@ export class Relationships extends Component {

    {i18n.translate( - 'kbn.management.objects.objectsTable.relationships.relationshipsTitle', + 'savedObjectsManagement.objectsTable.relationships.relationshipsTitle', { defaultMessage: 'Here are the saved objects related to {title}. ' + @@ -301,7 +308,7 @@ export class Relationships extends Component { ({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx similarity index 87% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx index 9b3e2314c9f84..356f227773610 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx @@ -19,27 +19,22 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { keyCodes } from '@elastic/eui/lib/services'; -import { npSetup as mockNpSetup } from '../../../../../../../../../../../ui/public/new_platform/__mocks__'; +import { keyCodes } from '@elastic/eui'; +import { httpServiceMock } from '../../../../../../core/public/mocks'; +import { actionServiceMock } from '../../../services/action_service.mock'; +import { Table, TableProps } from './table'; -jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); - -jest.mock('ui/chrome', () => ({ - addBasePath: () => '', -})); - -jest.mock('ui/new_platform', () => ({ - npSetup: mockNpSetup, -})); - -import { Table } from '../table'; - -const defaultProps = { +const defaultProps: TableProps = { + basePath: httpServiceMock.createSetupContract().basePath, + actionRegistry: actionServiceMock.createStart(), selectedSavedObjects: [ { id: '1', type: 'index-pattern', + attributes: {}, + references: [], meta: { title: `MyIndexPattern*`, icon: 'indexPatternApp', @@ -58,13 +53,15 @@ const defaultProps = { onDelete: () => {}, onExport: () => {}, goInspectObject: () => {}, - canGoInApp: () => {}, + canGoInApp: () => true, pageIndex: 1, pageSize: 2, items: [ { id: '1', type: 'index-pattern', + attributes: {}, + references: [], meta: { title: `MyIndexPattern*`, icon: 'indexPatternApp', @@ -120,7 +117,7 @@ describe('Table', () => { { type: 'visualization' }, { type: 'search' }, { type: 'index-pattern' }, - ]; + ] as any; const customizedProps = { ...defaultProps, selectedSavedObjects, canDelete: false }; const component = shallowWithI18nProvider(

); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx similarity index 71% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx index 132fa1e691c1c..5b574e4b3d331 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx @@ -17,12 +17,10 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { npSetup } from 'ui/new_platform'; +import { IBasePath } from 'src/core/public'; import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; - import { + // @ts-ignore EuiSearchBar, EuiBasicTable, EuiButton, @@ -35,54 +33,64 @@ import { EuiSwitch, EuiFormRow, EuiText, + EuiTableFieldDataColumnType, + EuiTableActionsColumnType, } from '@elastic/eui'; -import { getDefaultTitle, getSavedObjectLabel } from '../../../../lib'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getDefaultTitle, getSavedObjectLabel } from '../../../lib'; +import { SavedObjectWithMetadata } from '../../../types'; +import { + SavedObjectsManagementActionServiceStart, + SavedObjectsManagementAction, +} from '../../../services'; -export class Table extends PureComponent { - static propTypes = { - selectedSavedObjects: PropTypes.array.isRequired, - selectionConfig: PropTypes.shape({ - selectable: PropTypes.func, - selectableMessage: PropTypes.func, - onSelectionChange: PropTypes.func.isRequired, - }).isRequired, - filterOptions: PropTypes.array.isRequired, - canDelete: PropTypes.bool.isRequired, - onDelete: PropTypes.func.isRequired, - onExport: PropTypes.func.isRequired, - goInspectObject: PropTypes.func.isRequired, - - pageIndex: PropTypes.number.isRequired, - pageSize: PropTypes.number.isRequired, - items: PropTypes.array.isRequired, - itemId: PropTypes.oneOfType([ - PropTypes.string, // the name of the item id property - PropTypes.func, // (item) => string - ]), - totalItemCount: PropTypes.number.isRequired, - onQueryChange: PropTypes.func.isRequired, - onTableChange: PropTypes.func.isRequired, - isSearching: PropTypes.bool.isRequired, - - onShowRelationships: PropTypes.func.isRequired, +export interface TableProps { + basePath: IBasePath; + actionRegistry: SavedObjectsManagementActionServiceStart; + selectedSavedObjects: SavedObjectWithMetadata[]; + selectionConfig: { + onSelectionChange: (selection: SavedObjectWithMetadata[]) => void; }; + filterOptions: any[]; + canDelete: boolean; + onDelete: () => void; + onExport: (includeReferencesDeep: boolean) => void; + goInspectObject: (obj: SavedObjectWithMetadata) => void; + pageIndex: number; + pageSize: number; + items: SavedObjectWithMetadata[]; + itemId: string | (() => string); + totalItemCount: number; + onQueryChange: (query: any) => void; + onTableChange: (table: any) => void; + isSearching: boolean; + onShowRelationships: (object: SavedObjectWithMetadata) => void; + canGoInApp: (obj: SavedObjectWithMetadata) => boolean; +} + +interface TableState { + isSearchTextValid: boolean; + parseErrorMessage: any; + isExportPopoverOpen: boolean; + isIncludeReferencesDeepChecked: boolean; + activeAction?: SavedObjectsManagementAction; +} - state = { +export class Table extends PureComponent { + state: TableState = { isSearchTextValid: true, parseErrorMessage: null, isExportPopoverOpen: false, isIncludeReferencesDeepChecked: true, - activeAction: null, + activeAction: undefined, }; - constructor(props) { + constructor(props: TableProps) { super(props); - this.extraActions = npSetup.plugins.savedObjectsManagement.actionRegistry.getAll(); } - onChange = ({ query, error }) => { + onChange = ({ query, error }: any) => { if (error) { this.setState({ isSearchTextValid: false, @@ -136,12 +144,14 @@ export class Table extends PureComponent { onTableChange, goInspectObject, onShowRelationships, + basePath, + actionRegistry, } = this.props; const pagination = { - pageIndex: pageIndex, - pageSize: pageSize, - totalItemCount: totalItemCount, + pageIndex, + pageSize, + totalItemCount, pageSizeOptions: [5, 10, 20, 50], }; @@ -149,7 +159,7 @@ export class Table extends PureComponent { { type: 'field_value_selection', field: 'type', - name: i18n.translate('kbn.management.objects.objectsTable.table.typeFilterName', { + name: i18n.translate('savedObjectsManagement.objectsTable.table.typeFilterName', { defaultMessage: 'Type', }), multiSelect: 'or', @@ -168,18 +178,18 @@ export class Table extends PureComponent { const columns = [ { field: 'type', - name: i18n.translate('kbn.management.objects.objectsTable.table.columnTypeName', { + name: i18n.translate('savedObjectsManagement.objectsTable.table.columnTypeName', { defaultMessage: 'Type', }), width: '50px', align: 'center', description: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnTypeDescription', + 'savedObjectsManagement.objectsTable.table.columnTypeDescription', { defaultMessage: 'Type of the saved object' } ), sortable: false, 'data-test-subj': 'savedObjectsTableRowType', - render: (type, object) => { + render: (type: string, object: SavedObjectWithMetadata) => { return ( ); }, - }, + } as EuiTableFieldDataColumnType>, { field: 'meta.title', - name: i18n.translate('kbn.management.objects.objectsTable.table.columnTitleName', { + name: i18n.translate('savedObjectsManagement.objectsTable.table.columnTitleName', { defaultMessage: 'Title', }), description: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnTitleDescription', + 'savedObjectsManagement.objectsTable.table.columnTitleDescription', { defaultMessage: 'Title of the saved object' } ), dataType: 'string', sortable: false, 'data-test-subj': 'savedObjectsTableRowTitle', - render: (title, object) => { - const { path } = object.meta.inAppUrl || {}; + render: (title: string, object: SavedObjectWithMetadata) => { + const { path = '' } = object.meta.inAppUrl || {}; const canGoInApp = this.props.canGoInApp(object); if (!canGoInApp) { return {title || getDefaultTitle(object)}; } return ( - {title || getDefaultTitle(object)} + {title || getDefaultTitle(object)} ); }, - }, + } as EuiTableFieldDataColumnType>, { - name: i18n.translate('kbn.management.objects.objectsTable.table.columnActionsName', { + name: i18n.translate('savedObjectsManagement.objectsTable.table.columnActionsName', { defaultMessage: 'Actions', }), actions: [ { name: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnActions.inspectActionName', + 'savedObjectsManagement.objectsTable.table.columnActions.inspectActionName', { defaultMessage: 'Inspect' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnActions.inspectActionDescription', + 'savedObjectsManagement.objectsTable.table.columnActions.inspectActionDescription', { defaultMessage: 'Inspect this saved object' } ), type: 'icon', @@ -237,11 +247,11 @@ export class Table extends PureComponent { }, { name: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionName', + 'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionName', { defaultMessage: 'Relationships' } ), description: i18n.translate( - 'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionDescription', + 'savedObjectsManagement.objectsTable.table.columnActions.viewRelationshipsActionDescription', { defaultMessage: 'View the relationships this saved object has to other saved objects', @@ -252,33 +262,35 @@ export class Table extends PureComponent { onClick: object => onShowRelationships(object), 'data-test-subj': 'savedObjectsTableAction-relationships', }, - ...this.extraActions.map(action => { + ...actionRegistry.getAll().map(action => { return { ...action.euiAction, 'data-test-subj': `savedObjectsTableAction-${action.id}`, - onClick: object => { + onClick: (object: SavedObjectWithMetadata) => { this.setState({ activeAction: action, }); action.registerOnFinishCallback(() => { this.setState({ - activeAction: null, + activeAction: undefined, }); }); - action.euiAction.onClick(object); + if (action.euiAction.onClick) { + action.euiAction.onClick(object as any); + } }, }; }), ], - }, + } as EuiTableActionsColumnType, ]; let queryParseError; if (!this.state.isSearchTextValid) { const parseErrorMsg = i18n.translate( - 'kbn.management.objects.objectsTable.searchBar.unableToParseQueryErrorMessage', + 'savedObjectsManagement.objectsTable.searchBar.unableToParseQueryErrorMessage', { defaultMessage: 'Unable to parse query' } ); queryParseError = ( @@ -294,20 +306,20 @@ export class Table extends PureComponent { isDisabled={selectedSavedObjects.length === 0} > ); - const activeActionContents = this.state.activeAction ? this.state.activeAction.render() : null; + const activeActionContents = this.state.activeAction?.render() ?? null; return ( {activeActionContents} , @@ -339,7 +351,7 @@ export class Table extends PureComponent { } @@ -348,7 +360,7 @@ export class Table extends PureComponent { name="includeReferencesDeep" label={ } @@ -359,7 +371,7 @@ export class Table extends PureComponent { @@ -374,7 +386,7 @@ export class Table extends PureComponent { loading={isSearching} itemId={itemId} items={items} - columns={columns} + columns={columns as any} pagination={pagination} selection={selection} onChange={onTableChange} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/index.js b/src/plugins/saved_objects_management/public/management_section/objects_table/index.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/index.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/index.ts index ac1e7bac06c87..8777b15389690 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/index.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { Header } from './header'; +export { SavedObjectsTable } from './saved_objects_table'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.mocks.ts b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.mocks.ts new file mode 100644 index 0000000000000..6b4659a6b5a13 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.mocks.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const saveAsMock = jest.fn(); +jest.doMock('@elastic/filesaver', () => ({ + saveAs: saveAsMock, +})); + +jest.doMock('lodash', () => ({ + ...jest.requireActual('lodash'), + debounce: (func: Function) => { + function debounced(this: any, ...args: any[]) { + return func.apply(this, args); + } + return debounced; + }, +})); + +export const findObjectsMock = jest.fn(); +jest.doMock('../../lib/find_objects', () => ({ + findObjects: findObjectsMock, +})); + +export const fetchExportObjectsMock = jest.fn(); +jest.doMock('../../lib/fetch_export_objects', () => ({ + fetchExportObjects: fetchExportObjectsMock, +})); + +export const fetchExportByTypeAndSearchMock = jest.fn(); +jest.doMock('../../lib/fetch_export_by_type_and_search', () => ({ + fetchExportByTypeAndSearch: fetchExportByTypeAndSearchMock, +})); + +export const extractExportDetailsMock = jest.fn(); +jest.doMock('../../lib/extract_export_details', () => ({ + extractExportDetails: extractExportDetailsMock, +})); + +jest.doMock('./components/header', () => ({ + Header: () => 'Header', +})); + +export const getSavedObjectCountsMock = jest.fn(); +jest.doMock('../../lib/get_saved_object_counts', () => ({ + getSavedObjectCounts: getSavedObjectCountsMock, +})); + +export const getRelationshipsMock = jest.fn(); +jest.doMock('../../lib/get_relationships', () => ({ + getRelationships: getRelationshipsMock, +})); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx similarity index 58% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 7b9c17640a0f3..342fdc4784b09 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -17,69 +17,39 @@ * under the License. */ +import { + extractExportDetailsMock, + fetchExportByTypeAndSearchMock, + fetchExportObjectsMock, + findObjectsMock, + getRelationshipsMock, + getSavedObjectCountsMock, + saveAsMock, +} from './saved_objects_table.test.mocks'; + import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../../plugins/index_pattern_management/public/mocks'; import { Query } from '@elastic/eui'; - -import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; -import { Flyout } from '../components/flyout/'; -import { Relationships } from '../components/relationships/'; -import { findObjects } from '../../../lib'; -import { extractExportDetails } from '../../../lib/extract_export_details'; - -jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); - -jest.mock('../../../../../../../../../../plugins/index_pattern_management/public', () => ({ - setup: mockManagementPlugin.createSetupContract(), - start: mockManagementPlugin.createStartContract(), -})); - -jest.mock('../../../lib/find_objects', () => ({ - findObjects: jest.fn(), -})); - -jest.mock('../components/header', () => ({ - Header: () => 'Header', -})); - -jest.mock('ui/chrome', () => ({ - addBasePath: () => '', - getInjected: () => ['index-pattern', 'visualization', 'dashboard', 'search'], -})); - -jest.mock('../../../lib/fetch_export_objects', () => ({ - fetchExportObjects: jest.fn(), -})); - -jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({ - fetchExportByTypeAndSearch: jest.fn(), -})); - -jest.mock('../../../lib/extract_export_details', () => ({ - extractExportDetails: jest.fn(), -})); - -jest.mock('../../../lib/get_saved_object_counts', () => ({ - getSavedObjectCounts: jest.fn().mockImplementation(() => { - return { - 'index-pattern': 0, - visualization: 0, - dashboard: 0, - search: 0, - }; - }), -})); - -jest.mock('@elastic/filesaver', () => ({ - saveAs: jest.fn(), -})); - -jest.mock('../../../lib/get_relationships', () => ({ - getRelationships: jest.fn(), -})); - -jest.mock('ui/notify', () => ({})); +import { ShallowWrapper } from 'enzyme'; +import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { + httpServiceMock, + overlayServiceMock, + notificationServiceMock, + savedObjectsServiceMock, + applicationServiceMock, +} from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../../../data/public/mocks'; +import { serviceRegistryMock } from '../../services/service_registry.mock'; +import { actionServiceMock } from '../../services/action_service.mock'; +import { + SavedObjectsTable, + SavedObjectsTableProps, + SavedObjectsTableState, +} from './saved_objects_table'; +import { Flyout, Relationships } from './components'; +import { SavedObjectWithMetadata } from '../../types'; + +const allowedTypes = ['index-pattern', 'visualization', 'dashboard', 'search']; const allSavedObjects = [ { @@ -112,122 +82,128 @@ const allSavedObjects = [ }, ]; -const $http = () => {}; -$http.post = jest.fn().mockImplementation(() => []); -const defaultProps = { - goInspectObject: () => {}, - confirmModalPromise: jest.fn(), - savedObjectsClient: { - find: jest.fn(), - bulkGet: jest.fn(), - }, - indexPatterns: { - clearCache: jest.fn(), - }, - $http, - basePath: '', - newIndexPatternUrl: '', - kbnIndex: '', - services: [], - uiCapabilities: { - savedObjectsManagement: { - read: true, - edit: false, - delete: false, - }, - }, - canDelete: true, -}; - -beforeEach(() => { - findObjects.mockImplementation(() => ({ - total: 4, - savedObjects: [ - { - id: '1', - type: 'index-pattern', - meta: { - title: `MyIndexPattern*`, - icon: 'indexPatternApp', - editUrl: '#/management/kibana/index_patterns/1', - inAppUrl: { - path: '/management/kibana/index_patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', +describe('SavedObjectsTable', () => { + let defaultProps: SavedObjectsTableProps; + let http: ReturnType; + let overlays: ReturnType; + let notifications: ReturnType; + let savedObjects: ReturnType; + + const shallowRender = (overrides: Partial = {}) => { + return (shallowWithI18nProvider( + + ) as unknown) as ShallowWrapper< + SavedObjectsTableProps, + SavedObjectsTableState, + SavedObjectsTable + >; + }; + + beforeEach(() => { + extractExportDetailsMock.mockReset(); + + http = httpServiceMock.createStartContract(); + overlays = overlayServiceMock.createStartContract(); + notifications = notificationServiceMock.createStartContract(); + savedObjects = savedObjectsServiceMock.createStartContract(); + + const applications = applicationServiceMock.createStartContract(); + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: false, + }, + }; + + http.post.mockResolvedValue([]); + + getSavedObjectCountsMock.mockReturnValue({ + 'index-pattern': 0, + visualization: 0, + dashboard: 0, + search: 0, + }); + + defaultProps = { + allowedTypes, + serviceRegistry: serviceRegistryMock.create(), + actionRegistry: actionServiceMock.createStart(), + savedObjectsClient: savedObjects.client, + indexPatterns: dataPluginMock.createStartContract().indexPatterns, + http, + overlays, + notifications, + applications, + perPageConfig: 15, + goInspectObject: () => {}, + canGoInApp: () => true, + }; + + findObjectsMock.mockImplementation(() => ({ + total: 4, + savedObjects: [ + { + id: '1', + type: 'index-pattern', + meta: { + title: `MyIndexPattern*`, + icon: 'indexPatternApp', + editUrl: '#/management/kibana/index_patterns/1', + inAppUrl: { + path: '/management/kibana/index_patterns/1', + uiCapabilitiesPath: 'management.kibana.index_patterns', + }, }, }, - }, - { - id: '2', - type: 'search', - meta: { - title: `MySearch`, - icon: 'search', - editUrl: '#/management/kibana/objects/savedSearches/2', - inAppUrl: { - path: '/discover/2', - uiCapabilitiesPath: 'discover.show', + { + id: '2', + type: 'search', + meta: { + title: `MySearch`, + icon: 'search', + editUrl: '#/management/kibana/objects/savedSearches/2', + inAppUrl: { + path: '/discover/2', + uiCapabilitiesPath: 'discover.show', + }, }, }, - }, - { - id: '3', - type: 'dashboard', - meta: { - title: `MyDashboard`, - icon: 'dashboardApp', - editUrl: '#/management/kibana/objects/savedDashboards/3', - inAppUrl: { - path: '/dashboard/3', - uiCapabilitiesPath: 'dashboard.show', + { + id: '3', + type: 'dashboard', + meta: { + title: `MyDashboard`, + icon: 'dashboardApp', + editUrl: '#/management/kibana/objects/savedDashboards/3', + inAppUrl: { + path: '/dashboard/3', + uiCapabilitiesPath: 'dashboard.show', + }, }, }, - }, - { - id: '4', - type: 'visualization', - meta: { - title: `MyViz`, - icon: 'visualizeApp', - editUrl: '#/management/kibana/objects/savedVisualizations/4', - inAppUrl: { - path: '/visualize/edit/4', - uiCapabilitiesPath: 'visualize.show', + { + id: '4', + type: 'visualization', + meta: { + title: `MyViz`, + icon: 'visualizeApp', + editUrl: '#/management/kibana/objects/savedVisualizations/4', + inAppUrl: { + path: '/visualize/edit/4', + uiCapabilitiesPath: 'visualize.show', + }, }, }, - }, - ], - })); -}); - -let addDangerMock; -let addSuccessMock; -let addWarningMock; - -describe('ObjectsTable', () => { - beforeEach(() => { - defaultProps.savedObjectsClient.find.mockClear(); - extractExportDetails.mockReset(); - // mock _.debounce to fire immediately with no internal timer - require('lodash').debounce = func => { - function debounced(...args) { - return func.apply(this, args); - } - return debounced; - }; - addDangerMock = jest.fn(); - addSuccessMock = jest.fn(); - addWarningMock = jest.fn(); - require('ui/notify').toastNotifications = { - addDanger: addDangerMock, - addSuccess: addSuccessMock, - addWarning: addWarningMock, - }; + ], + })); }); it('should render normally', async () => { - const component = shallowWithI18nProvider( - - ); + const component = shallowRender({ perPageConfig: 15 }); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -238,19 +214,17 @@ describe('ObjectsTable', () => { }); it('should add danger toast when find fails', async () => { - findObjects.mockImplementation(() => { + findObjectsMock.mockImplementation(() => { throw new Error('Simulated find error'); }); - const component = shallowWithI18nProvider( - - ); + const component = shallowRender({ perPageConfig: 15 }); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); - expect(addDangerMock).toHaveBeenCalled(); + expect(notifications.toasts.addDanger).toHaveBeenCalled(); }); describe('export', () => { @@ -258,7 +232,7 @@ describe('ObjectsTable', () => { const mockSelectedSavedObjects = [ { id: '1', type: 'index-pattern' }, { id: '3', type: 'dashboard' }, - ]; + ] as SavedObjectWithMetadata[]; const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({ _id: obj.id, @@ -272,11 +246,7 @@ describe('ObjectsTable', () => { })), }; - const { fetchExportObjects } = require('../../../lib/fetch_export_objects'); - - const component = shallowWithI18nProvider( - - ); + const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -288,8 +258,8 @@ describe('ObjectsTable', () => { await component.instance().onExport(true); - expect(fetchExportObjects).toHaveBeenCalledWith(mockSelectedSavedObjects, true); - expect(addSuccessMock).toHaveBeenCalledWith({ + expect(fetchExportObjectsMock).toHaveBeenCalledWith(http, mockSelectedSavedObjects, true); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({ title: 'Your file is downloading in the background', }); }); @@ -298,7 +268,7 @@ describe('ObjectsTable', () => { const mockSelectedSavedObjects = [ { id: '1', type: 'index-pattern' }, { id: '3', type: 'dashboard' }, - ]; + ] as SavedObjectWithMetadata[]; const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({ _id: obj.id, @@ -312,16 +282,13 @@ describe('ObjectsTable', () => { })), }; - const { fetchExportObjects } = require('../../../lib/fetch_export_objects'); - extractExportDetails.mockImplementation(() => ({ + extractExportDetailsMock.mockImplementation(() => ({ exportedCount: 2, missingRefCount: 1, missingReferences: [{ id: '7', type: 'visualisation' }], })); - const component = shallowWithI18nProvider( - - ); + const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -333,8 +300,8 @@ describe('ObjectsTable', () => { await component.instance().onExport(true); - expect(fetchExportObjects).toHaveBeenCalledWith(mockSelectedSavedObjects, true); - expect(addWarningMock).toHaveBeenCalledWith({ + expect(fetchExportObjectsMock).toHaveBeenCalledWith(http, mockSelectedSavedObjects, true); + expect(notifications.toasts.addWarning).toHaveBeenCalledWith({ title: 'Your file is downloading in the background. ' + 'Some related objects could not be found. ' + @@ -343,25 +310,21 @@ describe('ObjectsTable', () => { }); it('should allow the user to choose when exporting all', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); // Ensure the state changes are reflected component.update(); - component.find('Header').prop('onExportAll')(); + (component.find('Header') as any).prop('onExportAll')(); component.update(); expect(component.find('EuiModal')).toMatchSnapshot(); }); it('should export all', async () => { - const { - fetchExportByTypeAndSearch, - } = require('../../../lib/fetch_export_by_type_and_search'); - const { saveAs } = require('@elastic/filesaver'); - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -370,23 +333,24 @@ describe('ObjectsTable', () => { // Set up mocks const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' }); - fetchExportByTypeAndSearch.mockImplementation(() => blob); + fetchExportByTypeAndSearchMock.mockImplementation(() => blob); await component.instance().onExportAll(); - expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, undefined, true); - expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson'); - expect(addSuccessMock).toHaveBeenCalledWith({ + expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith( + http, + allowedTypes, + undefined, + true + ); + expect(saveAsMock).toHaveBeenCalledWith(blob, 'export.ndjson'); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({ title: 'Your file is downloading in the background', }); }); it('should export all, accounting for the current search criteria', async () => { - const { - fetchExportByTypeAndSearch, - } = require('../../../lib/fetch_export_by_type_and_search'); - const { saveAs } = require('@elastic/filesaver'); - const component = shallowWithI18nProvider(); + const component = shallowRender(); component.instance().onQueryChange({ query: Query.parse('test'), @@ -399,13 +363,18 @@ describe('ObjectsTable', () => { // Set up mocks const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' }); - fetchExportByTypeAndSearch.mockImplementation(() => blob); + fetchExportByTypeAndSearchMock.mockImplementation(() => blob); await component.instance().onExportAll(); - expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, 'test*', true); - expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson'); - expect(addSuccessMock).toHaveBeenCalledWith({ + expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith( + http, + allowedTypes, + 'test*', + true + ); + expect(saveAsMock).toHaveBeenCalledWith(blob, 'export.ndjson'); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({ title: 'Your file is downloading in the background', }); }); @@ -413,7 +382,7 @@ describe('ObjectsTable', () => { describe('import', () => { it('should show the flyout', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -427,7 +396,7 @@ describe('ObjectsTable', () => { }); it('should hide the flyout', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -443,9 +412,7 @@ describe('ObjectsTable', () => { describe('relationships', () => { it('should fetch relationships', async () => { - const { getRelationships } = require('../../../lib/get_relationships'); - - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -454,17 +421,11 @@ describe('ObjectsTable', () => { await component.instance().getRelationships('search', '1'); const savedObjectTypes = ['index-pattern', 'visualization', 'dashboard', 'search']; - expect(getRelationships).toHaveBeenCalledWith( - 'search', - '1', - savedObjectTypes, - defaultProps.$http, - defaultProps.basePath - ); + expect(getRelationshipsMock).toHaveBeenCalledWith(http, 'search', '1', savedObjectTypes); }); it('should show the flyout', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -483,7 +444,7 @@ describe('ObjectsTable', () => { uiCapabilitiesPath: 'discover.show', }, }, - }); + } as SavedObjectWithMetadata); component.update(); expect(component.find(Relationships)).toMatchSnapshot(); @@ -503,7 +464,7 @@ describe('ObjectsTable', () => { }); it('should hide the flyout', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -522,12 +483,12 @@ describe('ObjectsTable', () => { describe('delete', () => { it('should show a confirm modal', async () => { - const component = shallowWithI18nProvider(); + const component = shallowRender(); const mockSelectedSavedObjects = [ - { id: '1', type: 'index-pattern', title: 'Title 1' }, - { id: '3', type: 'dashboard', title: 'Title 2' }, - ]; + { id: '1', type: 'index-pattern' }, + { id: '3', type: 'dashboard' }, + ] as SavedObjectWithMetadata[]; // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -546,7 +507,7 @@ describe('ObjectsTable', () => { const mockSelectedSavedObjects = [ { id: '1', type: 'index-pattern' }, { id: '3', type: 'dashboard' }, - ]; + ] as SavedObjectWithMetadata[]; const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({ id: obj.id, @@ -562,9 +523,7 @@ describe('ObjectsTable', () => { delete: jest.fn(), }; - const component = shallowWithI18nProvider( - - ); + const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx similarity index 73% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js rename to src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 188762f165b24..c76fea5a0fb29 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -17,17 +17,10 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { saveAs } from '@elastic/filesaver'; import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { debounce } from 'lodash'; -import { Header } from './components/header'; -import { Flyout } from './components/flyout'; -import { Relationships } from './components/relationships'; -import { Table } from './components/table'; -import { toastNotifications } from 'ui/notify'; - +// @ts-ignore +import { saveAs } from '@elastic/filesaver'; import { EuiSpacer, Query, @@ -54,7 +47,15 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - +import { + SavedObjectsClientContract, + SavedObjectsFindOptions, + HttpStart, + OverlayStart, + NotificationsStart, + ApplicationStart, +} from 'src/core/public'; +import { IndexPatternsContract } from '../../../../data/public'; import { parseQuery, getSavedObjectCounts, @@ -63,39 +64,72 @@ import { fetchExportObjects, fetchExportByTypeAndSearch, findObjects, + extractExportDetails, + SavedObjectsExportResultDetails, } from '../../lib'; -import { extractExportDetails } from '../../lib/extract_export_details'; - -export const POSSIBLE_TYPES = chrome.getInjected('importAndExportableTypes'); - -export class ObjectsTable extends Component { - static propTypes = { - savedObjectsClient: PropTypes.object.isRequired, - indexPatterns: PropTypes.object.isRequired, - $http: PropTypes.func.isRequired, - basePath: PropTypes.string.isRequired, - perPageConfig: PropTypes.number, - newIndexPatternUrl: PropTypes.string.isRequired, - confirmModalPromise: PropTypes.func.isRequired, - services: PropTypes.array.isRequired, - uiCapabilities: PropTypes.object.isRequired, - goInspectObject: PropTypes.func.isRequired, - canGoInApp: PropTypes.func.isRequired, - }; +import { SavedObjectWithMetadata } from '../../types'; +import { + ISavedObjectsManagementServiceRegistry, + SavedObjectsManagementActionServiceStart, +} from '../../services'; +import { Header, Table, Flyout, Relationships } from './components'; + +interface ExportAllOption { + id: string; + label: string; +} - constructor(props) { +export interface SavedObjectsTableProps { + allowedTypes: string[]; + serviceRegistry: ISavedObjectsManagementServiceRegistry; + actionRegistry: SavedObjectsManagementActionServiceStart; + savedObjectsClient: SavedObjectsClientContract; + indexPatterns: IndexPatternsContract; + http: HttpStart; + overlays: OverlayStart; + notifications: NotificationsStart; + applications: ApplicationStart; + perPageConfig: number; + goInspectObject: (obj: SavedObjectWithMetadata) => void; + canGoInApp: (obj: SavedObjectWithMetadata) => boolean; +} + +export interface SavedObjectsTableState { + totalCount: number; + page: number; + perPage: number; + savedObjects: SavedObjectWithMetadata[]; + savedObjectCounts: Record; + activeQuery: Query; + selectedSavedObjects: SavedObjectWithMetadata[]; + isShowingImportFlyout: boolean; + isSearching: boolean; + filteredItemCount: number; + isShowingRelationships: boolean; + relationshipObject?: SavedObjectWithMetadata; + isShowingDeleteConfirmModal: boolean; + isShowingExportAllOptionsModal: boolean; + isDeleting: boolean; + exportAllOptions: ExportAllOption[]; + exportAllSelectedOptions: Record; + isIncludeReferencesDeepChecked: boolean; +} + +export class SavedObjectsTable extends Component { + private _isMounted = false; + + constructor(props: SavedObjectsTableProps) { super(props); - this.savedObjectTypes = POSSIBLE_TYPES; this.state = { totalCount: 0, page: 0, perPage: props.perPageConfig || 50, savedObjects: [], - savedObjectCounts: this.savedObjectTypes.reduce((typeToCountMap, type) => { + savedObjectCounts: props.allowedTypes.reduce((typeToCountMap, type) => { typeToCountMap[type] = 0; return typeToCountMap; - }, {}), + }, {} as Record), activeQuery: Query.parse(''), selectedSavedObjects: [], isShowingImportFlyout: false, @@ -124,21 +158,20 @@ export class ObjectsTable extends Component { } fetchCounts = async () => { + const { allowedTypes } = this.props; const { queryText, visibleTypes } = parseQuery(this.state.activeQuery); - const filteredTypes = this.savedObjectTypes.filter( - type => !visibleTypes || visibleTypes.includes(type) - ); + const filteredTypes = allowedTypes.filter(type => !visibleTypes || visibleTypes.includes(type)); // These are the saved objects visible in the table. const filteredSavedObjectCounts = await getSavedObjectCounts( - this.props.$http, + this.props.http, filteredTypes, queryText ); - const exportAllOptions = []; - const exportAllSelectedOptions = {}; + const exportAllOptions: ExportAllOption[] = []; + const exportAllSelectedOptions: Record = {}; Object.keys(filteredSavedObjectCounts).forEach(id => { // Add this type as a bulk-export option. @@ -147,17 +180,13 @@ export class ObjectsTable extends Component { label: `${id} (${filteredSavedObjectCounts[id] || 0})`, }); - // Select it by defayult. + // Select it by default. exportAllSelectedOptions[id] = true; }); // Fetch all the saved objects that exist so we can accurately populate the counts within // the table filter dropdown. - const savedObjectCounts = await getSavedObjectCounts( - this.props.$http, - this.savedObjectTypes, - queryText - ); + const savedObjectCounts = await getSavedObjectCounts(this.props.http, allowedTypes, queryText); this.setState(state => ({ ...state, @@ -178,66 +207,64 @@ export class ObjectsTable extends Component { debouncedFetch = debounce(async () => { const { activeQuery: query, page, perPage } = this.state; + const { notifications, http, allowedTypes } = this.props; const { queryText, visibleTypes } = parseQuery(query); // "searchFields" is missing from the "findOptions" but gets injected via the API. // The API extracts the fields from each uiExports.savedObjectsManagement "defaultSearchField" attribute - const findOptions = { + const findOptions: SavedObjectsFindOptions = { search: queryText ? `${queryText}*` : undefined, perPage, page: page + 1, fields: ['id'], - type: this.savedObjectTypes.filter(type => !visibleTypes || visibleTypes.includes(type)), + type: allowedTypes.filter(type => !visibleTypes || visibleTypes.includes(type)), }; if (findOptions.type.length > 1) { findOptions.sortField = 'type'; } - let resp; try { - resp = await findObjects(findOptions); + const resp = await findObjects(http, findOptions); + if (!this._isMounted) { + return; + } + + this.setState(({ activeQuery }) => { + // ignore results for old requests + if (activeQuery.text !== query.text) { + return null; + } + + return { + savedObjects: resp.savedObjects, + filteredItemCount: resp.total, + isSearching: false, + }; + }); } catch (error) { if (this._isMounted) { this.setState({ isSearching: false, }); } - toastNotifications.addDanger({ + notifications.toasts.addDanger({ title: i18n.translate( - 'kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage', + 'savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage', { defaultMessage: 'Unable find saved objects' } ), text: `${error}`, }); - return; - } - - if (!this._isMounted) { - return; } - - this.setState(({ activeQuery }) => { - // ignore results for old requests - if (activeQuery.text !== query.text) { - return {}; - } - - return { - savedObjects: resp.savedObjects, - filteredItemCount: resp.total, - isSearching: false, - }; - }); }, 300); refreshData = async () => { await Promise.all([this.fetchSavedObjects(), this.fetchCounts()]); }; - onSelectionChanged = selection => { + onSelectionChanged = (selection: SavedObjectWithMetadata[]) => { this.setState({ selectedSavedObjects: selection }); }; - onQueryChange = ({ query }) => { + onQueryChange = ({ query }: { query: Query }) => { // TODO: Use isSameQuery to compare new query with state.activeQuery to avoid re-fetching the // same data we already have. this.setState( @@ -253,7 +280,7 @@ export class ObjectsTable extends Component { ); }; - onTableChange = async table => { + onTableChange = async (table: any) => { const { index: page, size: perPage } = table.page || {}; this.setState( @@ -266,7 +293,7 @@ export class ObjectsTable extends Component { ); }; - onShowRelationships = object => { + onShowRelationships = (object: SavedObjectWithMetadata) => { this.setState({ isShowingRelationships: true, relationshipObject: object, @@ -280,16 +307,17 @@ export class ObjectsTable extends Component { }); }; - onExport = async includeReferencesDeep => { + onExport = async (includeReferencesDeep: boolean) => { const { selectedSavedObjects } = this.state; + const { notifications, http } = this.props; const objectsToExport = selectedSavedObjects.map(obj => ({ id: obj.id, type: obj.type })); let blob; try { - blob = await fetchExportObjects(objectsToExport, includeReferencesDeep); + blob = await fetchExportObjects(http, objectsToExport, includeReferencesDeep); } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('kbn.management.objects.objectsTable.export.dangerNotification', { + notifications.toasts.addDanger({ + title: i18n.translate('savedObjectsManagement.objectsTable.export.dangerNotification', { defaultMessage: 'Unable to generate export', }), }); @@ -304,24 +332,26 @@ export class ObjectsTable extends Component { onExportAll = async () => { const { exportAllSelectedOptions, isIncludeReferencesDeepChecked, activeQuery } = this.state; + const { notifications, http } = this.props; const { queryText } = parseQuery(activeQuery); const exportTypes = Object.entries(exportAllSelectedOptions).reduce((accum, [id, selected]) => { if (selected) { accum.push(id); } return accum; - }, []); + }, [] as string[]); let blob; try { blob = await fetchExportByTypeAndSearch( + http, exportTypes, queryText ? `${queryText}*` : undefined, isIncludeReferencesDeepChecked ); } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('kbn.management.objects.objectsTable.export.dangerNotification', { + notifications.toasts.addDanger({ + title: i18n.translate('savedObjectsManagement.objectsTable.export.dangerNotification', { defaultMessage: 'Unable to generate export', }), }); @@ -335,11 +365,12 @@ export class ObjectsTable extends Component { this.setState({ isShowingExportAllOptionsModal: false }); }; - showExportSuccessMessage = exportDetails => { + showExportSuccessMessage = (exportDetails: SavedObjectsExportResultDetails | undefined) => { + const { notifications } = this.props; if (exportDetails && exportDetails.missingReferences.length > 0) { - toastNotifications.addWarning({ + notifications.toasts.addWarning({ title: i18n.translate( - 'kbn.management.objects.objectsTable.export.successWithMissingRefsNotification', + 'savedObjectsManagement.objectsTable.export.successWithMissingRefsNotification', { defaultMessage: 'Your file is downloading in the background. ' + @@ -349,8 +380,8 @@ export class ObjectsTable extends Component { ), }); } else { - toastNotifications.addSuccess({ - title: i18n.translate('kbn.management.objects.objectsTable.export.successNotification', { + notifications.toasts.addSuccess({ + title: i18n.translate('savedObjectsManagement.objectsTable.export.successNotification', { defaultMessage: 'Your file is downloading in the background', }), }); @@ -412,30 +443,30 @@ export class ObjectsTable extends Component { }); }; - getRelationships = async (type, id) => { - return await getRelationships( - type, - id, - this.savedObjectTypes, - this.props.$http, - this.props.basePath - ); + getRelationships = async (type: string, id: string) => { + const { allowedTypes, http } = this.props; + return await getRelationships(http, type, id, allowedTypes); }; renderFlyout() { if (!this.state.isShowingImportFlyout) { return null; } + const { applications } = this.props; + const newIndexPatternUrl = applications.getUrlForApp('kibana', { + path: '#/management/kibana/index_pattern', + }); return ( ); } @@ -447,10 +478,10 @@ export class ObjectsTable extends Component { return ( @@ -482,7 +513,7 @@ export class ObjectsTable extends Component { } @@ -491,19 +522,19 @@ export class ObjectsTable extends Component { buttonColor="danger" cancelButtonText={ } confirmButtonText={ isDeleting ? ( ) : ( ) @@ -512,7 +543,7 @@ export class ObjectsTable extends Component { >

@@ -522,7 +553,7 @@ export class ObjectsTable extends Component { { field: 'type', name: i18n.translate( - 'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName', + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName', { defaultMessage: 'Type' } ), width: '50px', @@ -535,14 +566,14 @@ export class ObjectsTable extends Component { { field: 'id', name: i18n.translate( - 'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', { defaultMessage: 'Id' } ), }, { field: 'meta.title', name: i18n.translate( - 'kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', { defaultMessage: 'Title' } ), }, @@ -586,7 +617,7 @@ export class ObjectsTable extends Component { } @@ -626,7 +657,7 @@ export class ObjectsTable extends Component { name="includeReferencesDeep" label={ } @@ -641,7 +672,7 @@ export class ObjectsTable extends Component { @@ -649,7 +680,7 @@ export class ObjectsTable extends Component { @@ -673,12 +704,13 @@ export class ObjectsTable extends Component { isSearching, savedObjectCounts, } = this.state; + const { http, allowedTypes, applications } = this.props; const selectionConfig = { onSelectionChange: this.onSelectionChanged, }; - const filterOptions = this.savedObjectTypes.map(type => ({ + const filterOptions = allowedTypes.map(type => ({ value: type, name: type, view: `${type} (${savedObjectCounts[type] || 0})`, @@ -698,14 +730,16 @@ export class ObjectsTable extends Component { />
=> { const mock = { - actionRegistry: actionRegistryMock.create(), + actions: actionServiceMock.createSetup(), + serviceRegistry: serviceRegistryMock.create(), }; return mock; }; const createStartContractMock = (): jest.Mocked => { - const mock = {}; + const mock = { + actions: actionServiceMock.createStart(), + }; return mock; }; export const savedObjectsManagementPluginMock = { - createActionRegistry: actionRegistryMock.create, + createServiceRegistry: serviceRegistryMock.create, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, }; diff --git a/src/plugins/saved_objects_management/public/plugin.test.ts b/src/plugins/saved_objects_management/public/plugin.test.ts index 1cafbb235ad5b..09080f46a6869 100644 --- a/src/plugins/saved_objects_management/public/plugin.test.ts +++ b/src/plugins/saved_objects_management/public/plugin.test.ts @@ -20,6 +20,9 @@ import { coreMock } from '../../../core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { homePluginMock } from '../../home/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { managementPluginMock } from '../../management/public/mocks'; +import { dataPluginMock } from '../../data/public/mocks'; import { SavedObjectsManagementPlugin } from './plugin'; describe('SavedObjectsManagementPlugin', () => { @@ -31,10 +34,13 @@ describe('SavedObjectsManagementPlugin', () => { describe('#setup', () => { it('registers the saved_objects feature to the home plugin', async () => { - const coreSetup = coreMock.createSetup(); + const coreSetup = coreMock.createSetup({ + pluginStartDeps: { data: dataPluginMock.createStartContract() }, + }); const homeSetup = homePluginMock.createSetupContract(); + const managementSetup = managementPluginMock.createSetupContract(); - await plugin.setup(coreSetup, { home: homeSetup }); + await plugin.setup(coreSetup, { home: homeSetup, management: managementSetup }); expect(homeSetup.featureCatalogue.register).toHaveBeenCalledTimes(1); expect(homeSetup.featureCatalogue.register).toHaveBeenCalledWith( diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 3f2e9c166058e..c8dede3da9263 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -19,37 +19,59 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { ManagementSetup } from '../../management/public'; +import { DataPublicPluginStart } from '../../data/public'; +import { DashboardStart } from '../../dashboard/public'; +import { DiscoverStart } from '../../discover/public'; import { HomePublicPluginSetup, FeatureCatalogueCategory } from '../../home/public'; +import { VisualizationsStart } from '../../visualizations/public'; import { - SavedObjectsManagementActionRegistry, - ISavedObjectsManagementActionRegistry, + SavedObjectsManagementActionService, + SavedObjectsManagementActionServiceSetup, + SavedObjectsManagementActionServiceStart, + SavedObjectsManagementServiceRegistry, + ISavedObjectsManagementServiceRegistry, } from './services'; +import { registerServices } from './register_services'; export interface SavedObjectsManagementPluginSetup { - actionRegistry: ISavedObjectsManagementActionRegistry; + actions: SavedObjectsManagementActionServiceSetup; + serviceRegistry: ISavedObjectsManagementServiceRegistry; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SavedObjectsManagementPluginStart {} +export interface SavedObjectsManagementPluginStart { + actions: SavedObjectsManagementActionServiceStart; +} export interface SetupDependencies { + management: ManagementSetup; home: HomePublicPluginSetup; } +export interface StartDependencies { + data: DataPublicPluginStart; + dashboard?: DashboardStart; + visualizations?: VisualizationsStart; + discover?: DiscoverStart; +} + export class SavedObjectsManagementPlugin implements Plugin< SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart, SetupDependencies, - {} + StartDependencies > { - private actionRegistry = new SavedObjectsManagementActionRegistry(); + private actionService = new SavedObjectsManagementActionService(); + private serviceRegistry = new SavedObjectsManagementServiceRegistry(); public setup( - core: CoreSetup<{}>, - { home }: SetupDependencies + core: CoreSetup, + { home, management }: SetupDependencies ): SavedObjectsManagementPluginSetup { + const actionSetup = this.actionService.setup(); + home.featureCatalogue.register({ id: 'saved_objects', title: i18n.translate('savedObjectsManagement.objects.savedObjectsTitle', { @@ -65,12 +87,39 @@ export class SavedObjectsManagementPlugin category: FeatureCatalogueCategory.ADMIN, }); + const kibanaSection = management.sections.getSection('kibana'); + if (!kibanaSection) { + throw new Error('`kibana` management section not found.'); + } + kibanaSection.registerApp({ + id: 'objects', + title: i18n.translate('savedObjectsManagement.managementSectionLabel', { + defaultMessage: 'Saved Objects', + }), + order: 10, + mount: async mountParams => { + const { mountManagementSection } = await import('./management_section'); + return mountManagementSection({ + core, + serviceRegistry: this.serviceRegistry, + mountParams, + }); + }, + }); + + // depends on `getStartServices`, should not be awaited + registerServices(this.serviceRegistry, core.getStartServices); + return { - actionRegistry: this.actionRegistry, + actions: actionSetup, + serviceRegistry: this.serviceRegistry, }; } public start(core: CoreStart) { - return {}; + const actionStart = this.actionService.start(); + return { + actions: actionStart, + }; } } diff --git a/src/plugins/saved_objects_management/public/register_services.ts b/src/plugins/saved_objects_management/public/register_services.ts new file mode 100644 index 0000000000000..a34b632b78f6c --- /dev/null +++ b/src/plugins/saved_objects_management/public/register_services.ts @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { StartServicesAccessor } from '../../../core/public'; +import { SavedObjectsManagementPluginStart, StartDependencies } from './plugin'; +import { ISavedObjectsManagementServiceRegistry } from './services'; + +export const registerServices = async ( + registry: ISavedObjectsManagementServiceRegistry, + getStartServices: StartServicesAccessor +) => { + const [coreStart, { dashboard, data, visualizations, discover }] = await getStartServices(); + + if (dashboard) { + registry.register({ + id: 'savedDashboards', + title: 'dashboards', + service: dashboard.getSavedDashboardLoader(), + }); + } + + if (visualizations) { + registry.register({ + id: 'savedVisualizations', + title: 'visualizations', + service: visualizations.savedVisualizationsLoader, + }); + } + + if (discover) { + registry.register({ + id: 'savedSearches', + title: 'searches', + service: discover.savedSearches.createLoader({ + savedObjectsClient: coreStart.savedObjects.client, + indexPatterns: data.indexPatterns, + search: data.search, + chrome: coreStart.chrome, + overlays: coreStart.overlays, + }), + }); + } +}; diff --git a/src/plugins/saved_objects_management/public/services/action_service.mock.ts b/src/plugins/saved_objects_management/public/services/action_service.mock.ts new file mode 100644 index 0000000000000..97c95a589b925 --- /dev/null +++ b/src/plugins/saved_objects_management/public/services/action_service.mock.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 { + SavedObjectsManagementActionService, + SavedObjectsManagementActionServiceSetup, + SavedObjectsManagementActionServiceStart, +} from './action_service'; + +const createSetupMock = (): jest.Mocked => { + const mock = { + register: jest.fn(), + }; + return mock; +}; + +const createStartMock = (): jest.Mocked => { + const mock = { + has: jest.fn(), + getAll: jest.fn(), + }; + + mock.has.mockReturnValue(true); + mock.getAll.mockReturnValue([]); + + return mock; +}; + +const createServiceMock = (): jest.Mocked> => { + const mock = { + setup: jest.fn().mockReturnValue(createSetupMock()), + start: jest.fn().mockReturnValue(createStartMock()), + }; + return mock; +}; + +export const actionServiceMock = { + create: createServiceMock, + createSetup: createSetupMock, + createStart: createStartMock, +}; diff --git a/src/plugins/saved_objects_management/public/services/action_registry.test.ts b/src/plugins/saved_objects_management/public/services/action_service.test.ts similarity index 69% rename from src/plugins/saved_objects_management/public/services/action_registry.test.ts rename to src/plugins/saved_objects_management/public/services/action_service.test.ts index eb3bda00f4196..107554589f83d 100644 --- a/src/plugins/saved_objects_management/public/services/action_registry.test.ts +++ b/src/plugins/saved_objects_management/public/services/action_service.test.ts @@ -17,8 +17,11 @@ * under the License. */ -import { SavedObjectsManagementActionRegistry } from './action_registry'; -import { SavedObjectsManagementAction } from './action_types'; +import { + SavedObjectsManagementActionService, + SavedObjectsManagementActionServiceSetup, +} from './action_service'; +import { SavedObjectsManagementAction } from './types'; class DummyAction extends SavedObjectsManagementAction { constructor(public id: string) { @@ -36,27 +39,30 @@ class DummyAction extends SavedObjectsManagementAction { } describe('SavedObjectsManagementActionRegistry', () => { - let registry: SavedObjectsManagementActionRegistry; + let service: SavedObjectsManagementActionService; + let setup: SavedObjectsManagementActionServiceSetup; const createAction = (id: string): SavedObjectsManagementAction => { return new DummyAction(id); }; beforeEach(() => { - registry = new SavedObjectsManagementActionRegistry(); + service = new SavedObjectsManagementActionService(); + setup = service.setup(); }); describe('#register', () => { it('allows actions to be registered and retrieved', () => { const action = createAction('foo'); - registry.register(action); - expect(registry.getAll()).toContain(action); + setup.register(action); + const start = service.start(); + expect(start.getAll()).toContain(action); }); it('does not allow actions with duplicate ids to be registered', () => { const action = createAction('my-action'); - registry.register(action); - expect(() => registry.register(action)).toThrowErrorMatchingInlineSnapshot( + setup.register(action); + expect(() => setup.register(action)).toThrowErrorMatchingInlineSnapshot( `"Saved Objects Management Action with id 'my-action' already exists"` ); }); @@ -65,12 +71,14 @@ describe('SavedObjectsManagementActionRegistry', () => { describe('#has', () => { it('returns true when an action with a matching ID exists', () => { const action = createAction('existing-action'); - registry.register(action); - expect(registry.has('existing-action')).toEqual(true); + setup.register(action); + const start = service.start(); + expect(start.has('existing-action')).toEqual(true); }); it(`returns false when an action doesn't exist`, () => { - expect(registry.has('missing-action')).toEqual(false); + const start = service.start(); + expect(start.has('missing-action')).toEqual(false); }); }); }); diff --git a/src/plugins/saved_objects_management/public/services/action_registry.ts b/src/plugins/saved_objects_management/public/services/action_service.ts similarity index 56% rename from src/plugins/saved_objects_management/public/services/action_registry.ts rename to src/plugins/saved_objects_management/public/services/action_service.ts index 8bf77231dd73f..2b0b4cf5431e5 100644 --- a/src/plugins/saved_objects_management/public/services/action_registry.ts +++ b/src/plugins/saved_objects_management/public/services/action_service.ts @@ -17,36 +17,44 @@ * under the License. */ -import { SavedObjectsManagementAction } from './action_types'; - -export type ISavedObjectsManagementActionRegistry = PublicMethodsOf< - SavedObjectsManagementActionRegistry ->; - -export class SavedObjectsManagementActionRegistry { - private readonly actions = new Map(); +import { SavedObjectsManagementAction } from './types'; +export interface SavedObjectsManagementActionServiceSetup { /** * register given action in the registry. */ - register(action: SavedObjectsManagementAction) { - if (this.actions.has(action.id)) { - throw new Error(`Saved Objects Management Action with id '${action.id}' already exists`); - } - this.actions.set(action.id, action); - } + register: (action: SavedObjectsManagementAction) => void; +} +export interface SavedObjectsManagementActionServiceStart { /** * return true if the registry contains given action, false otherwise. */ - has(actionId: string) { - return this.actions.has(actionId); - } - + has: (actionId: string) => boolean; /** * return all {@link SavedObjectsManagementAction | actions} currently registered. */ - getAll() { - return [...this.actions.values()]; + getAll: () => SavedObjectsManagementAction[]; +} + +export class SavedObjectsManagementActionService { + private readonly actions = new Map(); + + setup(): SavedObjectsManagementActionServiceSetup { + return { + register: action => { + if (this.actions.has(action.id)) { + throw new Error(`Saved Objects Management Action with id '${action.id}' already exists`); + } + this.actions.set(action.id, action); + }, + }; + } + + start(): SavedObjectsManagementActionServiceStart { + return { + has: actionId => this.actions.has(actionId), + getAll: () => [...this.actions.values()], + }; } } diff --git a/src/plugins/saved_objects_management/public/services/index.ts b/src/plugins/saved_objects_management/public/services/index.ts index d6353576b8e11..a59ad9012c402 100644 --- a/src/plugins/saved_objects_management/public/services/index.ts +++ b/src/plugins/saved_objects_management/public/services/index.ts @@ -18,7 +18,13 @@ */ export { - SavedObjectsManagementActionRegistry, - ISavedObjectsManagementActionRegistry, -} from './action_registry'; -export { SavedObjectsManagementAction, SavedObjectsManagementRecord } from './action_types'; + SavedObjectsManagementActionService, + SavedObjectsManagementActionServiceStart, + SavedObjectsManagementActionServiceSetup, +} from './action_service'; +export { + SavedObjectsManagementServiceRegistry, + ISavedObjectsManagementServiceRegistry, + SavedObjectsManagementServiceRegistryEntry, +} from './service_registry'; +export { SavedObjectsManagementAction, SavedObjectsManagementRecord } from './types'; diff --git a/src/plugins/saved_objects_management/public/services/action_registry.mock.ts b/src/plugins/saved_objects_management/public/services/service_registry.mock.ts similarity index 79% rename from src/plugins/saved_objects_management/public/services/action_registry.mock.ts rename to src/plugins/saved_objects_management/public/services/service_registry.mock.ts index a9093ad42d0ac..2e671c781928f 100644 --- a/src/plugins/saved_objects_management/public/services/action_registry.mock.ts +++ b/src/plugins/saved_objects_management/public/services/service_registry.mock.ts @@ -17,21 +17,20 @@ * under the License. */ -import { ISavedObjectsManagementActionRegistry } from './action_registry'; +import { ISavedObjectsManagementServiceRegistry } from './service_registry'; -const createRegistryMock = (): jest.Mocked => { +const createRegistryMock = (): jest.Mocked => { const mock = { register: jest.fn(), - has: jest.fn(), - getAll: jest.fn(), + all: jest.fn(), + get: jest.fn(), }; - mock.has.mockReturnValue(true); - mock.getAll.mockReturnValue([]); + mock.all.mockReturnValue([]); return mock; }; -export const actionRegistryMock = { +export const serviceRegistryMock = { create: createRegistryMock, }; diff --git a/src/plugins/saved_objects_management/public/services/service_registry.ts b/src/plugins/saved_objects_management/public/services/service_registry.ts new file mode 100644 index 0000000000000..2d6ec0b92047a --- /dev/null +++ b/src/plugins/saved_objects_management/public/services/service_registry.ts @@ -0,0 +1,49 @@ +/* + * 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 { SavedObjectLoader } from '../../../saved_objects/public'; + +export interface SavedObjectsManagementServiceRegistryEntry { + id: string; + service: SavedObjectLoader; + title: string; +} + +export type ISavedObjectsManagementServiceRegistry = PublicMethodsOf< + SavedObjectsManagementServiceRegistry +>; + +export class SavedObjectsManagementServiceRegistry { + private readonly registry = new Map(); + + public register(entry: SavedObjectsManagementServiceRegistryEntry) { + if (this.registry.has(entry.id)) { + throw new Error(''); + } + this.registry.set(entry.id, entry); + } + + public all(): SavedObjectsManagementServiceRegistryEntry[] { + return [...this.registry.values()]; + } + + public get(id: string): SavedObjectsManagementServiceRegistryEntry | undefined { + return this.registry.get(id); + } +} diff --git a/src/plugins/saved_objects_management/public/services/action_types.ts b/src/plugins/saved_objects_management/public/services/types.ts similarity index 100% rename from src/plugins/saved_objects_management/public/services/action_types.ts rename to src/plugins/saved_objects_management/public/services/types.ts diff --git a/src/plugins/saved_objects_management/public/types.ts b/src/plugins/saved_objects_management/public/types.ts new file mode 100644 index 0000000000000..e91b5d253b55f --- /dev/null +++ b/src/plugins/saved_objects_management/public/types.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SavedObjectMetadata, SavedObjectWithMetadata, SavedObjectRelation } from '../common'; diff --git a/src/plugins/saved_objects_management/server/routes/get_allowed_types.ts b/src/plugins/saved_objects_management/server/routes/get_allowed_types.ts new file mode 100644 index 0000000000000..ab5bec6678946 --- /dev/null +++ b/src/plugins/saved_objects_management/server/routes/get_allowed_types.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from 'src/core/server'; + +export const registerGetAllowedTypesRoute = (router: IRouter) => { + router.get( + { + path: '/api/kibana/management/saved_objects/_allowed_types', + validate: false, + }, + async (context, req, res) => { + const allowedTypes = context.core.savedObjects.typeRegistry + .getImportableAndExportableTypes() + .map(type => type.name); + + return res.ok({ + body: { + types: allowedTypes, + }, + }); + } + ); +}; diff --git a/src/plugins/saved_objects_management/server/routes/index.test.ts b/src/plugins/saved_objects_management/server/routes/index.test.ts index f183972953dce..237760444f04e 100644 --- a/src/plugins/saved_objects_management/server/routes/index.test.ts +++ b/src/plugins/saved_objects_management/server/routes/index.test.ts @@ -34,7 +34,7 @@ describe('registerRoutes', () => { }); expect(httpSetup.createRouter).toHaveBeenCalledTimes(1); - expect(router.get).toHaveBeenCalledTimes(2); + expect(router.get).toHaveBeenCalledTimes(3); expect(router.post).toHaveBeenCalledTimes(2); expect(router.get).toHaveBeenCalledWith( @@ -49,6 +49,12 @@ describe('registerRoutes', () => { }), expect.any(Function) ); + expect(router.get).toHaveBeenCalledWith( + expect.objectContaining({ + path: '/api/kibana/management/saved_objects/_allowed_types', + }), + expect.any(Function) + ); expect(router.post).toHaveBeenCalledWith( expect.objectContaining({ path: '/api/kibana/management/saved_objects/scroll/counts', diff --git a/src/plugins/saved_objects_management/server/routes/index.ts b/src/plugins/saved_objects_management/server/routes/index.ts index 2c6adb71ed3ce..0929de56b215e 100644 --- a/src/plugins/saved_objects_management/server/routes/index.ts +++ b/src/plugins/saved_objects_management/server/routes/index.ts @@ -23,6 +23,7 @@ import { registerFindRoute } from './find'; import { registerScrollForCountRoute } from './scroll_count'; import { registerScrollForExportRoute } from './scroll_export'; import { registerRelationshipsRoute } from './relationships'; +import { registerGetAllowedTypesRoute } from './get_allowed_types'; interface RegisterRouteOptions { http: HttpServiceSetup; @@ -35,4 +36,5 @@ export function registerRoutes({ http, managementServicePromise }: RegisterRoute registerScrollForCountRoute(router); registerScrollForExportRoute(router); registerRelationshipsRoute(router, managementServicePromise); + registerGetAllowedTypesRoute(router); } diff --git a/src/plugins/saved_objects_management/server/types.ts b/src/plugins/saved_objects_management/server/types.ts index 5c4763d357e87..bd17d6a19ae70 100644 --- a/src/plugins/saved_objects_management/server/types.ts +++ b/src/plugins/saved_objects_management/server/types.ts @@ -17,38 +17,10 @@ * under the License. */ -import { SavedObject } from 'src/core/server'; - // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SavedObjectsManagementPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SavedObjectsManagementPluginStart {} -/** - * The metadata injected into a {@link SavedObject | saved object} when returning - * {@link SavedObjectWithMetadata | enhanced objects} from the plugin API endpoints. - */ -export interface SavedObjectMetadata { - icon?: string; - title?: string; - editUrl?: string; - inAppUrl?: { path: string; uiCapabilitiesPath: string }; -} - -/** - * A {@link SavedObject | saved object} enhanced with meta properties used by the client-side plugin. - */ -export type SavedObjectWithMetadata = SavedObject & { - meta: SavedObjectMetadata; -}; - -/** - * Represents a relation between two {@link SavedObject | saved object} - */ -export interface SavedObjectRelation { - id: string; - type: string; - relationship: 'child' | 'parent'; - meta: SavedObjectMetadata; -} +export { SavedObjectMetadata, SavedObjectWithMetadata, SavedObjectRelation } from '../common'; diff --git a/test/common/config.js b/test/common/config.js index faf8cef027170..ca80dfb01012f 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -56,6 +56,10 @@ export default function() { `--elasticsearch.password=${kibanaServerTestUser.password}`, `--home.disableWelcomeScreen=true`, '--telemetry.banner=false', + '--telemetry.optIn=false', + // These are *very* important to have them pointing to staging + '--telemetry.url=https://telemetry-staging.elastic.co/xpack/v2/send', + '--telemetry.optInStatusUrl=https://telemetry-staging.elastic.co/opt_in_status/v2/send', `--server.maxPayloadBytes=1679958`, // newsfeed mock service `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'newsfeed')}`, diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 13d2365c07191..5017947e95d03 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -47,9 +47,7 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { */ public readonly browserType: string = browserType; - public readonly isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes( - browserType - ); + public readonly isChrome: boolean = browserType === Browsers.Chrome; public readonly isFirefox: boolean = browserType === Browsers.Firefox; diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 8b57ecd3c8235..157918df874c8 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -55,7 +55,6 @@ export class WebElementWrapper { private driver: WebDriver = this.webDriver.driver; private Keys = Key; public isW3CEnabled: boolean = (this.webDriver.driver as any).executor_.w3c === true; - public isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes(this.browserType); public static create( webElement: WebElement | WebElementWrapper, @@ -64,7 +63,7 @@ export class WebElementWrapper { timeout: number, fixedHeaderHeight: number, logger: ToolingLog, - browserType: Browsers + browserType: string ): WebElementWrapper { if (webElement instanceof WebElementWrapper) { return webElement; @@ -88,7 +87,7 @@ export class WebElementWrapper { private timeout: number, private fixedHeaderHeight: number, private logger: ToolingLog, - private browserType: Browsers + private browserType: string ) {} private async _findWithCustomTimeout( @@ -244,7 +243,7 @@ export class WebElementWrapper { return this.clearValueWithKeyboard(); } await this.retryCall(async function clearValue(wrapper) { - if (wrapper.isChromium || options.withJS) { + if (wrapper.browserType === Browsers.Chrome || options.withJS) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2702 await wrapper.driver.executeScript(`arguments[0].value=''`, wrapper._webElement); } else { @@ -276,7 +275,7 @@ export class WebElementWrapper { await delay(100); } } else { - if (this.isChromium) { + if (this.browserType === Browsers.Chrome) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=30 await this.retryCall(async function clearValueWithKeyboard(wrapper) { await wrapper.driver.executeScript(`arguments[0].select();`, wrapper._webElement); diff --git a/test/functional/services/remote/browsers.ts b/test/functional/services/remote/browsers.ts index aa6e364d0a09d..46d81f1737a55 100644 --- a/test/functional/services/remote/browsers.ts +++ b/test/functional/services/remote/browsers.ts @@ -21,5 +21,4 @@ export enum Browsers { Chrome = 'chrome', Firefox = 'firefox', InternetExplorer = 'ie', - ChromiumEdge = 'msedge', } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index b0724488cb5db..e571a1a7e5551 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -64,23 +64,18 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { lifecycle, config.get('browser.logPollingMs') ); - const isW3CEnabled = (driver as any).executor_.w3c; const caps = await driver.getCapabilities(); - const browserVersion = caps.get( - isW3CEnabled || browserType === Browsers.ChromiumEdge ? 'browserVersion' : 'version' - ); + const browserVersion = caps.get(isW3CEnabled ? 'browserVersion' : 'version'); - log.info( - `Remote initialized: ${caps.get( - 'browserName' - )} ${browserVersion}, w3c compliance=${isW3CEnabled}, collectingCoverage=${collectCoverage}` - ); + log.info(`Remote initialized: ${caps.get('browserName')} ${browserVersion}`); - if ([Browsers.Chrome, Browsers.ChromiumEdge].includes(browserType)) { + if (browserType === Browsers.Chrome) { log.info( - `${browserType}driver version: ${caps.get(browserType)[`${browserType}driverVersion`]}` + `Chromedriver version: ${ + caps.get('chrome').chromedriverVersion + }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` ); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index fc0b5bbb787c8..3bf5b865aa7ba 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -31,12 +31,10 @@ import { Builder, Capabilities, By, logging, until } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; // @ts-ignore internal modules are not typed -import edge from 'selenium-webdriver/edge'; -import { installDriver } from 'ms-chromium-edge-driver'; -// @ts-ignore internal modules are not typed import { Executor } from 'selenium-webdriver/lib/http'; // @ts-ignore internal modules are not typed import { getLogger } from 'selenium-webdriver/lib/logging'; + import { pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -65,7 +63,6 @@ Executor.prototype.execute = preventParallelCalls( ); let attemptCounter = 0; -let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( log: ToolingLog, browserType: Browsers, @@ -77,46 +74,6 @@ async function attemptToCreateCommand( const buildDriverInstance = async () => { switch (browserType) { - case 'msedge': { - if (edgePaths && edgePaths.browserPath && edgePaths.driverPath) { - const edgeOptions = new edge.Options(); - if (headlessBrowser === '1') { - // @ts-ignore internal modules are not typed - edgeOptions.headless(); - } - // @ts-ignore internal modules are not typed - edgeOptions.setEdgeChromium(true); - // @ts-ignore internal modules are not typed - edgeOptions.setBinaryPath(edgePaths.browserPath); - const session = await new Builder() - .forBrowser('MicrosoftEdge') - .setEdgeOptions(edgeOptions) - .setEdgeService(new edge.ServiceBuilder(edgePaths.driverPath)) - .build(); - return { - session, - consoleLog$: pollForLogEntry$( - session, - logging.Type.BROWSER, - logPollingMs, - lifecycle.cleanup.after$ - ).pipe( - takeUntil(lifecycle.cleanup.after$), - map(({ message, level: { name: level } }) => ({ - message: message.replace(/\\n/g, '\n'), - level, - })) - ), - }; - } else { - throw new Error( - `Chromium Edge session requires browser or driver path to be defined: ${JSON.stringify( - edgePaths - )}` - ); - } - } - case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ @@ -308,11 +265,6 @@ export async function initWebDriver( log.verbose(entry.message); }); - // download Edge driver only in case of usage - if (browserType === Browsers.ChromiumEdge) { - edgePaths = await installDriver(); - } - return await Promise.race([ (async () => { await delay(2 * MINUTE); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index bbbcc062786b0..b3744f7cf93ab 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -24,7 +24,7 @@ "xpack.lens": "legacy/plugins/lens", "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", - "xpack.logstash": "legacy/plugins/logstash", + "xpack.logstash": ["plugins/logstash", "legacy/plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index 4b045b0c5edcf..cba19ce7da80f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -5,8 +5,6 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { TimeRange, Filter as DataFilter } from 'src/plugins/data/public'; -import { EmbeddableInput } from 'src/plugins/embeddable/public'; import { getQueryFilters } from '../../../public/lib/build_embeddable_filters'; import { Filter, MapCenter, TimeRange as TimeRangeArg } from '../../../types'; import { @@ -15,6 +13,7 @@ import { EmbeddableExpression, } from '../../expression_types'; import { getFunctionHelp } from '../../../i18n'; +import { MapEmbeddableInput } from '../../../../../plugins/maps/public'; interface Arguments { id: string; @@ -24,32 +23,12 @@ interface Arguments { timerange: TimeRangeArg | null; } -// Map embeddable is missing proper typings, so type is just to document what we -// are expecting to pass to the embeddable -export type SavedMapInput = EmbeddableInput & { - id: string; - isLayerTOCOpen: boolean; - timeRange?: TimeRange; - refreshConfig: { - isPaused: boolean; - interval: number; - }; - hideFilterActions: true; - filters: DataFilter[]; - mapCenter?: { - lat: number; - lon: number; - zoom: number; - }; - hiddenLayers?: string[]; -}; - const defaultTimeRange = { from: 'now-15m', to: 'now', }; -type Output = EmbeddableExpression; +type Output = EmbeddableExpression; export function savedMap(): ExpressionFunctionDefinition< 'savedMap', @@ -108,8 +87,8 @@ export function savedMap(): ExpressionFunctionDefinition< filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, refreshConfig: { - isPaused: false, - interval: 0, + pause: false, + value: 0, }, mapCenter: center, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts index 7cd1efe9e27c8..a654c6b28b350 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts @@ -6,11 +6,14 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { CanvasSetup } from '../public'; +import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; import { functions } from './functions/browser'; import { typeFunctions } from './expression_types'; // @ts-ignore: untyped local -import { renderFunctions } from './renderers'; +import { renderFunctions, renderFunctionFactories } from './renderers'; import { elementSpecs } from './elements'; // @ts-ignore Untyped Local @@ -30,13 +33,26 @@ interface SetupDeps { canvas: CanvasSetup; } +export interface StartDeps { + embeddable: EmbeddableStart; + uiActions: UiActionsStart; + inspector: InspectorStart; +} + /** @internal */ -export class CanvasSrcPlugin implements Plugin<{}, {}, SetupDeps, {}> { - public setup(core: CoreSetup, plugins: SetupDeps) { +export class CanvasSrcPlugin implements Plugin { + public setup(core: CoreSetup, plugins: SetupDeps) { plugins.canvas.addFunctions(functions); plugins.canvas.addTypes(typeFunctions); + plugins.canvas.addRenderers(renderFunctions); + core.getStartServices().then(([coreStart, depsStart]) => { + plugins.canvas.addRenderers( + renderFunctionFactories.map((factory: any) => factory(coreStart, depsStart)) + ); + }); + plugins.canvas.addElements(elementSpecs); plugins.canvas.addDatasourceUIs(datasourceSpecs); plugins.canvas.addModelUIs(modelSpecs); @@ -45,11 +61,7 @@ export class CanvasSrcPlugin implements Plugin<{}, {}, SetupDeps, {}> { plugins.canvas.addTagUIs(tagSpecs); plugins.canvas.addTemplates(templateSpecs); plugins.canvas.addTransformUIs(transformSpecs); - - return {}; } - public start(core: CoreStart, plugins: {}) { - return {}; - } + public start(core: CoreStart, plugins: StartDeps) {} } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index 817be6e144fc8..a1096d50c1653 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -7,7 +7,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nContext } from 'ui/i18n'; -import { npStart } from 'ui/new_platform'; +import { CoreStart } from '../../../../../../../src/core/public'; +import { StartDeps } from '../../plugin'; import { IEmbeddable, EmbeddableFactory, @@ -28,86 +29,88 @@ const embeddablesRegistry: { [key: string]: IEmbeddable; } = {}; -const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) => { - return ( -
- - - -
- ); +const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => { + return (embeddableObject: IEmbeddable, domNode: HTMLElement) => { + return ( +
+ + + +
+ ); + }; }; -const embeddable = () => ({ - name: 'embeddable', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render: async ( - domNode: HTMLElement, - { input, embeddableType }: EmbeddableExpression, - handlers: RendererHandlers - ) => { - const uniqueId = handlers.getElementId(); - - if (!embeddablesRegistry[uniqueId]) { - const factory = Array.from(npStart.plugins.embeddable.getEmbeddableFactories()).find( - embeddableFactory => embeddableFactory.type === embeddableType - ) as EmbeddableFactory; - - if (!factory) { - handlers.done(); - throw new EmbeddableFactoryNotFoundError(embeddableType); - } - - const embeddableObject = await factory.createFromSavedObject(input.id, input); +export const embeddableRendererFactory = (core: CoreStart, plugins: StartDeps) => { + const renderEmbeddable = renderEmbeddableFactory(core, plugins); + return () => ({ + name: 'embeddable', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + { input, embeddableType }: EmbeddableExpression, + handlers: RendererHandlers + ) => { + const uniqueId = handlers.getElementId(); + + if (!embeddablesRegistry[uniqueId]) { + const factory = Array.from(plugins.embeddable.getEmbeddableFactories()).find( + embeddableFactory => embeddableFactory.type === embeddableType + ) as EmbeddableFactory; + + if (!factory) { + handlers.done(); + throw new EmbeddableFactoryNotFoundError(embeddableType); + } - embeddablesRegistry[uniqueId] = embeddableObject; - ReactDOM.unmountComponentAtNode(domNode); + const embeddableObject = await factory.createFromSavedObject(input.id, input); - const subscription = embeddableObject.getInput$().subscribe(function(updatedInput) { - const updatedExpression = embeddableInputToExpression(updatedInput, embeddableType); + embeddablesRegistry[uniqueId] = embeddableObject; + ReactDOM.unmountComponentAtNode(domNode); - if (updatedExpression) { - handlers.onEmbeddableInputChange(updatedExpression); - } - }); + const subscription = embeddableObject.getInput$().subscribe(function(updatedInput) { + const updatedExpression = embeddableInputToExpression(updatedInput, embeddableType); - ReactDOM.render(renderEmbeddable(embeddableObject, domNode), domNode, () => handlers.done()); + if (updatedExpression) { + handlers.onEmbeddableInputChange(updatedExpression); + } + }); - handlers.onResize(() => { ReactDOM.render(renderEmbeddable(embeddableObject, domNode), domNode, () => handlers.done() ); - }); - handlers.onDestroy(() => { - subscription.unsubscribe(); - handlers.onEmbeddableDestroyed(); + handlers.onResize(() => { + ReactDOM.render(renderEmbeddable(embeddableObject, domNode), domNode, () => + handlers.done() + ); + }); - delete embeddablesRegistry[uniqueId]; + handlers.onDestroy(() => { + subscription.unsubscribe(); + handlers.onEmbeddableDestroyed(); - return ReactDOM.unmountComponentAtNode(domNode); - }); - } else { - embeddablesRegistry[uniqueId].updateInput(input); - } - }, -}); + delete embeddablesRegistry[uniqueId]; -export { embeddable }; + return ReactDOM.unmountComponentAtNode(domNode); + }); + } else { + embeddablesRegistry[uniqueId].updateInput(input); + } + }, + }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts index 4c294fb37c2db..f9ff94ee7d8f1 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts @@ -5,7 +5,7 @@ */ import { toExpression } from './map'; -import { SavedMapInput } from '../../../functions/common/saved_map'; +import { MapEmbeddableInput } from '../../../../../maps/public'; import { fromExpression, Ast } from '@kbn/interpreter/common'; const baseSavedMapInput = { @@ -13,15 +13,15 @@ const baseSavedMapInput = { filters: [], isLayerTOCOpen: false, refreshConfig: { - isPaused: true, - interval: 0, + pause: true, + value: 0, }, hideFilterActions: true as true, }; describe('toExpression', () => { it('converts to a savedMap expression', () => { - const input: SavedMapInput = { + const input: MapEmbeddableInput = { ...baseSavedMapInput, }; @@ -39,7 +39,7 @@ describe('toExpression', () => { }); it('includes optional input values', () => { - const input: SavedMapInput = { + const input: MapEmbeddableInput = { ...baseSavedMapInput, mapCenter: { lat: 1, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts index e3f9eca61ae28..e0cb71c17774c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedMapInput } from '../../../functions/common/saved_map'; +import { MapEmbeddableInput } from '../../../../../maps/public'; -export function toExpression(input: SavedMapInput): string { +export function toExpression(input: MapEmbeddableInput): string { const expressionParts = [] as string[]; expressionParts.push('savedMap'); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js index 48364be06e539..84f92f5149893 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js @@ -7,7 +7,7 @@ import { advancedFilter } from './advanced_filter'; import { debug } from './debug'; import { dropdownFilter } from './dropdown_filter'; -import { embeddable } from './embeddable/embeddable'; +import { embeddableRendererFactory } from './embeddable/embeddable'; import { error } from './error'; import { image } from './image'; import { markdown } from './markdown'; @@ -26,7 +26,6 @@ export const renderFunctions = [ advancedFilter, debug, dropdownFilter, - embeddable, error, image, markdown, @@ -41,3 +40,5 @@ export const renderFunctions = [ text, timeFilter, ]; + +export const renderFunctionFactories = [embeddableRendererFactory]; diff --git a/x-pack/legacy/plugins/canvas/index.js b/x-pack/legacy/plugins/canvas/index.js index 489b9600f200e..a1d4b35826b00 100644 --- a/x-pack/legacy/plugins/canvas/index.js +++ b/x-pack/legacy/plugins/canvas/index.js @@ -7,9 +7,7 @@ import { resolve } from 'path'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { init } from './init'; -import { mappings } from './server/mappings'; import { CANVAS_APP, CANVAS_TYPE, CUSTOM_ELEMENT_TYPE } from './common/lib'; -import { migrations } from './migrations'; export function canvas(kibana) { return new kibana.Plugin({ @@ -33,8 +31,6 @@ export function canvas(kibana) { 'plugins/canvas/lib/window_error_handler.js', ], home: ['plugins/canvas/legacy_register_feature'], - mappings, - migrations, savedObjectsManagement: { [CANVAS_TYPE]: { icon: 'canvasApp', diff --git a/x-pack/legacy/plugins/canvas/migrations.js b/x-pack/legacy/plugins/canvas/migrations.js deleted file mode 100644 index d5b3d3fb1ce2a..0000000000000 --- a/x-pack/legacy/plugins/canvas/migrations.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CANVAS_TYPE } from './common/lib'; - -export const migrations = { - [CANVAS_TYPE]: { - '7.0.0': doc => { - if (doc.attributes) { - delete doc.attributes.id; - } - return doc; - }, - }, -}; diff --git a/x-pack/legacy/plugins/canvas/migrations.test.js b/x-pack/legacy/plugins/canvas/migrations.test.js deleted file mode 100644 index 182ef3b18cce7..0000000000000 --- a/x-pack/legacy/plugins/canvas/migrations.test.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { migrations } from './migrations'; -import { CANVAS_TYPE } from './common/lib'; - -describe(`${CANVAS_TYPE}`, () => { - describe('7.0.0', () => { - const migrate = doc => migrations[CANVAS_TYPE]['7.0.0'](doc); - - it('does not throw error on empty object', () => { - const migratedDoc = migrate({}); - expect(migratedDoc).toMatchInlineSnapshot(`Object {}`); - }); - - it('removes id from "attributes"', () => { - const migratedDoc = migrate({ - foo: true, - attributes: { - id: '123', - bar: true, - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "bar": true, - }, - "foo": true, -} -`); - }); - }); -}); diff --git a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx index 08cd3084c35cf..4916a27fcbe60 100644 --- a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { npStart } from 'ui/new_platform'; import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui'; import { SavedObjectFinderUi, @@ -13,6 +12,7 @@ import { } from '../../../../../../../src/plugins/saved_objects/public/'; import { ComponentStrings } from '../../../i18n'; import { CoreStart } from '../../../../../../../src/core/public'; +import { CanvasStartDeps } from '../../plugin'; const { AddEmbeddableFlyout: strings } = ComponentStrings; @@ -22,11 +22,12 @@ export interface Props { availableEmbeddables: string[]; savedObjects: CoreStart['savedObjects']; uiSettings: CoreStart['uiSettings']; + getEmbeddableFactories: CanvasStartDeps['embeddable']['getEmbeddableFactories']; } export class AddEmbeddableFlyout extends React.Component { onAddPanel = (id: string, savedObjectType: string, name: string) => { - const embeddableFactories = npStart.plugins.embeddable.getEmbeddableFactories(); + const embeddableFactories = this.props.getEmbeddableFactories(); // Find the embeddable type from the saved object type const found = Array.from(embeddableFactories).find(embeddableFactory => { @@ -42,7 +43,7 @@ export class AddEmbeddableFlyout extends React.Component { }; render() { - const embeddableFactories = npStart.plugins.embeddable.getEmbeddableFactories(); + const embeddableFactories = this.props.getEmbeddableFactories(); const availableSavedObjects = Array.from(embeddableFactories) .filter(factory => { diff --git a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/index.tsx b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/index.tsx index a86784d374f49..c13cbfd042237 100644 --- a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/index.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/index.tsx @@ -105,6 +105,7 @@ export class EmbeddableFlyoutPortal extends React.Component, this.el ); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 9b30b3e1ec7ca..30d4ded8571c5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -266,11 +266,17 @@ export class WorkpadLoader extends React.PureComponent { data-test-subj="canvasWorkpadLoaderTable" /> - - - - - + {rows.length > 0 && ( + + + + + + )} ); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js index c80db544bf370..a9a157f5675f8 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js @@ -113,11 +113,13 @@ export class WorkpadTemplates extends React.PureComponent { className="canvasWorkpad__dropzoneTable canvasWorkpad__dropzoneTable--tags" /> - - - - - + {rows.length > 0 && ( + + + + + + )} ); }; diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index a6caa1985325e..4af7c9b2bd057 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -26,7 +26,9 @@ const shimSetupPlugins: CanvasSetupDeps = { }; const shimStartPlugins: CanvasStartDeps = { ...npStart.plugins, + embeddable: npStart.plugins.embeddable, expressions: npStart.plugins.expressions, + inspector: npStart.plugins.inspector, uiActions: npStart.plugins.uiActions, __LEGACY: { // ToDo: Copy directly into canvas diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index d9e5e6b4b084b..3ea3ce625ca71 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -11,6 +11,8 @@ import { initLoadingIndicator } from './lib/loading_indicator'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; // @ts-ignore untyped local import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; @@ -31,7 +33,9 @@ export interface CanvasSetupDeps { } export interface CanvasStartDeps { + embeddable: EmbeddableStart; expressions: ExpressionsStart; + inspector: InspectorStart; uiActions: UiActionsStart; __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; @@ -48,14 +52,19 @@ export interface CanvasStartDeps { // These interfaces are empty for now but will be populate as we need to export // things for other plugins to use at startup or runtime export type CanvasSetup = CanvasApi; -export interface CanvasStart {} // eslint-disable-line @typescript-eslint/no-empty-interface +export type CanvasStart = void; /** @internal */ export class CanvasPlugin implements Plugin { + // TODO: Do we want to completely move canvas_plugin_src into it's own plugin? + private srcPlugin = new CanvasSrcPlugin(); + public setup(core: CoreSetup, plugins: CanvasSetupDeps) { const { api: canvasApi, registries } = getPluginApi(plugins.expressions); + this.srcPlugin.setup(core, { canvas: canvasApi }); + core.application.register({ id: 'canvas', title: 'Canvas App', @@ -84,10 +93,6 @@ export class CanvasPlugin canvasApi.addElements(legacyRegistries.elements.getOriginalFns()); canvasApi.addTypes(legacyRegistries.types.getOriginalFns()); - // TODO: Do we want to completely move canvas_plugin_src into it's own plugin? - const srcPlugin = new CanvasSrcPlugin(); - srcPlugin.setup(core, { canvas: canvasApi }); - // Register core canvas stuff canvasApi.addFunctions(initFunctions({ typesRegistry: plugins.expressions.__LEGACY.types })); canvasApi.addArgumentUIs(argTypeSpecs); @@ -99,8 +104,7 @@ export class CanvasPlugin } public start(core: CoreStart, plugins: CanvasStartDeps) { + this.srcPlugin.start(core, plugins); initLoadingIndicator(core.http.addLoadingCountSource); - - return {}; } } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index b146f2467c46c..0e32f1f053b9d 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -16,6 +16,7 @@ import { } from '../mocks'; import { act } from 'react-dom/test-utils'; import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; +import { esFilters, IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; @@ -243,14 +244,25 @@ describe('suggestion_panel', () => { (mockVisualization.toPreviewExpression as jest.Mock).mockReturnValueOnce('test | expression'); mockDatasource.toExpression.mockReturnValue('datasource_expression'); - mount(); + const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; + const field = ({ name: 'myfield' } as unknown) as IFieldType; + + mount( + + ); expect(expressionRendererMock).toHaveBeenCalledTimes(1); const passedExpression = (expressionRendererMock as jest.Mock).mock.calls[0][0].expression; expect(passedExpression).toMatchInlineSnapshot(` "kibana - | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\" filters=\\"[]\\" + | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\" filters=\\"[{\\\\\\"meta\\\\\\":{\\\\\\"index\\\\\\":\\\\\\"index1\\\\\\"},\\\\\\"exists\\\\\\":{\\\\\\"field\\\\\\":\\\\\\"myfield\\\\\\"}}]\\" | lens_merge_tables layerIds=\\"first\\" tables={datasource_expression} | test | expression" diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 93f6ea6ea67ac..76443027ab88a 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -235,6 +235,7 @@ export function SuggestionPanel({ const expressionContext = { query: frame.query, + filters: frame.filters, timeRange: { from: frame.dateRange.fromDate, to: frame.dateRange.toDate, diff --git a/x-pack/legacy/plugins/logstash/index.js b/x-pack/legacy/plugins/logstash/index.js index ae8571d1c19c3..29f01032f3413 100755 --- a/x-pack/legacy/plugins/logstash/index.js +++ b/x-pack/legacy/plugins/logstash/index.js @@ -5,12 +5,8 @@ */ import { resolve } from 'path'; -import { registerLogstashPipelinesRoutes } from './server/routes/api/pipelines'; -import { registerLogstashPipelineRoutes } from './server/routes/api/pipeline'; -import { registerLogstashUpgradeRoutes } from './server/routes/api/upgrade'; -import { registerLogstashClusterRoutes } from './server/routes/api/cluster'; import { registerLicenseChecker } from './server/lib/register_license_checker'; -import { PLUGIN } from './common/constants'; +import { PLUGIN } from '../../../plugins/logstash/common/constants'; export const logstash = kibana => new kibana.Plugin({ @@ -32,9 +28,5 @@ export const logstash = kibana => }, init: server => { registerLicenseChecker(server); - registerLogstashPipelinesRoutes(server); - registerLogstashPipelineRoutes(server); - registerLogstashUpgradeRoutes(server); - registerLogstashClusterRoutes(server); }, }); diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js b/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js index 43ca656e0827c..5e430ccbd8ceb 100644 --- a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js +++ b/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js @@ -13,7 +13,7 @@ import 'brace/mode/plain_text'; import 'brace/theme/github'; import { isEmpty } from 'lodash'; -import { TOOLTIPS } from '../../../common/constants/tooltips'; +import { TOOLTIPS } from '../../../../../../plugins/logstash/common/constants/tooltips'; import { EuiButton, EuiButtonEmpty, diff --git a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts index e943656120d5e..2e1ee2afb9ce6 100644 --- a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts +++ b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts @@ -10,7 +10,7 @@ import { npSetup } from 'ui/new_platform'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { FeatureCatalogueCategory } from '../../../../../../src/plugins/home/public'; // @ts-ignore -import { PLUGIN } from '../../common/constants'; +import { PLUGIN } from '../../../../../plugins/logstash/common/constants'; const { plugins: { home }, diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js b/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js index 14900fdaa7cdc..06d01a05bac27 100755 --- a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js +++ b/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js @@ -8,7 +8,7 @@ import { pick, capitalize } from 'lodash'; import { getSearchValue } from 'plugins/logstash/lib/get_search_value'; import { getMoment } from 'plugins/logstash/../common/lib/get_moment'; -import { PIPELINE } from '../../../common/constants'; +import { PIPELINE } from '../../../../../../plugins/logstash/common/constants'; /** * Represents the model for listing pipelines in the UI diff --git a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js b/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js index 4bad4f48cc61d..e89c2fe7d11bf 100755 --- a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; import { Cluster } from 'plugins/logstash/models/cluster'; export class ClusterService { diff --git a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js b/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js index 97b336ec0728b..69cc8614a6ae2 100755 --- a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js @@ -7,7 +7,7 @@ import React from 'react'; import { toastNotifications } from 'ui/notify'; import { MarkdownSimple } from '../../../../../../../src/plugins/kibana_react/public'; -import { PLUGIN } from '../../../common/constants'; +import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; export class LogstashLicenseService { constructor(xpackInfoService, kbnUrlService, $timeout) { diff --git a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js b/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js index 8a267e38db738..6103e730c2171 100755 --- a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js @@ -6,7 +6,7 @@ import moment from 'moment'; import chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../common/constants'; +import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; export class MonitoringService { diff --git a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js index 0696bf9d83256..b5d0dbeb852d5 100755 --- a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; import { Pipeline } from 'plugins/logstash/models/pipeline'; export class PipelineService { diff --git a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js index 5a43cf07eba41..d70c8be06fde4 100755 --- a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../common/constants'; +import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; const RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY = 'xpack.logstash.recentlyDeletedPipelines'; diff --git a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js index 7870a495d07a3..2019bdc1bf1aa 100755 --- a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; export class UpgradeService { constructor($http) { diff --git a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100755 index 8dc09d394e973..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithRequest = once(server => { - const cluster = server.plugins.elasticsearch.createCluster('logstash'); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100755 index f9c102be7a1ff..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100755 index cab25cd0b1b10..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return invalid permissions message for 403 errors', () => { - const securityError = new Error('I am an error'); - securityError.statusCode = 403; - const wrappedError = wrapEsError(securityError); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be( - 'Insufficient user permissions for managing Logstash pipelines' - ); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100755 index 85e0b2b3033ad..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100755 index 3295113d38ee5..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100755 index 41819179bde55..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { i18n } from '@kbn/i18n'; - -/** - * Wraps ES errors into a Boom error response and returns it - * This also handles the permissions issue gracefully - * - * @param err Object ES error - * @return Object Boom error response - */ -export function wrapEsError(err) { - const statusCode = err.statusCode; - if (statusCode === 403) { - return Boom.forbidden( - i18n.translate('xpack.logstash.insufficientUserPermissionsDescription', { - defaultMessage: 'Insufficient user permissions for managing Logstash pipelines', - }) - ); - } - return Boom.boomify(err, { statusCode: err.statusCode }); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100755 index ffd915c513362..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js b/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js deleted file mode 100755 index b1593fb1ba355..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { fetchAllFromScroll } from '../fetch_all_from_scroll'; -import { set } from 'lodash'; - -describe('fetch_all_from_scroll', () => { - let mockResponse; - let stubCallWithRequest; - - beforeEach(() => { - mockResponse = {}; - - stubCallWithRequest = sinon.stub(); - stubCallWithRequest.onCall(0).returns( - new Promise(resolve => { - const mockInnerResponse = { - hits: { - hits: ['newhit'], - }, - _scroll_id: 'newScrollId', - }; - return resolve(mockInnerResponse); - }) - ); - - stubCallWithRequest.onCall(1).returns( - new Promise(resolve => { - const mockInnerResponse = { - hits: { - hits: [], - }, - }; - return resolve(mockInnerResponse); - }) - ); - }); - - describe('#fetchAllFromScroll', () => { - describe('when the passed-in response has no hits', () => { - beforeEach(() => { - set(mockResponse, 'hits.hits', []); - }); - - it('should return an empty array of hits', () => { - return fetchAllFromScroll(mockResponse).then(hits => { - expect(hits).to.eql([]); - }); - }); - - it('should not call callWithRequest', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(() => { - expect(stubCallWithRequest.called).to.be(false); - }); - }); - }); - - describe('when the passed-in response has some hits', () => { - beforeEach(() => { - set(mockResponse, 'hits.hits', ['foo', 'bar']); - set(mockResponse, '_scroll_id', 'originalScrollId'); - }); - - it('should return the hits from the response', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(hits => { - expect(hits).to.eql(['foo', 'bar', 'newhit']); - }); - }); - - it('should call callWithRequest', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(() => { - expect(stubCallWithRequest.calledTwice).to.be(true); - - const firstCallWithRequestCallArgs = stubCallWithRequest.args[0]; - expect(firstCallWithRequestCallArgs[1].body.scroll_id).to.eql('originalScrollId'); - - const secondCallWithRequestCallArgs = stubCallWithRequest.args[1]; - expect(secondCallWithRequestCallArgs[1].body.scroll_id).to.eql('newScrollId'); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js b/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js deleted file mode 100755 index 835ef0090a5d2..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import { ES_SCROLL_SETTINGS } from '../../../common/constants'; - -export function fetchAllFromScroll(response, callWithRequest, hits = []) { - const newHits = get(response, 'hits.hits', []); - const scrollId = get(response, '_scroll_id'); - - if (newHits.length > 0) { - hits.push(...newHits); - - return callWithRequest('scroll', { - body: { - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - scroll_id: scrollId, - }, - }).then(innerResponse => { - return fetchAllFromScroll(innerResponse, callWithRequest, hits); - }); - } - - return Promise.resolve(hits); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100755 index 1dc1df922acf7..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#logstashFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false, - }; - }); - - it('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException(response => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true, - }; - }); - - it('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - const response = licensePreRouting(stubRequest); - expect(response).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100755 index 05402a56a52d8..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; - -export const licensePreRoutingFactory = once(server => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - - return null; - } - - return licensePreRouting; -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js index 8a17fb2eea497..a0d06e77b410d 100755 --- a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js +++ b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js @@ -6,7 +6,7 @@ import { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common/constants'; +import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; export function registerLicenseChecker(server) { const xpackMainPlugin = server.plugins.xpack_main; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js b/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js deleted file mode 100755 index 86e18b02ddce2..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerLoadRoute } from './register_load_route'; - -export function registerLogstashClusterRoutes(server) { - registerLoadRoute(server); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js deleted file mode 100755 index 663b60cc8c1d1..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Cluster } from '../../../models/cluster'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchCluster(callWithRequest) { - return callWithRequest('info'); -} - -export function registerLoadRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/cluster', - method: 'GET', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchCluster(callWithRequest) - .then(responseFromES => ({ - cluster: Cluster.fromUpstreamJSON(responseFromES).downstreamJSON, - })) - .catch(e => { - if (e.status === 403) { - return h.response(); - } - throw Boom.internal(e); - }); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js deleted file mode 100755 index 232ee4207541c..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapEsError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deletePipeline(callWithRequest, pipelineId) { - return callWithRequest('delete', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - refresh: 'wait_for', - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'DELETE', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - return deletePipeline(callWithRequest, pipelineId) - .then(() => h.response().code(204)) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js deleted file mode 100755 index 796bf939d747f..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Pipeline } from '../../../models/pipeline'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchPipeline(callWithRequest, pipelineId) { - return callWithRequest('get', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - _source: ['description', 'username', 'pipeline', 'pipeline_settings'], - ignore: [404], - }); -} - -export function registerLoadRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'GET', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - return fetchPipeline(callWithRequest, pipelineId) - .then(pipelineResponseFromES => { - if (!pipelineResponseFromES.found) { - throw Boom.notFound(); - } - - const pipeline = Pipeline.fromUpstreamJSON(pipelineResponseFromES); - return pipeline.downstreamJSON; - }) - .catch(e => Boom.boomify(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js deleted file mode 100755 index 9966cd2ca2139..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerLoadRoute } from './register_load_route'; -import { registerDeleteRoute } from './register_delete_route'; -import { registerSaveRoute } from './register_save_route'; - -export function registerLogstashPipelineRoutes(server) { - registerLoadRoute(server); - registerDeleteRoute(server); - registerSaveRoute(server); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js deleted file mode 100755 index 50f62dc0a0ddd..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import { wrapEsError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Pipeline } from '../../../models/pipeline'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function savePipeline(callWithRequest, pipelineId, pipelineBody) { - return callWithRequest('index', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - body: pipelineBody, - refresh: 'wait_for', - }); -} - -export function registerSaveRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'PUT', - handler: async (request, h) => { - let username; - if (server.plugins.security) { - const user = await server.plugins.security.getUser(request); - username = get(user, 'username'); - } - - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - const pipeline = Pipeline.fromDownstreamJSON(request.payload, pipelineId, username); - return savePipeline(callWithRequest, pipeline.id, pipeline.upstreamJSON) - .then(() => h.response().code(204)) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js deleted file mode 100755 index db275b5a3ea79..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLogstashPipelinesRoutes } from './register_pipelines_routes'; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js deleted file mode 100755 index 8ccd792d5a876..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deletePipelines(callWithRequest, pipelineIds) { - const deletePromises = pipelineIds.map(pipelineId => { - return callWithRequest('delete', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - refresh: 'wait_for', - }) - .then(success => ({ success })) - .catch(error => ({ error })); - }); - - return Promise.all(deletePromises).then(results => { - const successes = results.filter(result => Boolean(result.success)); - const errors = results.filter(result => Boolean(result.error)); - - return { - numSuccesses: successes.length, - numErrors: errors.length, - }; - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipelines/delete', - method: 'POST', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - - return deletePipelines(callWithRequest, request.payload.pipelineIds) - .then(results => ({ results })) - .catch(err => wrapUnknownError(err)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js deleted file mode 100755 index 43ce1c3e8f6f6..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { wrapEsError } from '../../../lib/error_wrappers'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { PipelineListItem } from '../../../models/pipeline_list_item'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchPipelines(callWithRequest) { - const params = { - index: INDEX_NAMES.PIPELINES, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - }, - ignore: [404], - }; - - return callWithRequest('search', params).then(response => - fetchAllFromScroll(response, callWithRequest) - ); -} - -export function registerListRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipelines', - method: 'GET', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchPipelines(callWithRequest) - .then((pipelinesHits = []) => { - const pipelines = pipelinesHits.map(pipeline => { - return PipelineListItem.fromUpstreamJSON(pipeline).downstreamJSON; - }); - - return { pipelines }; - }) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js deleted file mode 100755 index 6d25f3acb9bf9..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerListRoute } from './register_list_route'; -import { registerDeleteRoute } from './register_delete_route'; - -export function registerLogstashPipelinesRoutes(server) { - registerListRoute(server); - registerDeleteRoute(server); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js b/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js deleted file mode 100755 index d616349dd6566..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLogstashUpgradeRoutes } from './register_upgrade_routes'; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js deleted file mode 100755 index 16f97930ae25e..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function doesIndexExist(callWithRequest) { - return callWithRequest('indices.exists', { - index: INDEX_NAMES.PIPELINES, - }); -} - -async function executeUpgrade(callWithRequest) { - // If index doesn't exist yet, there is no mapping to upgrade - if (!(await doesIndexExist(callWithRequest))) { - return; - } - - return callWithRequest('indices.putMapping', { - index: INDEX_NAMES.PIPELINES, - body: { - properties: { - pipeline_settings: { - dynamic: false, - type: 'object', - }, - }, - }, - }); -} - -export function registerExecuteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/upgrade', - method: 'POST', - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - try { - await executeUpgrade(callWithRequest); - return { is_upgraded: true }; - } catch (err) { - throw wrapUnknownError(err); - } - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js b/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js deleted file mode 100755 index a198f82613e37..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerExecuteRoute } from './register_execute_route'; - -export function registerLogstashUpgradeRoutes(server) { - registerExecuteRoute(server); -} diff --git a/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.js b/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.js index 94f4018bbdbb7..091cfd8605cb6 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.js +++ b/x-pack/legacy/plugins/maps/common/migrations/ems_raster_tile_to_ems_vector_tile.js @@ -5,11 +5,11 @@ */ import _ from 'lodash'; -import { EMS_TMS, LAYER_TYPE } from '../constants'; +import { SOURCE_TYPES, LAYER_TYPE } from '../constants'; function isEmsTileSource(layerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return sourceType === EMS_TMS; + return sourceType === SOURCE_TYPES.EMS_TMS; } function isTileLayer(layerDescriptor) { diff --git a/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.js b/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.js index 490e760d8c003..0d6b0052d2b0d 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.js +++ b/x-pack/legacy/plugins/maps/common/migrations/move_apply_global_query.js @@ -5,11 +5,13 @@ */ import _ from 'lodash'; -import { ES_GEO_GRID, ES_PEW_PEW, ES_SEARCH } from '../constants'; +import { SOURCE_TYPES } from '../constants'; function isEsSource(layerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return [ES_GEO_GRID, ES_PEW_PEW, ES_SEARCH].includes(sourceType); + return [SOURCE_TYPES.ES_GEO_GRID, SOURCE_TYPES.ES_PEW_PEW, SOURCE_TYPES.ES_SEARCH].includes( + sourceType + ); } // Migration to move applyGlobalQuery from layer to sources. diff --git a/x-pack/legacy/plugins/maps/common/migrations/references.js b/x-pack/legacy/plugins/maps/common/migrations/references.js index a96af700da37c..3980705fd7cfa 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/references.js +++ b/x-pack/legacy/plugins/maps/common/migrations/references.js @@ -7,11 +7,15 @@ // Can not use public Layer classes to extract references since this logic must run in both client and server. import _ from 'lodash'; -import { ES_GEO_GRID, ES_SEARCH, ES_PEW_PEW } from '../constants'; +import { SOURCE_TYPES } from '../constants'; function doesSourceUseIndexPattern(layerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return sourceType === ES_GEO_GRID || sourceType === ES_SEARCH || sourceType === ES_PEW_PEW; + return ( + sourceType === SOURCE_TYPES.ES_GEO_GRID || + sourceType === SOURCE_TYPES.ES_SEARCH || + sourceType === SOURCE_TYPES.ES_PEW_PEW + ); } export function extractReferences({ attributes, references = [] }) { diff --git a/x-pack/legacy/plugins/maps/common/migrations/references.test.js b/x-pack/legacy/plugins/maps/common/migrations/references.test.js index 40f6fd72a48d7..50a45c81339dc 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/references.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/references.test.js @@ -5,16 +5,16 @@ */ import { extractReferences, injectReferences } from './references'; -import { ES_GEO_GRID, ES_SEARCH, ES_PEW_PEW } from '../constants'; +import { SOURCE_TYPES } from '../constants'; const layerListJSON = { esSearchSource: { - withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_SEARCH}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, - withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_SEARCH}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, + withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_SEARCH}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, + withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_SEARCH}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, }, esGeoGridSource: { - withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_GEO_GRID}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, - withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_GEO_GRID}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, + withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_GEO_GRID}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, + withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_GEO_GRID}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, }, join: { withIndexPatternId: @@ -23,8 +23,8 @@ const layerListJSON = { '[{"joins":[{"right":{"indexPatternRefName":"layer_0_join_0_index_pattern"}}]}]', }, pewPewSource: { - withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_PEW_PEW}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, - withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_PEW_PEW}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, + withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_PEW_PEW}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, + withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${SOURCE_TYPES.ES_PEW_PEW}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, }, }; diff --git a/x-pack/legacy/plugins/maps/common/migrations/scaling_type.ts b/x-pack/legacy/plugins/maps/common/migrations/scaling_type.ts index 5823ddd6b42e3..551975fbacea5 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/scaling_type.ts +++ b/x-pack/legacy/plugins/maps/common/migrations/scaling_type.ts @@ -5,13 +5,13 @@ */ import _ from 'lodash'; -import { ES_SEARCH, SCALING_TYPES } from '../constants'; +import { SOURCE_TYPES, SCALING_TYPES } from '../constants'; import { LayerDescriptor, ESSearchSourceDescriptor } from '../descriptor_types'; import { MapSavedObjectAttributes } from '../../../../../plugins/maps/common/map_saved_object_type'; function isEsDocumentSource(layerDescriptor: LayerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return sourceType === ES_SEARCH; + return sourceType === SOURCE_TYPES.ES_SEARCH; } export function migrateUseTopHitsToScalingType({ diff --git a/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.js b/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.js index 7392dfa71bf3a..055c867486f6c 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.js +++ b/x-pack/legacy/plugins/maps/common/migrations/top_hits_time_to_sort.js @@ -5,11 +5,11 @@ */ import _ from 'lodash'; -import { ES_SEARCH, SORT_ORDER } from '../constants'; +import { SOURCE_TYPES, SORT_ORDER } from '../constants'; function isEsDocumentSource(layerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return sourceType === ES_SEARCH; + return sourceType === SOURCE_TYPES.ES_SEARCH; } export function topHitsTimeToSort({ attributes }) { diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index aa55cf0808ef2..7bfbf5761c5b8 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -125,9 +125,21 @@ async function syncDataForAllLayers(dispatch, getState, dataFilters) { export function cancelAllInFlightRequests() { return (dispatch, getState) => { getLayerList(getState()).forEach(layer => { - layer.getInFlightRequestTokens().forEach(requestToken => { - dispatch(cancelRequest(requestToken)); - }); + dispatch(clearDataRequests(layer)); + }); + }; +} + +function clearDataRequests(layer) { + return dispatch => { + layer.getInFlightRequestTokens().forEach(requestToken => { + dispatch(cancelRequest(requestToken)); + }); + dispatch({ + type: UPDATE_LAYER_PROP, + id: layer.getId(), + propName: '__dataRequests', + newValue: [], }); }; } @@ -663,13 +675,31 @@ export function updateSourceProp(layerId, propName, value, newLayerType) { layerId, propName, value, - newLayerType, }); + if (newLayerType) { + dispatch(updateLayerType(layerId, newLayerType)); + } await dispatch(clearMissingStyleProperties(layerId)); dispatch(syncDataForLayer(layerId)); }; } +function updateLayerType(layerId, newLayerType) { + return (dispatch, getState) => { + const layer = getLayerById(layerId, getState()); + if (!layer || layer.getType() === newLayerType) { + return; + } + dispatch(clearDataRequests(layer)); + dispatch({ + type: UPDATE_LAYER_PROP, + id: layerId, + propName: 'type', + newValue: newLayerType, + }); + }; +} + export function syncDataForLayer(layerId) { return async (dispatch, getState) => { const targetLayer = getLayerById(layerId, getState()); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js index b3ad3d1df68ce..256f52112ba97 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js @@ -12,7 +12,7 @@ import { fitToLayerExtent, updateSourceProp } from '../../actions/map_actions'; function mapStateToProps(state = {}) { const selectedLayer = getSelectedLayer(state); return { - key: selectedLayer ? selectedLayer.getId() : '', + key: selectedLayer ? `${selectedLayer.getId()}${selectedLayer.isJoinable()}` : '', selectedLayer, }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index df2988d399c5b..cc0e665525036 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -12,7 +12,6 @@ import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; import { DrawCircle } from './draw_circle'; import { createDistanceFilterWithMeta, - createSpatialFilterWithBoundingBox, createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, @@ -84,23 +83,17 @@ export class DrawControl extends React.Component { roundCoordinates(geometry.coordinates); try { - const options = { + const filter = createSpatialFilterWithGeometry({ + geometry: + this.props.drawState.drawType === DRAW_TYPE.BOUNDS + ? getBoundingBoxGeometry(geometry) + : geometry, indexPatternId: this.props.drawState.indexPatternId, geoFieldName: this.props.drawState.geoFieldName, geoFieldType: this.props.drawState.geoFieldType, geometryLabel: this.props.drawState.geometryLabel, relation: this.props.drawState.relation, - }; - const filter = - this.props.drawState.drawType === DRAW_TYPE.BOUNDS - ? createSpatialFilterWithBoundingBox({ - ...options, - geometry: getBoundingBoxGeometry(geometry), - }) - : createSpatialFilterWithGeometry({ - ...options, - geometry, - }); + }); this.props.addFilters([filter]); } catch (error) { // TODO notify user why filter was not created diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 5657e14622e9b..27c0211446e85 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -12,7 +12,7 @@ import { } from 'src/core/server'; import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; import { - EMS_FILE, + SOURCE_TYPES, ES_GEO_FIELD_TYPE, MAP_SAVED_OBJECT_TYPE, TELEMETRY_TYPE, @@ -87,6 +87,8 @@ export function buildMapsTelemetry({ const mapsCount = layerLists.length; const dataSourcesCount = layerLists.map(lList => { + // todo: not every source-descriptor has an id + // @ts-ignore const sourceIdList = lList.map((layer: LayerDescriptor) => layer.sourceDescriptor.id); return _.uniq(sourceIdList).length; }); @@ -98,7 +100,7 @@ export function buildMapsTelemetry({ const emsLayersCount = layerLists.map(lList => _(lList) .countBy((layer: LayerDescriptor) => { - const isEmsFile = _.get(layer, 'sourceDescriptor.type') === EMS_FILE; + const isEmsFile = _.get(layer, 'sourceDescriptor.type') === SOURCE_TYPES.EMS_FILE; return isEmsFile && _.get(layer, 'sourceDescriptor.id'); }) .pick((val, key) => key !== 'false') diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap index eb1c65c6a696d..4a7537166bd8a 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap @@ -161,7 +161,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1 "children":

{ ``` -Note that loading and unloading data takes a signifcant amount of time so try to minimize the use of it when possible. +Note that loading and unloading data take a significant amount of time, so try to minimize their use. -### Current sets of data +### Current archives -The current sets of data can be found in: `x-pack/test/siem_cypress/es_archives` folder. +The current archives can be found in `x-pack/test/siem_cypress/es_archives/`. - auditbeat - Auditbeat data generated in Sep, 2019 with the following hosts present: @@ -197,9 +187,9 @@ The current sets of data can be found in: `x-pack/test/siem_cypress/es_archives` - signals - Set of data with 108 opened signals linked to "Signals test" custom rule. -### How to generate new test data +### How to generate a new archive -We are using es_archiver in order to generate the data that our Cypress tests needs. +We are using es_archiver in order to manage the data that our Cypress tests needs. 1. Setup if possible a clean instance of kibana and elasticsearch (if not, possible please try to clean the data that you are going to generate). 2. With the kibana and elasticsearch instance up and running, create the data that you need for your test. diff --git a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts index 2d2db9e70255b..ce6a49b675ef1 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts @@ -7,11 +7,16 @@ import { FIFTH_RULE, FIRST_RULE, RULE_NAME, + RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, } from '../screens/signal_detection_rules'; -import { goToManageSignalDetectionRules } from '../tasks/detections'; +import { + goToManageSignalDetectionRules, + waitForSignalsPanelToBeLoaded, + waitForSignalsIndexToBeCreated, +} from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { @@ -32,8 +37,10 @@ describe('Signal detection rules', () => { esArchiverUnload('prebuilt_rules_loaded'); }); - it.skip('Sorts by activated rules', () => { + it('Sorts by activated rules', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForSignalsPanelToBeLoaded(); + waitForSignalsIndexToBeCreated(); goToManageSignalDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); cy.get(RULE_NAME) @@ -52,10 +59,24 @@ describe('Signal detection rules', () => { cy.get(RULE_NAME) .eq(FIRST_RULE) - .should('have.text', fifthRuleName); - cy.get(RULE_NAME) + .invoke('text') + .then(firstRuleName => { + cy.get(RULE_NAME) + .eq(SECOND_RULE) + .invoke('text') + .then(secondRuleName => { + const expectedRulesNames = `${firstRuleName} ${secondRuleName}`; + cy.wrap(expectedRulesNames).should('include', fifthRuleName); + cy.wrap(expectedRulesNames).should('include', seventhRuleName); + }); + }); + + cy.get(RULE_SWITCH) + .eq(FIRST_RULE) + .should('have.attr', 'role', 'switch'); + cy.get(RULE_SWITCH) .eq(SECOND_RULE) - .should('have.text', seventhRuleName); + .should('have.attr', 'role', 'switch'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx index 05d8f97bb8849..dd608babef48f 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx @@ -17,7 +17,7 @@ export interface OwnProps { start: number; } -const ALERTS_TABLE_ID = 'timeline-alerts-table'; +const ALERTS_TABLE_ID = 'alerts-table'; const defaultAlertsFilters: Filter[] = [ { meta: { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts index e8b267122f86f..8c96e0b75a136 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts @@ -13,6 +13,7 @@ import { LayerMappingDetails, } from './types'; import * as i18n from './translations'; +import { SOURCE_TYPES } from '../../../../../../plugins/maps/common/constants'; const euiVisColorPalette = euiPaletteColorBlind(); // Update field mappings to modify what fields will be returned to map tooltip @@ -101,7 +102,7 @@ export const lmc: LayerMappingCollection = { export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { return [ { - sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true }, + sourceDescriptor: { type: SOURCE_TYPES.EMS_TMS, isAutoSelect: true }, id: uuid.v4(), label: null, minZoom: 0, @@ -260,7 +261,7 @@ export const getLineLayer = ( layerDetails: LayerMapping ) => ({ sourceDescriptor: { - type: 'ES_PEW_PEW', + type: SOURCE_TYPES.ES_PEW_PEW, applyGlobalQuery: true, id: uuid.v4(), indexPatternId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json index fe248a6c1e23e..41f38173dba33 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json @@ -20,5 +20,6 @@ "ML" ], "type": "machine_learning", + "note": "### Investigating Unusual Network Activity ###\nSignals from this rule indicate the presence of network activity from a Linux process for which network activity is rare and unusual. Here are some possible avenues of investigation:\n- Consider the IP addresses and ports. Are these used by normal but infrequent network workflows? Are they expected or unexpected? \n- If the destination IP address is remote or external, does it associate with an expected domain, organization or geography? Note: avoid interacting directly with suspected malicious IP addresses.\n- Consider the user as identified by the username field. Is this network activity part of an expected workflow for the user who ran the program?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business or maintenance process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json index d15c4fc794378..103171bcdfe50 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json @@ -20,5 +20,6 @@ "ML" ], "type": "machine_learning", + "note": "### Investigating an Unusual Linux Process ###\nSignals from this rule indicate the presence of a Linux process that is rare and unusual for all of the monitored Linux hosts for which Auditbeat data is available. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json index 2f33948b0a93e..6642bb5d73fbd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json @@ -20,5 +20,6 @@ "ML" ], "type": "machine_learning", + "note": "### Investigating an Unusual Linux User ###\nSignals from this rule indicate activity for a Linux user name that is rare and unusual. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host? Could this be related to troubleshooting or debugging activity by a developer or site reliability engineer?\n- Examine the history of user activity. If this user manifested only very recently, it might be a service account for a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user is performing.", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json index f071677ae8d33..8ae1b84aaf199 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json @@ -20,5 +20,6 @@ "ML" ], "type": "machine_learning", + "note": "### Investigating an Unusual Linux Process ###\nSignals from this rule indicate the presence of a Linux process that is rare and unusual for the host it ran on. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json index 5e0050c6c25ec..879cee388f5dd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json @@ -20,5 +20,6 @@ "Windows" ], "type": "machine_learning", + "note": "### Investigating an Unusual Windows Process ###\nSignals from this rule indicate the presence of a Windows process that is rare and unusual for the host it ran on. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package. \n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json index ca18fe95b1fc1..1092bcb20bcc3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json @@ -20,5 +20,6 @@ "Windows" ], "type": "machine_learning", + "note": "### Investigating Unusual Network Activity ###\nSignals from this rule indicate the presence of network activity from a Windows process for which network activity is very unusual. Here are some possible avenues of investigation:\n- Consider the IP addresses, protocol and ports. Are these used by normal but infrequent network workflows? Are they expected or unexpected? \n- If the destination IP address is remote or external, does it associate with an expected domain, organization or geography? Note: avoid interacting directly with suspected malicious IP addresses.\n- Consider the user as identified by the username field. Is this network activity part of an expected workflow for the user who ran the program?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json index 1229c4a52b97d..f9adfeb830618 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json @@ -20,5 +20,6 @@ "Windows" ], "type": "machine_learning", + "note": "### Investigating an Unusual Windows Process ###\nSignals from this rule indicate the presence of a Windows process that is rare and unusual for all of the Windows hosts for which Winlogbeat data is available. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package. \n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json index 703dc1a1dc633..a0c6ff5c938f1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json @@ -20,5 +20,6 @@ "Windows" ], "type": "machine_learning", + "note": "### Investigating an Unusual Windows User ###\nSignals from this rule indicate activity for a Windows user name that is rare and unusual. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host? Could this be related to occasional troubleshooting or support activity?\n- Examine the history of user activity. If this user manifested only very recently, it might be a service account for a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json index 946cdb95b8e70..7318364c3aac2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json @@ -20,5 +20,6 @@ "Windows" ], "type": "machine_learning", + "note": "### Investigating an Unusual Windows User ###\nSignals from this rule indicate activity for a rare and unusual Windows RDP (remote desktop) user. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is the user part of a group who normally logs into Windows hosts using RDP (remote desktop protocol)? Is this logon activity part of an expected workflow for the user? \n- Consider the source of the login. If the source is remote, could this be related to occasional troubleshooting or support activity by a vendor or an employee working remotely?", "version": 1 -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/tilemap/public/vis_type_enhancers/update_tilemap_settings.js b/x-pack/legacy/plugins/tilemap/public/vis_type_enhancers/update_tilemap_settings.js index 45764016f0311..294bc31e3893e 100644 --- a/x-pack/legacy/plugins/tilemap/public/vis_type_enhancers/update_tilemap_settings.js +++ b/x-pack/legacy/plugins/tilemap/public/vis_type_enhancers/update_tilemap_settings.js @@ -4,20 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import uiRoutes from 'ui/routes'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import 'ui/vis/map/service_settings'; +import { npSetup } from 'ui/new_platform'; -uiRoutes.addSetupWork(function($injector, serviceSettings) { - const tileMapPluginInfo = xpackInfo.get('features.tilemap'); +const tileMapPluginInfo = xpackInfo.get('features.tilemap'); - if (!tileMapPluginInfo) { - return; - } - - if (!tileMapPluginInfo.license.active || !tileMapPluginInfo.license.valid) { - return; - } +if (tileMapPluginInfo && (tileMapPluginInfo.license.active || tileMapPluginInfo.license.valid)) { + const { serviceSettings } = npSetup.plugins.mapsLegacy; serviceSettings.addQueryParams({ license: tileMapPluginInfo.license.uid }); serviceSettings.disableZoomMessage(); -}); +} diff --git a/x-pack/legacy/plugins/uptime/common/graphql/types.ts b/x-pack/legacy/plugins/uptime/common/graphql/types.ts index c8beb91d807d5..506966ec6b5c9 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/types.ts +++ b/x-pack/legacy/plugins/uptime/common/graphql/types.ts @@ -15,294 +15,15 @@ export type UnsignedInteger = any; // ==================================================== export interface Query { - /** Get a list of all recorded pings for all monitors */ - allPings: PingResults; - /** Fetches the current state of Uptime monitors for the given parameters. */ getMonitorStates?: MonitorSummaryResult | null; } -export interface PingResults { - /** Total number of matching pings */ - total: UnsignedInteger; - /** Unique list of all locations the query matched */ - locations: string[]; - /** List of pings */ - pings: Ping[]; -} -/** A request sent from a monitor to a host */ -export interface Ping { - /** unique ID for this ping */ - id: string; - /** The timestamp of the ping's creation */ - timestamp: string; - /** The agent that recorded the ping */ - beat?: Beat | null; - - container?: Container | null; - - docker?: Docker | null; - - ecs?: Ecs | null; - - error?: Error | null; - - host?: Host | null; - - http?: Http | null; - - icmp?: Icmp | null; - - kubernetes?: Kubernetes | null; - - meta?: Meta | null; - - monitor?: Monitor | null; - - observer?: Observer | null; - - resolve?: Resolve | null; - - socks5?: Socks5 | null; - - summary?: Summary | null; - - tags?: string | null; - - tcp?: Tcp | null; - - tls?: PingTls | null; - - url?: Url | null; -} -/** An agent for recording a beat */ -export interface Beat { - hostname?: string | null; - - name?: string | null; - - timezone?: string | null; - - type?: string | null; -} - -export interface Container { - id?: string | null; - - image?: ContainerImage | null; - - name?: string | null; - - runtime?: string | null; -} - -export interface ContainerImage { - name?: string | null; - - tag?: string | null; -} - -export interface Docker { - id?: string | null; - - image?: string | null; - - name?: string | null; -} - -export interface Ecs { - version?: string | null; -} - -export interface Error { - code?: number | null; - - message?: string | null; - - type?: string | null; -} - -export interface Host { - architecture?: string | null; - - id?: string | null; - - hostname?: string | null; - - ip?: string | null; - - mac?: string | null; - - name?: string | null; - - os?: Os | null; -} - -export interface Os { - family?: string | null; - - kernel?: string | null; - - platform?: string | null; - - version?: string | null; - - name?: string | null; - - build?: string | null; -} - -export interface Http { - response?: HttpResponse | null; - - rtt?: HttpRtt | null; - - url?: string | null; -} - -export interface HttpResponse { - status_code?: UnsignedInteger | null; - - body?: HttpBody | null; -} - -export interface HttpBody { - /** Size of HTTP response body in bytes */ - bytes?: UnsignedInteger | null; - /** Hash of the HTTP response body */ - hash?: string | null; - /** Response body of the HTTP Response. May be truncated based on client settings. */ - content?: string | null; - /** Byte length of the content string, taking into account multibyte chars. */ - content_bytes?: UnsignedInteger | null; -} - -export interface HttpRtt { - content?: Duration | null; - - response_header?: Duration | null; - - total?: Duration | null; - - validate?: Duration | null; - - validate_body?: Duration | null; - - write_request?: Duration | null; -} /** The monitor's status for a ping */ export interface Duration { us?: UnsignedInteger | null; } -export interface Icmp { - requests?: number | null; - - rtt?: number | null; -} - -export interface Kubernetes { - container?: KubernetesContainer | null; - - namespace?: string | null; - - node?: KubernetesNode | null; - - pod?: KubernetesPod | null; -} - -export interface KubernetesContainer { - image?: string | null; - - name?: string | null; -} - -export interface KubernetesNode { - name?: string | null; -} - -export interface KubernetesPod { - name?: string | null; - - uid?: string | null; -} - -export interface Meta { - cloud?: MetaCloud | null; -} - -export interface MetaCloud { - availability_zone?: string | null; - - instance_id?: string | null; - - instance_name?: string | null; - - machine_type?: string | null; - - project_id?: string | null; - - provider?: string | null; - - region?: string | null; -} - -export interface Monitor { - duration?: Duration | null; - - host?: string | null; - /** The id of the monitor */ - id?: string | null; - /** The IP pinged by the monitor */ - ip?: string | null; - /** The name of the protocol being monitored */ - name?: string | null; - /** The protocol scheme of the monitored host */ - scheme?: string | null; - /** The status of the monitored host */ - status?: string | null; - /** The type of host being monitored */ - type?: string | null; - - check_group?: string | null; -} -/** Metadata added by a proccessor, which is specified in its configuration. */ -export interface Observer { - /** Geolocation data for the agent. */ - geo?: Geo | null; -} -/** Geolocation data added via processors to enrich events. */ -export interface Geo { - /** Name of the city in which the agent is running. */ - city_name?: string | null; - /** The name of the continent on which the agent is running. */ - continent_name?: string | null; - /** ISO designation for the agent's country. */ - country_iso_code?: string | null; - /** The name of the agent's country. */ - country_name?: string | null; - /** The lat/long of the agent. */ - location?: string | null; - /** A name for the host's location, e.g. 'us-east-1' or 'LAX'. */ - name?: string | null; - /** ISO designation of the agent's region. */ - region_iso_code?: string | null; - /** Name of the region hosting the agent. */ - region_name?: string | null; -} - -export interface Resolve { - host?: string | null; - - ip?: string | null; - - rtt?: Duration | null; -} - -export interface Socks5 { - rtt?: Rtt | null; -} - export interface Rtt { connect?: Duration | null; @@ -331,53 +52,10 @@ export interface Location { lon?: number | null; } -export interface Tcp { - port?: number | null; - - rtt?: Rtt | null; -} -/** Contains monitor transmission encryption information. */ -export interface PingTls { - /** The date and time after which the certificate is invalid. */ - certificate_not_valid_after?: string | null; - - certificate_not_valid_before?: string | null; - - certificates?: string | null; - - rtt?: Rtt | null; -} - -export interface Url { - full?: string | null; - - scheme?: string | null; - - domain?: string | null; - - port?: number | null; - - path?: string | null; - - query?: string | null; -} - export interface DocCount { count: UnsignedInteger; } -export interface Snapshot { - counts: SnapshotCount; -} - -export interface SnapshotCount { - up: number; - - down: number; - - total: number; -} - /** The primary object returned for monitor states. */ export interface MonitorSummaryResult { /** Used to go to the next page of results */ @@ -521,24 +199,6 @@ export interface SummaryHistogramPoint { down: number; } -export interface AllPingsQueryArgs { - /** Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'. */ - sort?: string | null; - /** Optional: the number of results to return. */ - size?: number | null; - /** Optional: the monitor ID filter. */ - monitorId?: string | null; - /** Optional: the check status to filter by. */ - status?: string | null; - /** The lower limit of the date range. */ - dateRangeStart: string; - /** The upper limit of the date range. */ - dateRangeEnd: string; - /** Optional: agent location to filter by. */ - location?: string | null; - page?: number; -} - export interface GetMonitorStatesQueryArgs { dateRangeStart: string; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts index 37101b5b46fd2..9018f4acaa320 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts @@ -27,7 +27,13 @@ export const StatesIndexStatusType = t.type({ docCount: t.number, }); +export const DateRangeType = t.type({ + from: t.string, + to: t.string, +}); + export type Summary = t.TypeOf; export type CheckGeo = t.TypeOf; export type Location = t.TypeOf; export type StatesIndexStatus = t.TypeOf; +export type DateRange = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts index 5e3fb2326bdb9..652d60cbe304d 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts @@ -8,5 +8,6 @@ export * from './alerts'; export * from './common'; export * from './monitor'; export * from './overview_filters'; +export * from './ping'; export * from './snapshot'; export * from './dynamic_settings'; diff --git a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts similarity index 77% rename from x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts rename to x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts index 3ae32e15ca55c..2c3b52051be0f 100644 --- a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts @@ -28,3 +28,15 @@ export interface HistogramResult { histogram: HistogramDataPoint[]; interval: string; } + +export interface HistogramQueryResult { + key: number; + key_as_string: string; + doc_count: number; + down: { + doc_count: number; + }; + up: { + doc_count: number; + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/queries/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts similarity index 79% rename from x-pack/legacy/plugins/uptime/public/queries/index.ts rename to x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts index 283382ec1b7ba..a2fc7c1b243ba 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { pingsQuery, pingsQueryString } from './pings_query'; +export * from './histogram'; +export * from './ping'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts new file mode 100644 index 0000000000000..ee14b298f3810 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { DateRangeType } from '../common'; + +export const HttpResponseBodyType = t.partial({ + bytes: t.number, + content: t.string, + content_bytes: t.number, + hash: t.string, +}); + +export type HttpResponseBody = t.TypeOf; + +export const TlsType = t.partial({ + certificate_not_valid_after: t.string, + certificate_not_valid_before: t.string, +}); + +export type Tls = t.TypeOf; + +export const MonitorType = t.intersection([ + t.type({ + duration: t.type({ + us: t.number, + }), + id: t.string, + status: t.string, + type: t.string, + }), + t.partial({ + check_group: t.string, + ip: t.string, + name: t.string, + timespan: t.partial({ + gte: t.string, + lte: t.string, + }), + }), +]); + +export type Monitor = t.TypeOf; + +export const PingType = t.intersection([ + t.type({ + timestamp: t.string, + monitor: MonitorType, + docId: t.string, + }), + t.partial({ + agent: t.intersection([ + t.type({ + ephemeral_id: t.string, + hostname: t.string, + id: t.string, + type: t.string, + version: t.string, + }), + t.partial({ + name: t.string, + }), + ]), + container: t.partial({ + id: t.string, + image: t.partial({ + name: t.string, + tag: t.string, + }), + name: t.string, + runtime: t.string, + }), + ecs: t.partial({ + version: t.string, + }), + error: t.intersection([ + t.partial({ + code: t.string, + id: t.string, + stack_trace: t.string, + type: t.string, + }), + t.type({ + // this is _always_ on the error field + message: t.string, + }), + ]), + http: t.partial({ + request: t.partial({ + body: t.partial({ + bytes: t.number, + content: t.partial({ + text: t.string, + }), + }), + bytes: t.number, + method: t.string, + referrer: t.string, + }), + response: t.partial({ + body: HttpResponseBodyType, + bytes: t.number, + redirects: t.string, + status_code: t.number, + }), + version: t.string, + }), + icmp: t.partial({ + requests: t.number, + rtt: t.partial({ + us: t.number, + }), + }), + kubernetes: t.partial({ + pod: t.partial({ + name: t.string, + uid: t.string, + }), + }), + observer: t.partial({ + geo: t.partial({ + name: t.string, + }), + }), + resolve: t.partial({ + ip: t.string, + rtt: t.partial({ + us: t.number, + }), + }), + summary: t.partial({ + down: t.number, + up: t.number, + }), + tags: t.array(t.string), + tcp: t.partial({ + rtt: t.partial({ + connect: t.partial({ + us: t.number, + }), + }), + }), + tls: TlsType, + // should this be partial? + url: t.partial({ + domain: t.string, + full: t.string, + port: t.number, + scheme: t.string, + }), + }), +]); + +export type Ping = t.TypeOf; + +export const PingsResponseType = t.type({ + total: t.number, + locations: t.array(t.string), + pings: t.array(PingType), +}); + +export type PingsResponse = t.TypeOf; + +export const GetPingsParamsType = t.intersection([ + t.type({ + dateRange: DateRangeType, + }), + t.partial({ + index: t.number, + size: t.number, + location: t.string, + monitorId: t.string, + sort: t.string, + status: t.string, + }), +]); + +export type GetPingsParams = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/types/index.ts b/x-pack/legacy/plugins/uptime/common/types/index.ts index 2c39f2a3b7314..1d0003addd761 100644 --- a/x-pack/legacy/plugins/uptime/common/types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/types/index.ts @@ -41,5 +41,3 @@ export interface MonitorDurationResult { /** The maximum duration value in this chart. */ durationMaxValue: number; } - -export * from './ping/histogram'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/monitor_duration.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/monitor_duration.tsx index 7d1cb08cb8b1c..40480905350af 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/charts/monitor_duration.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/monitor_duration.tsx @@ -6,7 +6,7 @@ import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useUrlParams } from '../../../hooks'; +import { useGetUrlParams } from '../../../hooks'; import { getAnomalyRecordsAction, getMLCapabilitiesAction, @@ -28,13 +28,12 @@ interface Props { } export const DurationChart: React.FC = ({ monitorId }: Props) => { - const [getUrlParams] = useUrlParams(); const { dateRangeStart, dateRangeEnd, absoluteDateRangeStart, absoluteDateRangeEnd, - } = getUrlParams(); + } = useGetUrlParams(); const { durationLines, loading } = useSelector(selectDurationLines); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx index 50f91be4ff09f..cf35dbf4e5206 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx @@ -14,8 +14,8 @@ import { import { getPingHistogram } from '../../../state/actions'; import { selectPingHistogram } from '../../../state/selectors'; import { withResponsiveWrapper, ResponsiveWrapperProps } from '../../higher_order'; -import { GetPingHistogramParams, HistogramResult } from '../../../../common/types'; -import { useUrlParams } from '../../../hooks'; +import { GetPingHistogramParams, HistogramResult } from '../../../../common/runtime_types'; +import { useGetUrlParams } from '../../../hooks'; type Props = ResponsiveWrapperProps & Pick & @@ -30,14 +30,13 @@ const PingHistogramContainer: React.FC = ({ loading, esKuery, }) => { - const [getUrlParams] = useUrlParams(); const { absoluteDateRangeStart, absoluteDateRangeEnd, dateRangeStart: dateStart, dateRangeEnd: dateEnd, statusFilter, - } = getUrlParams(); + } = useGetUrlParams(); useEffect(() => { loadData({ monitorId, dateStart, dateEnd, statusFilter, filters: esKuery }); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx index ac8ff13d1edce..39ead242527f8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx @@ -6,7 +6,7 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; -import { useUrlParams } from '../../../hooks'; +import { useGetUrlParams } from '../../../hooks'; import { AppState } from '../../../state'; import { getSnapshotCountAction } from '../../../state/actions'; import { SnapshotComponent } from '../../functional/snapshot'; @@ -54,8 +54,7 @@ export const Container: React.FC = ({ esKuery, loadSnapshotCount, }: Props) => { - const [getUrlParams] = useUrlParams(); - const { dateRangeStart, dateRangeEnd, statusFilter } = getUrlParams(); + const { dateRangeStart, dateRangeEnd, statusFilter } = useGetUrlParams(); useEffect(() => { loadSnapshotCount({ dateRangeStart, dateRangeEnd, filters: esKuery, statusFilter }); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx index b383a696095a3..55c92e70b6066 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx @@ -4,20 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { indexStatusAction } from '../../../state/actions'; import { indexStatusSelector } from '../../../state/selectors'; import { EmptyStateComponent } from '../../functional/empty_state/empty_state'; +import { UptimeRefreshContext } from '../../../contexts'; export const EmptyState: React.FC = ({ children }) => { const { data, loading, error } = useSelector(indexStatusSelector); + const { lastRefresh } = useContext(UptimeRefreshContext); const dispatch = useDispatch(); useEffect(() => { dispatch(indexStatusAction.get()); - }, [dispatch]); + }, [dispatch, lastRefresh]); return ( = ({ summary, loadMonitorDetails, monitorDetails }) => { const monitorId = summary?.monitor_id; - const [getUrlParams] = useUrlParams(); - const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = useGetUrlParams(); useEffect(() => { loadMonitorDetails({ diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx index dd6f7a89cf9a3..3a96aa7c0275b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx @@ -11,8 +11,8 @@ import { AppState } from '../../../state'; import { monitorLocationsSelector, monitorStatusSelector } from '../../../state/selectors'; import { MonitorStatusBarComponent } from '../../functional/monitor_status_details/monitor_status_bar'; import { getMonitorStatusAction } from '../../../state/actions'; -import { useUrlParams } from '../../../hooks'; -import { Ping } from '../../../../common/graphql/types'; +import { useGetUrlParams } from '../../../hooks'; +import { Ping } from '../../../../common/runtime_types'; import { MonitorLocations } from '../../../../common/runtime_types/monitor'; import { UptimeRefreshContext } from '../../../contexts'; @@ -39,8 +39,7 @@ const Container: React.FC = ({ }: Props) => { const { lastRefresh } = useContext(UptimeRefreshContext); - const [getUrlParams] = useUrlParams(); - const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = useGetUrlParams(); useEffect(() => { loadMonitorStatus({ dateStart, dateEnd, monitorId }); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx index 3ced251dfab8c..9d2e48830fbfe 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx @@ -7,7 +7,7 @@ import React, { useContext, useEffect } from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { useUrlParams } from '../../../hooks'; +import { useGetUrlParams } from '../../../hooks'; import { AppState } from '../../../state'; import { monitorLocationsSelector } from '../../../state/selectors'; import { getMonitorLocationsAction, MonitorLocationsPayload } from '../../../state/actions/monitor'; @@ -36,8 +36,7 @@ export const Container: React.FC = ({ }: Props) => { const { lastRefresh } = useContext(UptimeRefreshContext); - const [getUrlParams] = useUrlParams(); - const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = useGetUrlParams(); useEffect(() => { loadMonitorLocations({ dateStart, dateEnd, monitorId }); diff --git a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts old mode 100755 new mode 100644 similarity index 77% rename from x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js rename to x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts index 787814d87dff9..95ced104e5188 --- a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export { PingList, PingListProps } from './ping_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx new file mode 100644 index 0000000000000..5b32a623495f1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useSelector, useDispatch } from 'react-redux'; +import React, { useContext, useCallback } from 'react'; +import { selectPingList } from '../../../state/selectors'; +import { getPings } from '../../../state/actions'; +import { GetPingsParams } from '../../../../common/runtime_types'; +import { UptimeSettingsContext } from '../../../contexts'; +import { PingListComponent } from '../../functional'; + +export interface PingListProps { + monitorId: string; +} + +export const PingList = (props: PingListProps) => { + const { + lastRefresh, + pings: { + error, + loading, + pingList: { locations, pings, total }, + }, + } = useSelector(selectPingList); + + const { dateRangeStart: drs, dateRangeEnd: dre } = useContext(UptimeSettingsContext); + + const dispatch = useDispatch(); + const getPingsCallback = useCallback((params: GetPingsParams) => dispatch(getPings(params)), [ + dispatch, + ]); + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx index f988afc7fc84d..66e86d6731236 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx @@ -13,7 +13,7 @@ import moment from 'moment'; import { getChartDateLabel } from '../../../lib/helper'; import { ChartWrapper } from './chart_wrapper'; import { UptimeThemeContext } from '../../../contexts'; -import { HistogramResult } from '../../../../common/types'; +import { HistogramResult } from '../../../../common/runtime_types'; import { useUrlParams } from '../../../hooks'; import { ChartEmptyState } from './chart_empty_state'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts index 8d0352e01d40e..d82912a6216e8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts @@ -15,6 +15,6 @@ export { KueryBarComponent } from './kuery_bar/kuery_bar'; export { MonitorCharts } from './monitor_charts'; export { MonitorList } from './monitor_list'; export { OverviewPageParsingErrorCallout } from './overview_page_parsing_error_callout'; -export { PingList } from './ping_list'; +export { PingListComponent } from './ping_list'; export { PingHistogramComponent } from './charts'; export { StatusPanel } from './status_panel'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/most_recent_error.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/most_recent_error.tsx index 036882b49359f..1963a9c852b11 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/most_recent_error.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/most_recent_error.tsx @@ -8,7 +8,7 @@ import { EuiText, EuiSpacer } from '@elastic/eui'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { MonitorPageLink } from '../monitor_page_link'; -import { useUrlParams } from '../../../../hooks'; +import { useGetUrlParams } from '../../../../hooks'; import { stringifyUrlParams } from '../../../../lib/helper/stringify_url_params'; import { MonitorError } from '../../../../../common/runtime_types'; @@ -30,8 +30,7 @@ interface MostRecentErrorProps { } export const MostRecentError = ({ error, monitorId, timestamp }: MostRecentErrorProps) => { - const [getUrlParams] = useUrlParams(); - const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams(); + const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); params.selectedPingStatus = 'down'; const linkParameters = stringifyUrlParams(params, true); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx index 2eae14301fd4d..57ed09cc30ef1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx @@ -9,11 +9,11 @@ import moment from 'moment'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { EuiBadge } from '@elastic/eui'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; -import { PingTls } from '../../../../../common/graphql/types'; +import { Tls } from '../../../../../common/runtime_types'; import { MonitorSSLCertificate } from '../monitor_status_bar'; describe('MonitorStatusBar component', () => { - let monitorTls: PingTls; + let monitorTls: Tls; beforeEach(() => { const dateInTwoMonths = moment() diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx index 0a53eeb89d793..5fd32c808da42 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx @@ -8,7 +8,7 @@ import moment from 'moment'; import React from 'react'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; import { MonitorStatusBarComponent } from '../monitor_status_bar'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; describe('MonitorStatusBar component', () => { let monitorStatus: Ping; @@ -16,7 +16,7 @@ describe('MonitorStatusBar component', () => { beforeEach(() => { monitorStatus = { - id: 'id1', + docId: 'few213kl', timestamp: moment(new Date()) .subtract(15, 'm') .toString(), @@ -24,7 +24,9 @@ describe('MonitorStatusBar component', () => { duration: { us: 1234567, }, + id: 'id1', status: 'up', + type: 'http', }, url: { full: 'https://www.example.com/', diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx index c57348c4ab4cd..d92534aecd175 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx @@ -9,14 +9,13 @@ import moment from 'moment'; import { EuiSpacer, EuiText, EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; - -import { PingTls } from '../../../../../common/graphql/types'; +import { Tls } from '../../../../../common/runtime_types'; interface Props { /** * TLS information coming from monitor in ES heartbeat index */ - tls: PingTls | null | undefined; + tls: Tls | null | undefined; } export const MonitorSSLCertificate = ({ tls }: Props) => { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx index 22e4377944be1..ac3cedc517995 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx @@ -17,7 +17,7 @@ import { import { MonitorSSLCertificate } from './monitor_ssl_certificate'; import * as labels from './translations'; import { StatusByLocations } from './status_by_location'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; import { MonitorLocations } from '../../../../../common/runtime_types'; interface MonitorStatusBarProps { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 2e59ec5e57337..154ab6399452d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -1,384 +1,349 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PingList component renders sorted list without errors 1`] = ` - - - + +

+ +

+ + + + -

- + -

- - - - + + + - - - - - - - - - - - - - - - - - - - - + +
+
+ + + Response code + , + "render": [Function], + }, + Object { + "align": "right", + "isExpander": true, + "render": [Function], + "width": "24px", + }, + ] + } + hasActions={true} + isExpandable={true} + itemId="docId" + itemIdToExpandedRowMap={Object {}} + items={ + Array [ + Object { + "docId": "fewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "align": "right", - "dataType": "number", - "field": "monitor.ip", - "name": "IP", + "monitor": Object { + "duration": Object { + "us": 1430, + }, + "id": "auto-tcp-0X81440A68E839814F", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "align": "right", - "field": "monitor.duration.us", - "name": "Duration", - "render": [Function], + "timestamp": "2019-01-28T17:47:08.078Z", + }, + Object { + "docId": "fewjoo21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "align": "right", - "field": "error.type", - "name": "Error type", - "render": [Function], + "monitor": Object { + "duration": Object { + "us": 1370, + }, + "id": "auto-tcp-0X81440A68E839814D", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "align": "right", - "field": "http.response.status_code", - "name": - Response code - , - "render": [Function], + "timestamp": "2019-01-28T17:47:09.075Z", + }, + Object { + "docId": "fejjio21", + "monitor": Object { + "duration": Object { + "us": 1452, + }, + "id": "auto-tcp-0X81440A68E839814D", + "ip": "127.0.0.1", + "name": "", + "status": "up", + "type": "tcp", }, - Object { - "align": "right", - "isExpander": true, - "render": [Function], - "width": "24px", + "timestamp": "2019-01-28T17:47:06.077Z", + }, + Object { + "docId": "fewzio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - ] - } - hasActions={true} - isExpandable={true} - itemId="id" - itemIdToExpandedRowMap={Object {}} - items={ - Array [ - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", + "monitor": Object { + "duration": Object { + "us": 1094, }, - "http": null, - "id": "id1", - "monitor": Object { - "duration": Object { - "us": 1430, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:08.078Z", + "id": "auto-tcp-0X81440A68E839814E", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id2", - "monitor": Object { - "duration": Object { - "us": 1370, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:09.075Z", + "timestamp": "2019-01-28T17:47:07.075Z", + }, + Object { + "docId": "fewpi321", + "error": Object { + "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", + "type": "io", }, - Object { - "error": null, - "http": null, - "id": "id3", - "monitor": Object { - "duration": Object { - "us": 1452, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "tcp", + "monitor": Object { + "duration": Object { + "us": 1597, }, - "timestamp": "2019-01-28T17:47:06.077Z", + "id": "auto-http-0X3675F89EF061209G", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id4", - "monitor": Object { - "duration": Object { - "us": 1094, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:07.075Z", + "timestamp": "2019-01-28T17:47:07.074Z", + }, + Object { + "docId": "0ewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "error": Object { - "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", - "type": "io", + "monitor": Object { + "duration": Object { + "us": 1699, }, - "http": null, - "id": "id5", - "monitor": Object { - "duration": Object { - "us": 1597, - }, - "id": "auto-http-0X3675F89EF0612091", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http", - }, - "timestamp": "2019-01-28T17:47:07.074Z", + "id": "auto-tcp-0X81440A68E839814H", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id6", - "monitor": Object { - "duration": Object { - "us": 1699, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:18.080Z", + "timestamp": "2019-01-28T17:47:18.080Z", + }, + Object { + "docId": "3ewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id7", - "monitor": Object { - "duration": Object { - "us": 5384, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", + "monitor": Object { + "duration": Object { + "us": 5384, }, - "timestamp": "2019-01-28T17:47:19.076Z", + "id": "auto-tcp-0X81440A68E839814I", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id8", - "monitor": Object { - "duration": Object { - "us": 5397, - }, - "id": "auto-http-0X3675F89EF0612091", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http", + "timestamp": "2019-01-28T17:47:19.076Z", + }, + Object { + "docId": "fewjip21", + "error": Object { + "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", + "type": "io", + }, + "monitor": Object { + "duration": Object { + "us": 5397, }, - "timestamp": "2019-01-28T17:47:19.076Z", + "id": "auto-http-0X3675F89EF061209J", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", }, - Object { - "error": null, - "http": Object { - "response": Object { - "status_code": 200, - }, + "timestamp": "2019-01-28T17:47:19.076Z", + }, + Object { + "docId": "fewjio21", + "http": Object { + "response": Object { + "status_code": 200, }, - "id": "id9", - "monitor": Object { - "duration": Object { - "us": 127511, - }, - "id": "auto-http-0X131221E73F825974", - "ip": "172.217.7.4", - "name": "", - "scheme": null, - "status": "up", - "type": "http", + }, + "monitor": Object { + "duration": Object { + "us": 127511, }, - "timestamp": "2019-01-28T17:47:19.077Z", + "id": "auto-tcp-0X81440A68E839814C", + "ip": "172.217.7.4", + "name": "", + "status": "up", + "type": "http", }, - Object { - "error": null, - "http": Object { - "response": Object { - "status_code": 200, - }, + "timestamp": "2019-01-28T17:47:19.077Z", + }, + Object { + "docId": "fewjik81", + "http": Object { + "response": Object { + "status_code": 200, }, - "id": "id10", - "monitor": Object { - "duration": Object { - "us": 287543, - }, - "id": "auto-http-0X9CB71300ABD5A2A8", - "ip": "192.30.253.112", - "name": "", - "scheme": null, - "status": "up", - "type": "http", + }, + "monitor": Object { + "duration": Object { + "us": 287543, }, - "timestamp": "2019-01-28T17:47:19.077Z", + "id": "auto-http-0X131221E73F825974", + "ip": "192.30.253.112", + "name": "", + "status": "up", + "type": "http", }, - ] - } - loading={false} - noItemsMessage="No items found" - onChange={[Function]} - pagination={ - Object { - "initialPageSize": 25, - "pageIndex": 0, - "pageSize": 10, - "pageSizeOptions": Array [ - 10, - 25, - 50, - 100, - ], - "totalItemCount": 9231, - } + "timestamp": "2019-01-28T17:47:19.077Z", + }, + ] + } + loading={false} + noItemsMessage="No items found" + onChange={[Function]} + pagination={ + Object { + "initialPageSize": 10, + "pageIndex": 0, + "pageSize": 10, + "pageSizeOptions": Array [ + 10, + 25, + 50, + 100, + ], + "totalItemCount": 10, } - responsive={true} - tableLayout="fixed" - /> - - + } + responsive={true} + tableLayout="fixed" + /> + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx index 9dbe48ec5553a..2c1434cfd64bd 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx @@ -7,15 +7,23 @@ import { mountWithIntl, renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { PingListExpandedRowComponent } from '../expanded_row'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; import { DocLinkForBody } from '../doc_link_body'; describe('PingListExpandedRow', () => { let ping: Ping; beforeEach(() => { ping = { - id: '123', + docId: 'fdeio12', timestamp: '19290310', + monitor: { + duration: { + us: 12345, + }, + id: '123', + status: 'down', + type: 'http', + }, http: { response: { body: { @@ -34,7 +42,7 @@ describe('PingListExpandedRow', () => { it('renders error information when an error field is present', () => { ping.error = { - code: 403, + code: '403', message: 'Forbidden', }; expect(shallowWithIntl()).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx index 68d285bd0baf1..ec256a886aa16 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx @@ -6,211 +6,188 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { PingResults, Ping } from '../../../../../common/graphql/types'; -import { PingListComponent, AllLocationOption, toggleDetails } from '../ping_list'; +import { PingListComponent, toggleDetails } from '../ping_list'; import { ExpandedRowMap } from '../../monitor_list/types'; +import { Ping, PingsResponse } from '../../../../../common/runtime_types'; describe('PingList component', () => { - let pingList: { allPings: PingResults }; + let response: PingsResponse; beforeEach(() => { - pingList = { - allPings: { - total: 9231, - pings: [ - { - id: 'id1', - timestamp: '2019-01-28T17:47:08.078Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1430 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id2', - timestamp: '2019-01-28T17:47:09.075Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1370 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id3', - timestamp: '2019-01-28T17:47:06.077Z', - http: null, - error: null, - monitor: { - duration: { us: 1452 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'up', - type: 'tcp', - }, - }, - { - id: 'id4', - timestamp: '2019-01-28T17:47:07.075Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1094 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id5', - timestamp: '2019-01-28T17:47:07.074Z', - http: null, - error: { - message: - 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1597 }, - id: 'auto-http-0X3675F89EF0612091', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'http', - }, - }, - { - id: 'id6', - timestamp: '2019-01-28T17:47:18.080Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1699 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id7', - timestamp: '2019-01-28T17:47:19.076Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 5384 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id8', - timestamp: '2019-01-28T17:47:19.076Z', - http: null, - error: { - message: - 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 5397 }, - id: 'auto-http-0X3675F89EF0612091', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'http', - }, - }, - { - id: 'id9', - timestamp: '2019-01-28T17:47:19.077Z', - http: { response: { status_code: 200 } }, - error: null, - monitor: { - duration: { us: 127511 }, - id: 'auto-http-0X131221E73F825974', - ip: '172.217.7.4', - name: '', - scheme: null, - status: 'up', - type: 'http', - }, - }, - { - id: 'id10', - timestamp: '2019-01-28T17:47:19.077Z', - http: { response: { status_code: 200 } }, - error: null, - monitor: { - duration: { us: 287543 }, - id: 'auto-http-0X9CB71300ABD5A2A8', - ip: '192.30.253.112', - name: '', - scheme: null, - status: 'up', - type: 'http', - }, - }, - ], - locations: ['nyc'], - }, + response = { + total: 9231, + locations: ['nyc'], + pings: [ + { + docId: 'fewjio21', + timestamp: '2019-01-28T17:47:08.078Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1430 }, + id: 'auto-tcp-0X81440A68E839814F', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewjoo21', + timestamp: '2019-01-28T17:47:09.075Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1370 }, + id: 'auto-tcp-0X81440A68E839814D', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fejjio21', + timestamp: '2019-01-28T17:47:06.077Z', + monitor: { + duration: { us: 1452 }, + id: 'auto-tcp-0X81440A68E839814D', + ip: '127.0.0.1', + name: '', + status: 'up', + type: 'tcp', + }, + }, + { + docId: 'fewzio21', + timestamp: '2019-01-28T17:47:07.075Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1094 }, + id: 'auto-tcp-0X81440A68E839814E', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewpi321', + timestamp: '2019-01-28T17:47:07.074Z', + error: { + message: + 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1597 }, + id: 'auto-http-0X3675F89EF061209G', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'http', + }, + }, + { + docId: '0ewjio21', + timestamp: '2019-01-28T17:47:18.080Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1699 }, + id: 'auto-tcp-0X81440A68E839814H', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: '3ewjio21', + timestamp: '2019-01-28T17:47:19.076Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 5384 }, + id: 'auto-tcp-0X81440A68E839814I', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewjip21', + timestamp: '2019-01-28T17:47:19.076Z', + error: { + message: + 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 5397 }, + id: 'auto-http-0X3675F89EF061209J', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'http', + }, + }, + { + docId: 'fewjio21', + timestamp: '2019-01-28T17:47:19.077Z', + http: { response: { status_code: 200 } }, + monitor: { + duration: { us: 127511 }, + id: 'auto-tcp-0X81440A68E839814C', + ip: '172.217.7.4', + name: '', + status: 'up', + type: 'http', + }, + }, + { + docId: 'fewjik81', + timestamp: '2019-01-28T17:47:19.077Z', + http: { response: { status_code: 200 } }, + monitor: { + duration: { us: 287543 }, + id: 'auto-http-0X131221E73F825974', + ip: '192.30.253.112', + name: '', + status: 'up', + type: 'http', + }, + }, + ], }; }); it('renders sorted list without errors', () => { - const { allPings } = pingList; const component = shallowWithIntl( {}} - onSelectedStatusChange={jest.fn()} - pageIndex={0} - pageSize={10} - selectedOption="down" - selectedLocation={AllLocationOption.value} + locations={[]} + monitorId="foo" + pings={response.pings} + total={10} /> ); expect(component).toMatchSnapshot(); @@ -224,13 +201,38 @@ describe('PingList component', () => { beforeEach(() => { itemIdToExpandedRowMap = {}; - pings = pingList.allPings.pings; + pings = response.pings; }); it('should expand an item if empty', () => { const ping = pings[0]; toggleDetails(ping, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); - expect(itemIdToExpandedRowMap).toHaveProperty(ping.id); + expect(itemIdToExpandedRowMap).toMatchInlineSnapshot(` + Object { + "fewjio21": , + } + `); }); it('should un-expand an item if clicked again', () => { @@ -245,7 +247,31 @@ describe('PingList component', () => { const pingB = pings[1]; toggleDetails(pingA, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); toggleDetails(pingB, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); - expect(itemIdToExpandedRowMap).toHaveProperty(pingB.id); + expect(pingA.docId).not.toEqual(pingB.docId); + expect(itemIdToExpandedRowMap[pingB.docId]).toMatchInlineSnapshot(` + + `); }); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx index c684235122e34..28b96fcb1bf7b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx @@ -16,14 +16,14 @@ import { } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Ping, HttpBody } from '../../../../common/graphql/types'; +import { Ping, HttpResponseBody } from '../../../../common/runtime_types'; import { DocLinkForBody } from './doc_link_body'; interface Props { ping: Ping; } -const BodyDescription = ({ body }: { body: HttpBody }) => { +const BodyDescription = ({ body }: { body: HttpResponseBody }) => { const contentBytes = body.content_bytes || 0; const bodyBytes = body.bytes || 0; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx index e57b229dfd973..808f3f90ef015 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './ping_list'; +export { PingListComponent } from './ping_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 19768c7104e91..934dfd961f9e0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -20,109 +20,127 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { get } from 'lodash'; import moment from 'moment'; -import React, { Fragment, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; -import { Ping, PingResults } from '../../../../common/graphql/types'; +import { Ping, GetPingsParams, DateRange } from '../../../../common/runtime_types'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; -import { pingsQuery } from '../../../queries'; import { LocationName } from './location_name'; import { Pagination } from './../monitor_list'; import { PingListExpandedRowComponent } from './expanded_row'; +import { PingListProps } from '../../connected/pings'; -interface PingListQueryResult { - allPings?: PingResults; -} - -interface PingListProps { - onSelectedStatusChange: (status: string | undefined) => void; - onSelectedLocationChange: (location: any) => void; - onPageCountChange: (itemCount: number) => void; - onPageIndexChange: (index: number) => void; - pageSize: number; - pageIndex: number; - selectedOption: string; - selectedLocation: string | undefined; -} - -type Props = UptimeGraphQLQueryProps & PingListProps; -interface ExpandedRowMap { - [key: string]: JSX.Element; -} - -export const AllLocationOption = { text: 'All', value: '' }; +export const AllLocationOption = { + 'data-test-subj': 'xpack.uptime.pingList.locationOptions.all', + text: 'All', + value: '', +}; export const toggleDetails = ( ping: Ping, - itemIdToExpandedRowMap: ExpandedRowMap, - setItemIdToExpandedRowMap: (update: ExpandedRowMap) => any + expandedRows: Record, + setExpandedRows: (update: Record) => any ) => { - // If the user has clicked on the expanded map, close all expanded rows. - if (itemIdToExpandedRowMap[ping.id]) { - setItemIdToExpandedRowMap({}); + // If already expanded, collapse + if (expandedRows[ping.docId]) { + delete expandedRows[ping.docId]; + setExpandedRows({ ...expandedRows }); return; } // Otherwise expand this row - const newItemIdToExpandedRowMap: ExpandedRowMap = {}; - newItemIdToExpandedRowMap[ping.id] = ; - setItemIdToExpandedRowMap(newItemIdToExpandedRowMap); + setExpandedRows({ + ...expandedRows, + [ping.docId]: , + }); }; const SpanWithMargin = styled.span` margin-right: 16px; `; -export const PingListComponent = ({ - data, - loading, - onPageCountChange, - onPageIndexChange, - onSelectedLocationChange, - onSelectedStatusChange, - pageIndex, - pageSize, - selectedOption, - selectedLocation, -}: Props) => { - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); +interface Props extends PingListProps { + dateRange: DateRange; + error?: Error; + getPings: (props: GetPingsParams) => void; + lastRefresh: number; + loading: boolean; + locations: string[]; + pings: Ping[]; + total: number; +} + +const DEFAULT_PAGE_SIZE = 10; + +const statusOptions = [ + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.all', + text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', { + defaultMessage: 'All', + }), + value: '', + }, + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.up', + text: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', { + defaultMessage: 'Up', + }), + value: 'up', + }, + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.down', + text: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', { + defaultMessage: 'Down', + }), + value: 'down', + }, +]; + +export const PingListComponent = (props: Props) => { + const [selectedLocation, setSelectedLocation] = useState(''); + const [status, setStatus] = useState(''); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [pageIndex, setPageIndex] = useState(0); + const { + dateRange: { from, to }, + error, + getPings, + lastRefresh, + loading, + locations, + monitorId, + pings, + total, + } = props; + + useEffect(() => { + getPings({ + dateRange: { + from, + to, + }, + location: selectedLocation, + monitorId, + index: pageIndex, + size: pageSize, + status: status !== 'all' ? status : '', + }); + }, [from, to, getPings, monitorId, lastRefresh, selectedLocation, pageIndex, pageSize, status]); + + const [expandedRows, setExpandedRows] = useState>({}); - const statusOptions = [ - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', { - defaultMessage: 'All', - }), - value: '', - }, - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', { - defaultMessage: 'Up', - }), - value: 'up', - }, - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', { - defaultMessage: 'Down', - }), - value: 'down', - }, - ]; - const locations = get(data, 'allPings.locations'); const locationOptions = !locations ? [AllLocationOption] : [AllLocationOption].concat( - locations.map(name => { - return { text: name, value: name }; - }) + locations.map(name => ({ + text: name, + 'data-test-subj': `xpack.uptime.pingList.locationOptions.${name}`, + value: name, + })) ); - const pings: Ping[] = data?.allPings?.pings ?? []; - const hasStatus: boolean = pings.reduce( - (hasHttpStatus: boolean, currentPing: Ping) => + (hasHttpStatus: boolean, currentPing) => hasHttpStatus || !!currentPing.http?.response?.status_code, false ); @@ -134,7 +152,7 @@ export const PingListComponent = ({ defaultMessage: 'Status', }), render: (pingStatus: string, item: Ping) => ( -
+
{pingStatus === 'up' ? i18n.translate('xpack.uptime.pingList.statusColumnHealthUpLabel', { @@ -189,7 +207,7 @@ export const PingListComponent = ({ name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', { defaultMessage: 'Error type', }), - render: (error: string) => error ?? '-', + render: (errorType: string) => errorType ?? '-', }, // Only add this column is there is any status present in list ...(hasStatus @@ -219,16 +237,16 @@ export const PingListComponent = ({ render: (item: Ping) => { return ( toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} - disabled={!item.error && !(item.http?.response?.body?.bytes > 0)} + onClick={() => toggleDetails(item, expandedRows, setExpandedRows)} + disabled={!item.error && !(item.http?.response?.body?.bytes ?? 0 > 0)} aria-label={ - itemIdToExpandedRowMap[item.id] + expandedRows[item.docId] ? i18n.translate('xpack.uptime.pingList.collapseRow', { defaultMessage: 'Collapse', }) : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) } - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + iconType={expandedRows[item.docId] ? 'arrowUp' : 'arrowDown'} /> ); }, @@ -236,104 +254,83 @@ export const PingListComponent = ({ ]; const pagination: Pagination = { - initialPageSize: 25, + initialPageSize: DEFAULT_PAGE_SIZE, pageIndex, pageSize, pageSizeOptions: [10, 25, 50, 100], - totalItemCount: data?.allPings?.total ?? pageSize, + /** + * we're not currently supporting pagination in this component + * so the first page is the only page + */ + totalItemCount: total, }; return ( - - - -

- + +

+ +

+
+ + + + + { + setStatus(selected.target.value); + }} /> -

-
- - - - - - - - - { - if (typeof selected.target.value === 'string') { - onSelectedStatusChange( - selected.target && selected.target.value !== '' - ? selected.target.value - : undefined - ); - } - }} - /> - - - - - { - onSelectedLocationChange( - selected.target && selected.target.value !== '' - ? selected.target.value - : null - ); - }} - /> - - - - - - - - - ) => { - onPageCountChange(criteria.page!.size); - onPageIndexChange(criteria.page!.index); - }} - /> -
-
+ + + + + { + setSelectedLocation(selected.target.value); + }} + /> + + + + + { + setPageSize(criteria.page!.size); + setPageIndex(criteria.page!.index); + }} + /> + ); }; - -export const PingList = withUptimeGraphQL( - PingListComponent, - pingsQuery -); diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/confirm_delete.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/confirm_delete.test.tsx.snap index 24ef7eda0d129..d83e45fea1aec 100644 --- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/confirm_delete.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/confirm_delete.test.tsx.snap @@ -6,6 +6,7 @@ exports[`ML Confirm Job Delete shallow renders without errors 1`] = ` buttonColor="danger" cancelButtonText="Cancel" confirmButtonText="Delete" + data-test-subj="uptimeMLJobDeleteConfirmModel" defaultFocusedButton="confirm" onCancel={[MockFunction]} onConfirm={[MockFunction]} @@ -35,6 +36,7 @@ exports[`ML Confirm Job Delete shallow renders without errors while loading 1`] buttonColor="danger" cancelButtonText="Cancel" confirmButtonText="Delete" + data-test-subj="uptimeMLJobDeleteConfirmModel" defaultFocusedButton="confirm" onCancel={[MockFunction]} onConfirm={[MockFunction]} diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/license_info.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/license_info.test.tsx.snap index 2457488c4facc..fb40a42e47f75 100644 --- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/license_info.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/license_info.test.tsx.snap @@ -4,6 +4,7 @@ exports[`ShowLicenseInfo renders without errors 1`] = ` Array [
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index ead27425c26f3..a83a1d99d7bb0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -3,6 +3,7 @@ exports[`ML Flyout component renders without errors 1`] = `